001/* 002 * (C) Copyright 2011-2012 Nuxeo SA (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.InvalidKeyException; 027import java.security.KeyPair; 028import java.security.KeyPairGenerator; 029import java.security.KeyStore; 030import java.security.KeyStoreException; 031import java.security.NoSuchAlgorithmException; 032import java.security.NoSuchProviderException; 033import java.security.PrivateKey; 034import java.security.PublicKey; 035import java.security.Security; 036import java.security.UnrecoverableKeyException; 037import java.security.cert.CertificateEncodingException; 038import java.security.cert.CertificateException; 039import java.security.cert.CertificateParsingException; 040import java.security.cert.X509Certificate; 041import java.util.Calendar; 042import java.util.Date; 043import java.util.Enumeration; 044import java.util.Vector; 045 046import org.apache.commons.logging.Log; 047import org.apache.commons.logging.LogFactory; 048import org.bouncycastle.asn1.ASN1Set; 049import org.bouncycastle.asn1.DERObjectIdentifier; 050import org.bouncycastle.asn1.DEROctetString; 051import org.bouncycastle.asn1.DERSet; 052import org.bouncycastle.asn1.pkcs.Attribute; 053import org.bouncycastle.asn1.pkcs.CertificationRequest; 054import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; 055import org.bouncycastle.asn1.x509.BasicConstraints; 056import org.bouncycastle.asn1.x509.ExtendedKeyUsage; 057import org.bouncycastle.asn1.x509.GeneralName; 058import org.bouncycastle.asn1.x509.GeneralNames; 059import org.bouncycastle.asn1.x509.KeyPurposeId; 060import org.bouncycastle.asn1.x509.KeyUsage; 061import org.bouncycastle.asn1.x509.X509Extension; 062import org.bouncycastle.asn1.x509.X509Extensions; 063import org.bouncycastle.jce.PKCS10CertificationRequest; 064import org.bouncycastle.jce.provider.BouncyCastleProvider; 065import org.bouncycastle.x509.X509V3CertificateGenerator; 066import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure; 067import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure; 068import org.nuxeo.ecm.platform.signature.api.exception.CertException; 069import org.nuxeo.ecm.platform.signature.api.pki.CertService; 070import org.nuxeo.ecm.platform.signature.api.pki.RootService; 071import org.nuxeo.ecm.platform.signature.api.user.AliasType; 072import org.nuxeo.ecm.platform.signature.api.user.AliasWrapper; 073import org.nuxeo.ecm.platform.signature.api.user.CNField; 074import org.nuxeo.ecm.platform.signature.api.user.UserInfo; 075import org.nuxeo.runtime.api.Framework; 076import org.nuxeo.runtime.model.DefaultComponent; 077 078/** 079 * Base implementation of the certification service. 080 */ 081public class CertServiceImpl extends DefaultComponent implements CertService { 082 083 protected RootService rootService; 084 085 private static final Log LOG = LogFactory.getLog(CertServiceImpl.class); 086 087 @Override 088 public void setRootService(RootService rootService) { 089 this.rootService = rootService; 090 } 091 092 protected X509Certificate rootCertificate; 093 094 private static final int CERTIFICATE_DURATION_IN_MONTHS = 12; 095 096 private static final String CERT_SIGNATURE_ALGORITHM = "SHA256WithRSAEncryption"; 097 098 private static final String KEY_ALGORITHM = "RSA"; 099 100 private static final int KEY_SIZE = 1024; 101 102 private static final String KEYSTORE_TYPE = "JKS"; 103 104 static { 105 if (Security.getProvider("BC") == null) { 106 Security.addProvider(new BouncyCastleProvider()); 107 } 108 } 109 110 protected X509Certificate createCertificateFromCSR(PKCS10CertificationRequest csr) throws CertException { 111 X509Certificate cert; 112 try { 113 X509V3CertificateGenerator certGen = new X509V3CertificateGenerator(); 114 certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis())); 115 certGen.setIssuerDN(getRootCertificate().getIssuerX500Principal()); 116 certGen.setSubjectDN(csr.getCertificationRequestInfo().getSubject()); 117 certGen.setNotBefore(getCertStartDate()); 118 certGen.setNotAfter(getCertEndDate()); 119 certGen.setPublicKey(csr.getPublicKey("BC")); 120 certGen.setSignatureAlgorithm(CERT_SIGNATURE_ALGORITHM); 121 certGen.addExtension(X509Extensions.SubjectKeyIdentifier, false, 122 new SubjectKeyIdentifierStructure(csr.getPublicKey("BC"))); 123 certGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure( 124 getRootCertificate())); 125 certGen.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(false)); 126 certGen.addExtension(X509Extensions.KeyUsage, true, new KeyUsage(KeyUsage.digitalSignature)); 127 certGen.addExtension(X509Extensions.ExtendedKeyUsage, true, new ExtendedKeyUsage( 128 KeyPurposeId.id_kp_serverAuth)); 129 130 ASN1Set attributes = csr.getCertificationRequestInfo().getAttributes(); 131 for (int i = 0; i != attributes.size(); i++) { 132 Attribute attr = Attribute.getInstance(attributes.getObjectAt(i)); 133 if (attr.getAttrType().equals(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest)) { 134 X509Extensions extensions = X509Extensions.getInstance(attr.getAttrValues().getObjectAt(0)); 135 @SuppressWarnings("rawtypes") 136 Enumeration e = extensions.oids(); 137 while (e.hasMoreElements()) { 138 DERObjectIdentifier oid = (DERObjectIdentifier) e.nextElement(); 139 X509Extension ext = extensions.getExtension(oid); 140 certGen.addExtension(oid, ext.isCritical(), ext.getValue().getOctets()); 141 } 142 } 143 } 144 145 KeyPair rootKeyPair = getKeyPair(rootService.getRootKeyStore(), rootService.getRootKeyAlias(), 146 rootService.getRootCertificateAlias(), rootService.getRootKeyPassword()); 147 cert = certGen.generate(rootKeyPair.getPrivate(), "BC"); 148 } catch (CertificateParsingException e) { 149 throw new CertException(e); 150 } catch (CertificateEncodingException e) { 151 throw new CertException(e); 152 } catch (InvalidKeyException e) { 153 throw new CertException(e); 154 } catch (IllegalStateException e) { 155 throw new CertException(e); 156 } catch (NoSuchProviderException e) { 157 throw new CertException(e); 158 } catch (NoSuchAlgorithmException e) { 159 throw new CertException(e); 160 } catch (java.security.SignatureException e) { 161 throw new CertException(e); 162 } 163 LOG.debug("Certificate generated for subject: " + cert.getSubjectDN()); 164 return cert; 165 } 166 167 @Override 168 public X509Certificate getRootCertificate() throws CertException { 169 if (rootCertificate == null) { 170 rootCertificate = getCertificate(getRootService().getRootKeyStore(), 171 getRootService().getRootCertificateAlias()); 172 } 173 return rootCertificate; 174 } 175 176 protected Date getCertStartDate() { 177 Calendar cal = Calendar.getInstance(); 178 return cal.getTime(); 179 } 180 181 protected Date getCertEndDate() { 182 Calendar cal = Calendar.getInstance(); 183 cal.add(Calendar.MONTH, CERTIFICATE_DURATION_IN_MONTHS); 184 return cal.getTime(); 185 } 186 187 @Override 188 public KeyStore initializeUser(UserInfo userInfo, String suppliedPassword) throws CertException { 189 char[] password = suppliedPassword.toCharArray(); 190 KeyStore ks = null; 191 String userName = userInfo.getUserFields().get(CNField.UserID); 192 AliasWrapper keystoreAlias = new AliasWrapper(userName); 193 try { 194 ks = java.security.KeyStore.getInstance(KEYSTORE_TYPE); 195 ks.load(null, password); 196 KeyPairGenerator keyGen = KeyPairGenerator.getInstance(KEY_ALGORITHM); 197 keyGen.initialize(KEY_SIZE); 198 KeyPair keyPair = keyGen.genKeyPair(); 199 java.security.cert.Certificate[] chain = { getRootCertificate() }; 200 ks.setKeyEntry(keystoreAlias.getId(AliasType.KEY), keyPair.getPrivate(), password, chain); 201 X509Certificate cert = getCertificate(keyPair, userInfo); 202 ks.setCertificateEntry(keystoreAlias.getId(AliasType.CERT), cert); 203 } catch (CertificateException e) { 204 throw new CertException(e); 205 } catch (IOException e) { 206 throw new CertException(e); 207 } catch (KeyStoreException e) { 208 throw new CertException(e); 209 } catch (NoSuchAlgorithmException e) { 210 throw new CertException(e); 211 } 212 return ks; 213 } 214 215 @Override 216 public KeyPair getKeyPair(KeyStore ks, String keyAlias, String certAlias, String keyPassword) throws CertException { 217 KeyPair keyPair = null; 218 try { 219 if (!ks.containsAlias(keyAlias)) { 220 throw new CertException("Missing keystore key entry for key alias:" + keyAlias); 221 } 222 if (!ks.containsAlias(certAlias)) { 223 throw new CertException("Missing keystore certificate entry for :" + certAlias); 224 } 225 PrivateKey privateKey = (PrivateKey) ks.getKey(keyAlias, keyPassword.toCharArray()); 226 X509Certificate cert = (X509Certificate) ks.getCertificate(certAlias); 227 PublicKey publicKey = cert.getPublicKey(); 228 keyPair = new KeyPair(publicKey, privateKey); 229 } catch (UnrecoverableKeyException e) { 230 throw new CertException(e); 231 } catch (KeyStoreException e) { 232 throw new CertException(e); 233 } catch (NoSuchAlgorithmException e) { 234 throw new CertException(e); 235 } 236 return keyPair; 237 } 238 239 @Override 240 public X509Certificate getCertificate(KeyStore ks, String certificateAlias) throws CertException { 241 X509Certificate certificate = null; 242 try { 243 244 if (ks == null) { 245 throw new CertException("Keystore missing for " + certificateAlias); 246 } 247 if (ks.containsAlias(certificateAlias)) { 248 certificate = (X509Certificate) ks.getCertificate(certificateAlias); 249 } else { 250 throw new CertException("Certificate not found"); 251 } 252 } catch (KeyStoreException e) { 253 throw new CertException(e); 254 } 255 return certificate; 256 } 257 258 protected X509Certificate getCertificate(KeyPair keyPair, UserInfo userInfo) throws CertException { 259 PKCS10CertificationRequest csr = (PKCS10CertificationRequest) generateCSR(keyPair, userInfo); 260 X509Certificate certificate = createCertificateFromCSR(csr); 261 return certificate; 262 } 263 264 protected CertificationRequest generateCSR(KeyPair keyPair, UserInfo userInfo) throws CertException { 265 266 CertificationRequest csr; 267 268 GeneralNames subjectAltName = new GeneralNames(new GeneralName(GeneralName.rfc822Name, 269 userInfo.getUserFields().get(CNField.Email))); 270 271 Vector<DERObjectIdentifier> objectIdentifiers = new Vector<DERObjectIdentifier>(); 272 Vector<X509Extension> extensionValues = new Vector<X509Extension>(); 273 274 objectIdentifiers.add(X509Extensions.SubjectAlternativeName); 275 extensionValues.add(new X509Extension(false, new DEROctetString(subjectAltName))); 276 277 X509Extensions extensions = new X509Extensions(objectIdentifiers, extensionValues); 278 279 Attribute attribute = new Attribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, new DERSet(extensions)); 280 try { 281 csr = new PKCS10CertificationRequest(CERT_SIGNATURE_ALGORITHM, userInfo.getX500Principal(), 282 keyPair.getPublic(), new DERSet(attribute), keyPair.getPrivate()); 283 } catch (InvalidKeyException e) { 284 throw new CertException(e); 285 } catch (NoSuchAlgorithmException e) { 286 throw new CertException(e); 287 } catch (NoSuchProviderException e) { 288 throw new CertException(e); 289 } catch (java.security.SignatureException e) { 290 throw new CertException(e); 291 } 292 return csr; 293 } 294 295 @Override 296 public KeyStore getKeyStore(InputStream keystoreIS, String password) throws CertException { 297 KeyStore ks; 298 try { 299 ks = java.security.KeyStore.getInstance(KEYSTORE_TYPE); 300 ks.load(keystoreIS, password.toCharArray()); 301 } catch (KeyStoreException e) { 302 throw new CertException(e); 303 } catch (NoSuchAlgorithmException e) { 304 throw new CertException(e); 305 } catch (CertificateException e) { 306 throw new CertException(e); 307 } catch (IOException e) { 308 if (String.valueOf(e.getMessage()).contains("password was incorrect")) { 309 // "Keystore was tampered with, or password was incorrect" 310 // is not very useful to end-users 311 throw new CertException("Incorrect password"); 312 } 313 throw new CertException(e); 314 } 315 return ks; 316 } 317 318 @Override 319 public String getCertificateEmail(X509Certificate certificate) throws CertException { 320 String emailOID = "2.5.29.17"; 321 byte[] emailBytes = certificate.getExtensionValue(emailOID); 322 String certificateEmail = null; 323 try { 324 byte[] octets = ((DEROctetString) org.bouncycastle.asn1.ASN1Object.fromByteArray(emailBytes)).getOctets(); 325 GeneralNames generalNameCont = GeneralNames.getInstance(org.bouncycastle.asn1.ASN1Object.fromByteArray(octets)); 326 GeneralName[] generalNames = generalNameCont.getNames(); 327 if (generalNames.length > 0) { 328 GeneralName generalName = generalNames[0]; 329 certificateEmail = generalName.getName().toString(); 330 } 331 } catch (IOException e) { 332 throw new CertException("Email could not be extracted from certificate", e); 333 } 334 return certificateEmail; 335 } 336 337 @Override 338 public void storeCertificate(KeyStore keystore, OutputStream os, String keystorePassword) throws CertException { 339 try { 340 keystore.store(os, keystorePassword.toCharArray()); 341 } catch (KeyStoreException e) { 342 throw new CertException(e); 343 } catch (NoSuchAlgorithmException e) { 344 throw new CertException(e); 345 } catch (CertificateException e) { 346 throw new CertException(e); 347 } catch (IOException e) { 348 throw new CertException(e); 349 } 350 } 351 352 protected RootService getRootService() throws CertException { 353 if (rootService == null) { 354 rootService = Framework.getService(RootService.class); 355 } 356 return rootService; 357 } 358}