View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.chemistry.opencmis.client.bindings.spi;
20  
21  import java.io.BufferedInputStream;
22  import java.io.FileInputStream;
23  import java.io.FileNotFoundException;
24  import java.io.InputStream;
25  import java.security.KeyStore;
26  import java.util.Enumeration;
27  import java.util.Locale;
28  
29  import javax.net.ssl.KeyManagerFactory;
30  import javax.net.ssl.SSLContext;
31  import javax.net.ssl.SSLSocketFactory;
32  
33  import org.apache.chemistry.opencmis.commons.SessionParameter;
34  import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException;
35  import org.apache.chemistry.opencmis.commons.impl.IOUtils;
36  import org.slf4j.Logger;
37  import org.slf4j.LoggerFactory;
38  
39  /**
40   * Client Certificate Authentication Provider.
41   * 
42   * Enables the use of SSL client certificates for authentication. It requires
43   * the path to a JKS key file and its pass phrase.
44   * 
45   * <pre>
46   * {@code
47   * SessionFactory factory = ...
48   * 
49   * Map<String, String> parameter = new HashMap<String, String>();
50   * 
51   * parameter.put(SessionParameter.ATOMPUB_URL, "https://localhost/cmis/atom");
52   * parameter.put(SessionParameter.BINDING_TYPE, BindingType.ATOMPUB.value());
53   * parameter.put(SessionParameter.REPOSITORY_ID, "myRepository");
54   * 
55   * parameter.put(SessionParameter.AUTHENTICATION_PROVIDER_CLASS, "org.apache.chemistry.opencmis.client.bindings.spi.ClientCertificateAuthenticationProvider");
56   * 
57   * parameter.put(SessionParameter.CLIENT_CERT_KEYFILE, "/path/to/mycert.jks");
58   * parameter.put(SessionParameter.CLIENT_CERT_PASSPHRASE, "changeme");
59   * 
60   * ...
61   * Session session = factory.createSession(parameter);
62   * }
63   * </pre>
64   * 
65   */
66  public class ClientCertificateAuthenticationProvider extends StandardAuthenticationProvider {
67      private static final long serialVersionUID = 1L;
68  
69      private static final Logger LOG = LoggerFactory.getLogger(ClientCertificateAuthenticationProvider.class);
70  
71      private SSLSocketFactory socketFactory;
72  
73      @Override
74      public void setSession(BindingSession session) {
75          super.setSession(session);
76  
77          if (socketFactory == null) {
78              Object keyfile = getSession().get(SessionParameter.CLIENT_CERT_KEYFILE);
79              if (keyfile instanceof String) {
80                  Object passphrase = getSession().get(SessionParameter.CLIENT_CERT_PASSPHRASE);
81  
82                  String keyfileStr = ((String) keyfile).trim();
83                  String passphraseStr = passphrase instanceof String ? ((String) passphrase).trim() : null;
84  
85                  socketFactory = createSSLSocketFactory(keyfileStr, passphraseStr);
86              }
87          }
88      }
89  
90      @Override
91      public SSLSocketFactory getSSLSocketFactory() {
92          return socketFactory;
93      }
94  
95      protected SSLSocketFactory createSSLSocketFactory(String keyFile, String passphrase) {
96          assert keyFile != null;
97  
98          if (LOG.isDebugEnabled()) {
99              LOG.debug("Using key file '{}'", keyFile);
100         }
101 
102         try {
103             char[] passphraseChars = passphrase == null ? null : passphrase.toCharArray();
104 
105             KeyStore keyStore;
106 
107             String ext = getExtension(keyFile);
108             if ("p12".equals(ext) || "pfx".equals(ext)) {
109                 keyStore = KeyStore.getInstance("PKCS12");
110             } else {
111                 keyStore = KeyStore.getInstance("JKS");
112             }
113 
114             // read key store
115             InputStream keyStream = null;
116             try {
117                 keyStream = new BufferedInputStream(new FileInputStream(keyFile));
118                 keyStore.load(keyStream, passphraseChars);
119             } finally {
120                 IOUtils.closeQuietly(keyStream);
121             }
122 
123             if (LOG.isDebugEnabled()) {
124                 LOG.debug("Key store type: {}", keyStore.getType());
125 
126                 StringBuilder sb = new StringBuilder();
127                 Enumeration<String> aliases = keyStore.aliases();
128                 while (aliases.hasMoreElements()) {
129                     if (sb.length() > 0) {
130                         sb.append(", ");
131                     }
132                     sb.append(aliases.nextElement());
133                 }
134 
135                 LOG.debug("Aliases in key store: {}", sb.toString());
136             }
137 
138             // create socket factory
139             KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
140             keyManagerFactory.init(keyStore, passphraseChars);
141 
142             SSLContext context = SSLContext.getInstance("TLS");
143             context.init(keyManagerFactory.getKeyManagers(), null, null);
144 
145             return context.getSocketFactory();
146         } catch (FileNotFoundException fnfe) {
147             throw new CmisRuntimeException("Key file '" + keyFile + "' not found!", fnfe);
148         } catch (Exception e) {
149             throw new CmisRuntimeException("Cannot set up client certificate: " + e.toString(), e);
150         }
151     }
152 
153     private String getExtension(String filename) {
154         int x = filename.lastIndexOf('.');
155         if (x > -1) {
156             return filename.substring(x + 1).toLowerCase(Locale.ENGLISH);
157         }
158 
159         return null;
160     }
161 }