001/* 002 * (C) Copyright 2011-2018 Nuxeo (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Wojciech Sulejman 018 * Florent Guillaume 019 */ 020package org.nuxeo.ecm.platform.signature.core.pki; 021 022import java.io.IOException; 023import java.io.InputStream; 024import java.io.OutputStream; 025import java.math.BigInteger; 026import java.security.GeneralSecurityException; 027import java.security.KeyPair; 028import java.security.KeyPairGenerator; 029import java.security.KeyStore; 030import java.security.KeyStoreException; 031import java.security.NoSuchAlgorithmException; 032import java.security.PrivateKey; 033import java.security.PublicKey; 034import java.security.Security; 035import java.security.UnrecoverableKeyException; 036import java.security.cert.CertificateException; 037import java.security.cert.X509Certificate; 038import java.util.Calendar; 039import java.util.Collection; 040import java.util.Date; 041import java.util.List; 042 043import javax.security.auth.x500.X500Principal; 044 045import org.apache.commons.logging.Log; 046import org.apache.commons.logging.LogFactory; 047import org.bouncycastle.asn1.x509.BasicConstraints; 048import org.bouncycastle.asn1.x509.ExtendedKeyUsage; 049import org.bouncycastle.asn1.x509.Extension; 050import org.bouncycastle.asn1.x509.GeneralName; 051import org.bouncycastle.asn1.x509.GeneralNames; 052import org.bouncycastle.asn1.x509.KeyPurposeId; 053import org.bouncycastle.asn1.x509.KeyUsage; 054import org.bouncycastle.cert.X509v3CertificateBuilder; 055import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; 056import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils; 057import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; 058import org.bouncycastle.jce.provider.BouncyCastleProvider; 059import org.bouncycastle.operator.ContentSigner; 060import org.bouncycastle.operator.OperatorException; 061import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; 062import org.bouncycastle.x509.extension.X509ExtensionUtil; 063import org.nuxeo.ecm.platform.signature.api.exception.CertException; 064import org.nuxeo.ecm.platform.signature.api.pki.CertService; 065import org.nuxeo.ecm.platform.signature.api.pki.RootService; 066import org.nuxeo.ecm.platform.signature.api.user.AliasType; 067import org.nuxeo.ecm.platform.signature.api.user.AliasWrapper; 068import org.nuxeo.ecm.platform.signature.api.user.CNField; 069import org.nuxeo.ecm.platform.signature.api.user.UserInfo; 070import org.nuxeo.runtime.api.Framework; 071import org.nuxeo.runtime.model.DefaultComponent; 072 073/** 074 * Base implementation of the certification service. 075 */ 076public class CertServiceImpl extends DefaultComponent implements CertService { 077 078 protected RootService rootService; 079 080 private static final Log LOG = LogFactory.getLog(CertServiceImpl.class); 081 082 @Override 083 public void setRootService(RootService rootService) { 084 this.rootService = rootService; 085 } 086 087 protected X509Certificate rootCertificate; 088 089 private static final int CERTIFICATE_DURATION_IN_MONTHS = 12; 090 091 private static final String CERT_SIGNATURE_ALGORITHM = "SHA256WithRSAEncryption"; 092 093 private static final String KEY_ALGORITHM = "RSA"; 094 095 private static final int KEY_SIZE = 1024; 096 097 private static final String KEYSTORE_TYPE = "JKS"; 098 099 static { 100 if (Security.getProvider("BC") == null) { 101 Security.addProvider(new BouncyCastleProvider()); 102 } 103 } 104 105 106 @Override 107 public X509Certificate getRootCertificate() throws CertException { 108 if (rootCertificate == null) { 109 rootCertificate = getCertificate(getRootService().getRootKeyStore(), 110 getRootService().getRootCertificateAlias()); 111 } 112 return rootCertificate; 113 } 114 115 protected Date getCertStartDate() { 116 Calendar cal = Calendar.getInstance(); 117 return cal.getTime(); 118 } 119 120 protected Date getCertEndDate() { 121 Calendar cal = Calendar.getInstance(); 122 cal.add(Calendar.MONTH, CERTIFICATE_DURATION_IN_MONTHS); 123 return cal.getTime(); 124 } 125 126 @Override 127 public KeyStore initializeUser(UserInfo userInfo, String suppliedPassword) throws CertException { 128 char[] password = suppliedPassword.toCharArray(); 129 KeyStore ks = null; 130 String userName = userInfo.getUserFields().get(CNField.UserID); 131 AliasWrapper keystoreAlias = new AliasWrapper(userName); 132 try { 133 ks = java.security.KeyStore.getInstance(KEYSTORE_TYPE); 134 ks.load(null, password); 135 KeyPairGenerator keyGen = KeyPairGenerator.getInstance(KEY_ALGORITHM); 136 keyGen.initialize(KEY_SIZE); 137 KeyPair keyPair = keyGen.genKeyPair(); 138 java.security.cert.Certificate[] chain = { getRootCertificate() }; 139 ks.setKeyEntry(keystoreAlias.getId(AliasType.KEY), keyPair.getPrivate(), password, chain); 140 X509Certificate cert = getCertificate(keyPair, userInfo); 141 ks.setCertificateEntry(keystoreAlias.getId(AliasType.CERT), cert); 142 } catch (CertificateException e) { 143 throw new CertException(e); 144 } catch (IOException e) { 145 throw new CertException(e); 146 } catch (KeyStoreException e) { 147 throw new CertException(e); 148 } catch (NoSuchAlgorithmException e) { 149 throw new CertException(e); 150 } 151 return ks; 152 } 153 154 @Override 155 public KeyPair getKeyPair(KeyStore ks, String keyAlias, String certAlias, String keyPassword) throws CertException { 156 KeyPair keyPair = null; 157 try { 158 if (!ks.containsAlias(keyAlias)) { 159 throw new CertException("Missing keystore key entry for key alias:" + keyAlias); 160 } 161 if (!ks.containsAlias(certAlias)) { 162 throw new CertException("Missing keystore certificate entry for :" + certAlias); 163 } 164 PrivateKey privateKey = (PrivateKey) ks.getKey(keyAlias, keyPassword.toCharArray()); 165 X509Certificate cert = (X509Certificate) ks.getCertificate(certAlias); 166 PublicKey publicKey = cert.getPublicKey(); 167 keyPair = new KeyPair(publicKey, privateKey); 168 } catch (UnrecoverableKeyException e) { 169 throw new CertException(e); 170 } catch (KeyStoreException e) { 171 throw new CertException(e); 172 } catch (NoSuchAlgorithmException e) { 173 throw new CertException(e); 174 } 175 return keyPair; 176 } 177 178 @Override 179 public X509Certificate getCertificate(KeyStore ks, String certificateAlias) throws CertException { 180 X509Certificate certificate = null; 181 try { 182 183 if (ks == null) { 184 throw new CertException("Keystore missing for " + certificateAlias); 185 } 186 if (ks.containsAlias(certificateAlias)) { 187 certificate = (X509Certificate) ks.getCertificate(certificateAlias); 188 } else { 189 throw new CertException("Certificate not found"); 190 } 191 } catch (KeyStoreException e) { 192 throw new CertException(e); 193 } 194 return certificate; 195 } 196 197 protected X509Certificate getCertificate(KeyPair keyPair, UserInfo userInfo) throws CertException { 198 X509Certificate rootCertificate = getRootCertificate(); 199 X500Principal issuer = rootCertificate.getIssuerX500Principal(); 200 BigInteger serial = BigInteger.valueOf(System.currentTimeMillis()); 201 X500Principal principal = userInfo.getX500Principal(); 202 String email = userInfo.getUserFields().get(CNField.Email); 203 try { 204 JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils(); 205 X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(issuer, serial, getCertStartDate(), 206 getCertEndDate(), principal, keyPair.getPublic()); 207 builder.addExtension(Extension.authorityKeyIdentifier, false, 208 extUtils.createAuthorityKeyIdentifier(rootCertificate)) 209 .addExtension(Extension.subjectKeyIdentifier, false, 210 extUtils.createSubjectKeyIdentifier(keyPair.getPublic())) 211 .addExtension(Extension.basicConstraints, true, new BasicConstraints(false)) 212 .addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.digitalSignature)) 213 .addExtension(Extension.extendedKeyUsage, true, new ExtendedKeyUsage(KeyPurposeId.id_kp_serverAuth)) 214 .addExtension(Extension.subjectAlternativeName, false, 215 new GeneralNames(new GeneralName(GeneralName.rfc822Name, email))); 216 ContentSigner signer = new JcaContentSignerBuilder(CERT_SIGNATURE_ALGORITHM).setProvider("BC") 217 .build(keyPair.getPrivate()); 218 return new JcaX509CertificateConverter().setProvider("BC").getCertificate(builder.build(signer)); 219 } catch (GeneralSecurityException | OperatorException | IOException e) { 220 throw new CertException(e); 221 } 222 } 223 224 @Override 225 public KeyStore getKeyStore(InputStream keystoreIS, String password) throws CertException { 226 KeyStore ks; 227 try { 228 ks = java.security.KeyStore.getInstance(KEYSTORE_TYPE); 229 ks.load(keystoreIS, password.toCharArray()); 230 } catch (KeyStoreException e) { 231 throw new CertException(e); 232 } catch (NoSuchAlgorithmException e) { 233 throw new CertException(e); 234 } catch (CertificateException e) { 235 throw new CertException(e); 236 } catch (IOException e) { 237 if (String.valueOf(e.getMessage()).contains("password was incorrect")) { 238 // "Keystore was tampered with, or password was incorrect" 239 // is not very useful to end-users 240 throw new CertException("Incorrect password"); 241 } 242 throw new CertException(e); 243 } 244 return ks; 245 } 246 247 @Override 248 public String getCertificateEmail(X509Certificate certificate) throws CertException { 249 try { 250 @SuppressWarnings("unchecked") 251 Collection<List<?>> altNames = X509ExtensionUtil.getSubjectAlternativeNames(certificate); 252 for (List<?> names : altNames) { 253 if (Integer.valueOf(GeneralName.rfc822Name).equals(names.get(0))) { 254 return (String) names.get(1); 255 } 256 } 257 return null; 258 } catch (GeneralSecurityException e) { 259 throw new CertException(e); 260 } 261 } 262 263 @Override 264 public void storeCertificate(KeyStore keystore, OutputStream os, String keystorePassword) throws CertException { 265 try { 266 keystore.store(os, keystorePassword.toCharArray()); 267 } catch (KeyStoreException e) { 268 throw new CertException(e); 269 } catch (NoSuchAlgorithmException e) { 270 throw new CertException(e); 271 } catch (CertificateException e) { 272 throw new CertException(e); 273 } catch (IOException e) { 274 throw new CertException(e); 275 } 276 } 277 278 protected RootService getRootService() throws CertException { 279 if (rootService == null) { 280 rootService = Framework.getService(RootService.class); 281 } 282 return rootService; 283 } 284}