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