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}