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 @Override 106 public X509Certificate getRootCertificate() throws CertException { 107 if (rootCertificate == null) { 108 rootCertificate = getCertificate(getRootService().getRootKeyStore(), 109 getRootService().getRootCertificateAlias()); 110 } 111 return rootCertificate; 112 } 113 114 protected Date getCertStartDate() { 115 Calendar cal = Calendar.getInstance(); 116 return cal.getTime(); 117 } 118 119 protected Date getCertEndDate() { 120 Calendar cal = Calendar.getInstance(); 121 cal.add(Calendar.MONTH, CERTIFICATE_DURATION_IN_MONTHS); 122 return cal.getTime(); 123 } 124 125 @Override 126 public KeyStore initializeUser(UserInfo userInfo, String suppliedPassword) throws CertException { 127 char[] password = suppliedPassword.toCharArray(); 128 KeyStore ks = null; 129 String userName = userInfo.getUserFields().get(CNField.UserID); 130 AliasWrapper keystoreAlias = new AliasWrapper(userName); 131 try { 132 ks = java.security.KeyStore.getInstance(KEYSTORE_TYPE); 133 ks.load(null, password); 134 KeyPairGenerator keyGen = KeyPairGenerator.getInstance(KEY_ALGORITHM); 135 keyGen.initialize(KEY_SIZE); 136 KeyPair keyPair = keyGen.genKeyPair(); 137 java.security.cert.Certificate[] chain = { getRootCertificate() }; 138 ks.setKeyEntry(keystoreAlias.getId(AliasType.KEY), keyPair.getPrivate(), password, chain); 139 X509Certificate cert = getCertificate(keyPair, userInfo); 140 ks.setCertificateEntry(keystoreAlias.getId(AliasType.CERT), cert); 141 } catch (CertificateException e) { 142 throw new CertException(e); 143 } catch (IOException e) { 144 throw new CertException(e); 145 } catch (KeyStoreException e) { 146 throw new CertException(e); 147 } catch (NoSuchAlgorithmException e) { 148 throw new CertException(e); 149 } 150 return ks; 151 } 152 153 @Override 154 public KeyPair getKeyPair(KeyStore ks, String keyAlias, String certAlias, String keyPassword) throws CertException { 155 KeyPair keyPair = null; 156 try { 157 if (!ks.containsAlias(keyAlias)) { 158 throw new CertException("Missing keystore key entry for key alias:" + keyAlias); 159 } 160 if (!ks.containsAlias(certAlias)) { 161 throw new CertException("Missing keystore certificate entry for :" + certAlias); 162 } 163 PrivateKey privateKey = (PrivateKey) ks.getKey(keyAlias, keyPassword.toCharArray()); 164 X509Certificate cert = (X509Certificate) ks.getCertificate(certAlias); 165 PublicKey publicKey = cert.getPublicKey(); 166 keyPair = new KeyPair(publicKey, privateKey); 167 } catch (UnrecoverableKeyException e) { 168 throw new CertException(e); 169 } catch (KeyStoreException e) { 170 throw new CertException(e); 171 } catch (NoSuchAlgorithmException e) { 172 throw new CertException(e); 173 } 174 return keyPair; 175 } 176 177 @Override 178 public X509Certificate getCertificate(KeyStore ks, String certificateAlias) throws CertException { 179 X509Certificate certificate = null; 180 try { 181 182 if (ks == null) { 183 throw new CertException("Keystore missing for " + certificateAlias); 184 } 185 if (ks.containsAlias(certificateAlias)) { 186 certificate = (X509Certificate) ks.getCertificate(certificateAlias); 187 } else { 188 throw new CertException("Certificate not found"); 189 } 190 } catch (KeyStoreException e) { 191 throw new CertException(e); 192 } 193 return certificate; 194 } 195 196 protected X509Certificate getCertificate(KeyPair keyPair, UserInfo userInfo) throws CertException { 197 X509Certificate rootCertificate = getRootCertificate(); 198 X500Principal issuer = rootCertificate.getIssuerX500Principal(); 199 BigInteger serial = BigInteger.valueOf(System.currentTimeMillis()); 200 X500Principal principal = userInfo.getX500Principal(); 201 String email = userInfo.getUserFields().get(CNField.Email); 202 try { 203 JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils(); 204 X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(issuer, serial, getCertStartDate(), 205 getCertEndDate(), principal, keyPair.getPublic()); 206 builder.addExtension(Extension.authorityKeyIdentifier, false, 207 extUtils.createAuthorityKeyIdentifier(rootCertificate)) 208 .addExtension(Extension.subjectKeyIdentifier, false, 209 extUtils.createSubjectKeyIdentifier(keyPair.getPublic())) 210 .addExtension(Extension.basicConstraints, true, new BasicConstraints(false)) 211 .addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.digitalSignature)) 212 .addExtension(Extension.extendedKeyUsage, true, new ExtendedKeyUsage(KeyPurposeId.id_kp_serverAuth)) 213 .addExtension(Extension.subjectAlternativeName, false, 214 new GeneralNames(new GeneralName(GeneralName.rfc822Name, email))); 215 ContentSigner signer = new JcaContentSignerBuilder(CERT_SIGNATURE_ALGORITHM).setProvider("BC") 216 .build(keyPair.getPrivate()); 217 return new JcaX509CertificateConverter().setProvider("BC").getCertificate(builder.build(signer)); 218 } catch (GeneralSecurityException | OperatorException | IOException e) { 219 throw new CertException(e); 220 } 221 } 222 223 @Override 224 public KeyStore getKeyStore(InputStream keystoreIS, String password) throws CertException { 225 KeyStore ks; 226 try { 227 ks = java.security.KeyStore.getInstance(KEYSTORE_TYPE); 228 ks.load(keystoreIS, password.toCharArray()); 229 } catch (KeyStoreException e) { 230 throw new CertException(e); 231 } catch (NoSuchAlgorithmException e) { 232 throw new CertException(e); 233 } catch (CertificateException e) { 234 throw new CertException(e); 235 } catch (IOException e) { 236 if (String.valueOf(e.getMessage()).contains("password was incorrect")) { 237 // "Keystore was tampered with, or password was incorrect" 238 // is not very useful to end-users 239 throw new CertException("Incorrect password"); 240 } 241 throw new CertException(e); 242 } 243 return ks; 244 } 245 246 @Override 247 public String getCertificateEmail(X509Certificate certificate) throws CertException { 248 try { 249 @SuppressWarnings("unchecked") 250 Collection<List<?>> altNames = X509ExtensionUtil.getSubjectAlternativeNames(certificate); 251 for (List<?> names : altNames) { 252 if (Integer.valueOf(GeneralName.rfc822Name).equals(names.get(0))) { 253 return (String) names.get(1); 254 } 255 } 256 return null; 257 } catch (GeneralSecurityException e) { 258 throw new CertException(e); 259 } 260 } 261 262 @Override 263 public void storeCertificate(KeyStore keystore, OutputStream os, String keystorePassword) throws CertException { 264 try { 265 keystore.store(os, keystorePassword.toCharArray()); 266 } catch (KeyStoreException e) { 267 throw new CertException(e); 268 } catch (NoSuchAlgorithmException e) { 269 throw new CertException(e); 270 } catch (CertificateException e) { 271 throw new CertException(e); 272 } catch (IOException e) { 273 throw new CertException(e); 274 } 275 } 276 277 protected RootService getRootService() throws CertException { 278 if (rootService == null) { 279 rootService = Framework.getService(RootService.class); 280 } 281 return rootService; 282 } 283}