com.rcn.service.CertificateService.java Source code

Java tutorial

Introduction

Here is the source code for com.rcn.service.CertificateService.java

Source

/*
 *
 * Copyright (c) 2016  by  Alex Shpurov. All rights reserved.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
 * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

package com.rcn.service;

import com.rcn.core.Tuple;
import com.rcn.domain.LicenseKey;
import com.rcn.repository.HistoryRepository;
import com.rcn.repository.ResourceRepository;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.x509.*;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.PKCS10CertificationRequest;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMReader;
import org.bouncycastle.openssl.PEMWriter;
import org.bouncycastle.openssl.PasswordFinder;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.security.auth.x500.X500Principal;
import java.io.*;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.*;

import static com.rcn.repository.ResourceRepository.TYPE_Resource;
import static com.rcn.repository.ResourceRepository.TYPE_Token;

/*******************************************************************************
 * $ Date: 5/20/2016 $
 * $ Author: Alex Shpurov  $
 ********************************************************************************/
@Service
public class CertificateService {

    private static final Logger log = LoggerFactory.getLogger(CertificateService.class);
    @Autowired
    private ResourceRepository resourceRepository;

    @Autowired
    private HistoryRepository historyRepository;

    private static final String ANY_POLICY = "2.5.29.32.0";

    public void storeCert(Long resourceId, String pemCert, String password) {
        Tuple<KeyPair, X509Certificate> c = fromPem(pemCert, password);
        X509Certificate certificate = c.getY();
        String thumb = getThumbPrint(certificate);
        resourceRepository.updateCertInfo(resourceId, thumb, certificate.getNotBefore().getTime(),
                certificate.getNotAfter().getTime(), certificate.getIssuerDN().toString(),
                String.valueOf(certificate.getSerialNumber()), pemCert);
    }

    private KeyPair generateKey() {
        KeyPairGenerator keyGen = null;
        try {
            keyGen = KeyPairGenerator.getInstance("RSA", "BC");
            keyGen.initialize(2048);
            return keyGen.genKeyPair();
        } catch (Exception e) {
            throw new SecurityException(e);
        }
    }

    public String generateCert(String certName, String password, int validDays, Optional<String> caPem,
            String caPassword, boolean generateCaCert, Optional<String> pkc10Request) {
        try {
            Optional<Tuple<KeyPair, X509Certificate>> caTuple = caPem.map(c -> fromPem(c, caPassword));
            Optional<KeyPair> ca = caTuple.map(a -> a.getX());

            Optional<KeyPair> optKeyPair = Optional.ofNullable(pkc10Request.isPresent() ? null : generateKey());

            PublicKey publicKey = pkc10Request.map(this::fromPkcs10).orElseGet(() -> optKeyPair.get().getPublic());

            Date now = new Date();
            Calendar tenYears = Calendar.getInstance();
            tenYears.add(Calendar.DATE, validDays);
            X500Principal x500Principal = new X500Principal(certName);
            BigInteger serial = rndBigInt(new BigInteger("8180385048")); //max value for SN
            X500Principal issuer = caTuple.map(a -> a.getY().getSubjectX500Principal())
                    .orElseGet(() -> x500Principal);
            JcaX509v3CertificateBuilder v3CertGen = new JcaX509v3CertificateBuilder(issuer, serial, now,
                    new Date(tenYears.getTimeInMillis()), x500Principal, publicKey);

            v3CertGen.addExtension(X509Extension.subjectKeyIdentifier, false,
                    new SubjectKeyIdentifier(getSubjectPublicKeyInfo(publicKey)));

            ca.ifPresent(caKey -> v3CertGen.addExtension(X509Extension.authorityKeyIdentifier, false,
                    new AuthorityKeyIdentifier(getSubjectPublicKeyInfo(caKey.getPublic()))));

            if (generateCaCert) {
                addCaExtension(v3CertGen);
            } else {
                addRegularExtension(Optional.empty(), v3CertGen);
            }

            KeyPair caKey = ca.orElseGet(
                    () -> optKeyPair.orElseThrow(() -> new SecurityException("no private key for self-sign cert")));
            X509Certificate certificate = new JcaX509CertificateConverter()
                    .setProvider(BouncyCastleProvider.PROVIDER_NAME)
                    .getCertificate(v3CertGen.build(new JcaContentSignerBuilder("SHA256WithRSAEncryption")
                            .setProvider(BouncyCastleProvider.PROVIDER_NAME).build(caKey.getPrivate())));
            String certPem = toPem(certificate, Optional.empty());

            String keyPem = optKeyPair
                    .map(k -> toPem(k, Optional.ofNullable(password.length() > 0 ? password : null))).orElse("");

            return String.format("%s%s", certPem, keyPem);
        } catch (Exception e) {
            throw new SecurityException(e);
        }
    }

    private void addCaExtension(JcaX509v3CertificateBuilder v3CertGen) {
        v3CertGen.addExtension(X509Extension.basicConstraints, false, new BasicConstraints(true));

        v3CertGen.addExtension(X509Extension.keyUsage, false,
                new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign));

        ASN1EncodableVector intPolicies = new ASN1EncodableVector();

        intPolicies.add(new PolicyInformation(new DERObjectIdentifier(ANY_POLICY)));

        v3CertGen.addExtension(X509Extension.certificatePolicies, false, new DERSequence(intPolicies));
    }

    private void addRegularExtension(Optional<GeneralNames> generalNames, JcaX509v3CertificateBuilder v3CertGen) {
        v3CertGen.addExtension(X509Extension.keyUsage, false,
                new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment));

        Vector<KeyPurposeId> authTypes = new Vector<>();
        authTypes.add(KeyPurposeId.id_kp_clientAuth);
        authTypes.add(KeyPurposeId.id_kp_serverAuth);

        v3CertGen.addExtension(X509Extension.extendedKeyUsage, false, new ExtendedKeyUsage(authTypes));

        generalNames.ifPresent(n -> v3CertGen.addExtension(X509Extension.subjectAlternativeName, false, n));
    }

    private BigInteger rndBigInt(BigInteger max) {
        Random rnd = new Random();
        do {
            BigInteger i = new BigInteger(max.bitLength(), rnd);
            if (i.compareTo(max) <= 0)
                return i;
        } while (true);
    }

    private GeneralNames toGeneralNames(String altName, Map<String, String> generalNameMap) {

        GeneralName subjectAltName = new GeneralName(GeneralName.rfc822Name, altName);
        List<GeneralName> generalNameList = new ArrayList<GeneralName>();
        generalNameList.add(subjectAltName);
        generalNameMap.keySet().forEach(oid -> {
            String value = generalNameMap.get(oid);
            DERUTF8String derUtf8 = new DERUTF8String(value);
            ASN1Encodable oidObj = new DERObjectIdentifier(oid);
            ASN1Encodable valueObj = new DERTaggedObject(true, 0, derUtf8);
            ASN1Encodable[] asn1Seq = new ASN1Encodable[] { oidObj, valueObj };
            generalNameList.add(new GeneralName(GeneralName.otherName, new DERSequence(asn1Seq)));
        });

        return new GeneralNames(new DERSequence(generalNameList.toArray(new GeneralName[0])));
    }

    private SubjectPublicKeyInfo getSubjectPublicKeyInfo(PublicKey pub) {
        ByteArrayInputStream bIn = new ByteArrayInputStream(pub.getEncoded());
        try {
            return new SubjectPublicKeyInfo((ASN1Sequence) new ASN1InputStream(bIn).readObject());
        } catch (IOException e) {
            throw new SecurityException(e);
        }
    }

    private byte[] p12Write(PrivateKey key, Certificate[] certChain, String password) {
        //BC 1.42 for jdk 1.6 provider is buggy for PKSC12, so we use default
        KeyStore store = null;
        try {
            store = KeyStore.getInstance("PKCS12", "SunJSSE");
            store.load(null, null);
            store.setKeyEntry("rcn-key", key, password.toCharArray(), certChain);
            ByteArrayOutputStream stream = new ByteArrayOutputStream();
            store.store(stream, password.toCharArray());

            return stream.toByteArray();
        } catch (Exception e) {
            throw new SecurityException(e);
        }
    }

    private Tuple<PrivateKey, Certificate[]> p12Read(String base64, String password) {
        KeyStore store = null;
        try {
            byte[] bytes = Base64.getDecoder().decode(base64.getBytes());
            store = KeyStore.getInstance("PKCS12", "SunJSSE");
            store.load(new ByteArrayInputStream(bytes), password.toCharArray());
            KeyStore.PrivateKeyEntry key = (KeyStore.PrivateKeyEntry) store.getEntry("rcn-key",
                    new KeyStore.PasswordProtection(password.toCharArray()));
            return Tuple.of(key.getPrivateKey(), key.getCertificateChain());
        } catch (Exception e) {
            throw new SecurityException(e);
        }
    }

    private String toPem(Object obj, Optional<String> pwd) {
        StringWriter writer = new StringWriter();
        PEMWriter pemWriter = new PEMWriter(writer);
        try {

            if (pwd.isPresent()) {
                pemWriter.writeObject(obj, "DES-EDE3-CBC", pwd.get().toCharArray(),
                        SecureRandom.getInstance("SHA1PRNG"));
            } else {
                pemWriter.writeObject(obj);
            }
            pemWriter.flush();
        } catch (Exception e) {
            throw new SecurityException(e);
        }

        return writer.getBuffer().toString();
    }

    public static String getThumbPrint(Certificate cert) {
        MessageDigest md = null;
        try {
            md = MessageDigest.getInstance("SHA-1");
            byte[] der = cert.getEncoded();
            md.update(der);
            byte[] digest = md.digest();
            return hexify(digest);
        } catch (Exception e) {
            throw new SecurityException(e);
        }
    }

    public static String hexify(byte bytes[]) {

        char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };

        StringBuffer buf = new StringBuffer(bytes.length * 2);

        for (int i = 0; i < bytes.length; ++i) {
            buf.append(hexDigits[(bytes[i] & 0xf0) >> 4]);
            buf.append(hexDigits[bytes[i] & 0x0f]);
        }

        return buf.toString();
    }

    public Tuple<KeyPair, X509Certificate> fromPem(String pem, String password) {
        try {

            KeyPair key = null;
            X509Certificate cert = null;
            PEMReader reader = new PEMReader(new StringReader(pem), () -> password.toCharArray(), "BC");
            Object o;
            while ((o = reader.readObject()) != null) {
                if (o instanceof X509Certificate)
                    cert = (X509Certificate) o;
                else if (o instanceof KeyPair)
                    key = (KeyPair) o;
            }
            reader.close();
            return Tuple.of(key, cert);
        } catch (IOException e) {
            throw new SecurityException(e);
        }

    }

    public byte[] toPkcs12(String cert, String password) {
        Tuple<KeyPair, X509Certificate> t = fromPem(cert, password);
        byte[] p12 = p12Write(t.getX().getPrivate(), new Certificate[] { t.getY() }, password);
        return p12;
    }

    public PublicKey fromPkcs10(String pkcs10) {
        PEMReader reader = new PEMReader(new StringReader(pkcs10), () -> new char[0], "BC");
        try {
            PKCS10CertificationRequest request = PKCS10CertificationRequest.class.cast(reader.readObject());
            return request.getPublicKey();
        } catch (Exception e) {
            throw new SecurityException(e);
        }
    }

    public Optional<String> issueCert(String licenseKey, String pkcs10, String displayName, String description,
            String regType) {
        Optional<LicenseKey> license = Optional.ofNullable(resourceRepository.findLicenseKey(licenseKey));
        return license.map(lk -> {
            Long rId = resourceRepository.createResourceFromLicense(lk.getId(), displayName, description, regType);
            resourceRepository.attachGroupsFromLk(lk.getId(), rId);
            String ca = new Scanner(getClass().getResourceAsStream("/cert/uni-subca1Sub.pem")).useDelimiter("\\Z")
                    .next();
            String cert = generateCert("cn=" + displayName + "." + rId, "", lk.getCertValidDays(), Optional.of(ca),
                    "", false, Optional.of(pkcs10));
            storeCert(rId, cert, "");

            historyRepository.histLicenseKey(lk.getUserId(), lk.getId(), rId, licenseKey, displayName,
                    lk.getCertValidDays(), lk.getName(), lk.getActivationsNumber());
            return cert;
        });
    }
}