org.kontalk.certgen.X509Bridge.java Source code

Java tutorial

Introduction

Here is the source code for org.kontalk.certgen.X509Bridge.java

Source

/*
 * Kontalk Android client
 * Copyright (C) 2014 Kontalk Devteam <devteam@kontalk.org>
    
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
    
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
    
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.kontalk.certgen;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.Iterator;

import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.misc.MiscObjectIdentifiers;
import org.bouncycastle.asn1.misc.NetscapeCertType;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.util.PrivateKeyFactory;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider;
import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.bc.BcContentSignerBuilder;
import org.bouncycastle.operator.bc.BcDSAContentSignerBuilder;
import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;

/**
 * Utility methods for bridging OpenPGP keys with X.509 certificates.<br>
 * Inspired by the Foaf server project.
 * @author Daniele Ricci
 * @see https://svn.java.net/svn/sommer~svn/trunk/misc/FoafServer/pgpx509/src/net/java/dev/sommer/foafserver/utils/PgpX509Bridge.java
 */
public class X509Bridge {

    private final static String DN_COMMON_PART_O = "OpenPGP to X.509 Bridge";

    private X509Bridge() {
    }

    public static X509Certificate createCertificate(byte[] publicKeyData, PGPSecretKey secretKey, String passphrase,
            String subjectAltName) throws PGPException, InvalidKeyException, IllegalStateException,
            NoSuchAlgorithmException, SignatureException, CertificateException, NoSuchProviderException,
            IOException, OperatorCreationException {

        KeyFingerPrintCalculator fpr = new BcKeyFingerprintCalculator();
        PGPPublicKeyRing pubRing = new PGPPublicKeyRing(publicKeyData, fpr);

        return createCertificate(pubRing, secretKey, passphrase, subjectAltName);

    }

    public static X509Certificate createCertificate(PGPPublicKeyRing publicKeyring, PGPSecretKey secretKey,
            String passphrase, String subjectAltName) throws PGPException, InvalidKeyException,
            IllegalStateException, NoSuchAlgorithmException, SignatureException, CertificateException,
            NoSuchProviderException, IOException, OperatorCreationException {

        // extract the private key
        PGPDigestCalculatorProvider sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build();
        PBESecretKeyDecryptor decryptor = new JcePBESecretKeyDecryptorBuilder(sha1Calc).setProvider(PGP.PROVIDER)
                .build(passphrase.toCharArray());

        PGPPrivateKey privateKey = secretKey.extractPrivateKey(decryptor);
        return createCertificate(publicKeyring, privateKey, subjectAltName);

    }

    public static X509Certificate createCertificate(byte[] privateKeyData, byte[] publicKeyData, String passphrase,
            String subjectAltName)
            throws PGPException, IOException, InvalidKeyException, IllegalStateException, NoSuchAlgorithmException,
            SignatureException, CertificateException, NoSuchProviderException, OperatorCreationException {

        KeyFingerPrintCalculator fpr = new BcKeyFingerprintCalculator();
        PGPSecretKeyRing secRing = new PGPSecretKeyRing(privateKeyData, fpr);
        PGPPublicKeyRing pubRing = new PGPPublicKeyRing(publicKeyData, fpr);

        PGPDigestCalculatorProvider sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build();
        PBESecretKeyDecryptor decryptor = new JcePBESecretKeyDecryptorBuilder(sha1Calc).setProvider(PGP.PROVIDER)
                .build(passphrase.toCharArray());

        // secret key
        PGPSecretKey secKey = secRing.getSecretKey();

        return createCertificate(pubRing, secKey.extractPrivateKey(decryptor), subjectAltName);
    }

    public static X509Certificate createCertificate(byte[] publicKeyData, PGPPrivateKey privateKey,
            String subjectAltName)
            throws InvalidKeyException, IllegalStateException, NoSuchAlgorithmException, SignatureException,
            CertificateException, NoSuchProviderException, PGPException, IOException, OperatorCreationException {

        KeyFingerPrintCalculator fpr = new BcKeyFingerprintCalculator();
        PGPPublicKeyRing pubRing = new PGPPublicKeyRing(publicKeyData, fpr);

        return createCertificate(pubRing, privateKey, subjectAltName);
    }

    public static X509Certificate createCertificate(PGPPublicKeyRing publicKeyRing, PGPPrivateKey privateKey,
            String subjectAltName)
            throws InvalidKeyException, IllegalStateException, NoSuchAlgorithmException, SignatureException,
            CertificateException, NoSuchProviderException, PGPException, IOException, OperatorCreationException {

        X500NameBuilder x500NameBuilder = new X500NameBuilder();

        /*
         * The X.509 Name to be the subject DN is prepared.
         * The CN is extracted from the Secret Key user ID.
         */

        x500NameBuilder.addRDN(BCStyle.O, DN_COMMON_PART_O);

        PGPPublicKey publicKey = publicKeyRing.getPublicKey();

        for (@SuppressWarnings("unchecked")
        Iterator<Object> it = publicKey.getUserIDs(); it.hasNext();) {
            Object attrib = it.next();
            x500NameBuilder.addRDN(BCStyle.CN, attrib.toString());
        }

        X500Name x509name = x500NameBuilder.build();

        /*
         * To check the signature from the certificate on the recipient side,
         * the creation time needs to be embedded in the certificate.
         * It seems natural to make this creation time be the "not-before"
         * date of the X.509 certificate.
         * Unlimited PGP keys have a validity of 0 second. In this case,
         * the "not-after" date will be the same as the not-before date.
         * This is something that needs to be checked by the service
         * receiving this certificate.
         */
        Date creationTime = publicKey.getCreationTime();
        Date validTo = null;
        if (publicKey.getValidSeconds() > 0)
            validTo = new Date(creationTime.getTime() + 1000L * publicKey.getValidSeconds());

        return createCertificate(PGP.convertPublicKey(publicKey), PGP.convertPrivateKey(privateKey), x509name,
                creationTime, validTo, subjectAltName, publicKeyRing.getEncoded());
    }

    /**
     * Creates a self-signed certificate from a public and private key. The
     * (critical) key-usage extension is set up with: digital signature,
     * non-repudiation, key-encipherment, key-agreement and certificate-signing.
     * The (non-critical) Netscape extension is set up with: SSL client and
     * S/MIME. A URI subjectAltName may also be set up.
     *
     * @param pubKey
     *            public key
     * @param privKey
     *            private key
     * @param subject
     *            subject (and issuer) DN for this certificate, RFC 2253 format
     *            preferred.
     * @param startDate
     *            date from which the certificate will be valid
     *            (defaults to current date and time if null)
     * @param endDate
     *            date until which the certificate will be valid
     *            (defaults to start date and time if null)
     * @param subjectAltName
     *            URI to be placed in subjectAltName
     * @return self-signed certificate
     */
    private static X509Certificate createCertificate(PublicKey pubKey, PrivateKey privKey, X500Name subject,
            Date startDate, Date endDate, String subjectAltName, byte[] publicKeyData)
            throws InvalidKeyException, IllegalStateException, NoSuchAlgorithmException, SignatureException,
            CertificateException, NoSuchProviderException, IOException, OperatorCreationException {

        /*
         * Sets the signature algorithm.
         */
        BcContentSignerBuilder signerBuilder;
        String pubKeyAlgorithm = pubKey.getAlgorithm();
        if (pubKeyAlgorithm.equals("DSA")) {
            AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1WithDSA");
            AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
            signerBuilder = new BcDSAContentSignerBuilder(sigAlgId, digAlgId);
        } else if (pubKeyAlgorithm.equals("RSA")) {
            AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder()
                    .find("SHA1WithRSAEncryption");
            AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
            signerBuilder = new BcRSAContentSignerBuilder(sigAlgId, digAlgId);
        }
        /*
        else if (pubKeyAlgorithm.equals("ECDSA")) {
        // TODO is this even legal?
        certGenerator.setSignatureAlgorithm("SHA1WithECDSA");
        }
        */
        else {
            throw new RuntimeException("Algorithm not recognised: " + pubKeyAlgorithm);
        }

        AsymmetricKeyParameter keyp = PrivateKeyFactory.createKey(privKey.getEncoded());
        ContentSigner signer = signerBuilder.build(keyp);

        /*
         * Sets up the validity dates.
         */
        if (startDate == null) {
            startDate = new Date(System.currentTimeMillis());
        }
        if (endDate == null) {
            endDate = startDate;
        }

        X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(
                /*
                 * Sets up the subject distinguished name.
                 * Since it's a self-signed certificate, issuer and subject are the
                 * same.
                 */
                subject,
                /*
                 * The serial-number of this certificate is 1. It makes sense
                 * because it's self-signed.
                 */
                BigInteger.ONE, startDate, endDate, subject,
                /*
                 * Sets the public-key to embed in this certificate.
                 */
                SubjectPublicKeyInfo.getInstance(new ASN1InputStream(pubKey.getEncoded()).readObject()));

        /*
         * Adds the Basic Constraint (CA: true) extension.
         */
        certBuilder.addExtension(Extension.basicConstraints, true, new BasicConstraints(true));

        /*
         * Adds the Key Usage extension.
         */
        certBuilder.addExtension(Extension.keyUsage, true,
                new KeyUsage(KeyUsage.digitalSignature | KeyUsage.nonRepudiation | KeyUsage.keyEncipherment
                        | KeyUsage.keyAgreement | KeyUsage.keyCertSign));

        /*
         * Adds the Netscape certificate type extension.
         */
        certBuilder.addExtension(MiscObjectIdentifiers.netscapeCertType, false,
                new NetscapeCertType(NetscapeCertType.sslClient | NetscapeCertType.smime));

        JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();

        /*
         * Adds the subject key identifier extension.
         */
        SubjectKeyIdentifier subjectKeyIdentifier = extUtils.createSubjectKeyIdentifier(pubKey);
        certBuilder.addExtension(Extension.subjectKeyIdentifier, false, subjectKeyIdentifier);

        /*
         * Adds the authority key identifier extension.
         */
        AuthorityKeyIdentifier authorityKeyIdentifier = extUtils.createAuthorityKeyIdentifier(pubKey);
        certBuilder.addExtension(Extension.authorityKeyIdentifier, false, authorityKeyIdentifier);

        /*
         * Adds the subject alternative-name extension.
         */
        if (subjectAltName != null) {
            GeneralNames subjectAltNames = new GeneralNames(new GeneralName(GeneralName.otherName, subjectAltName));
            certBuilder.addExtension(Extension.subjectAlternativeName, false, subjectAltNames);
        }

        /*
         * Adds the PGP public key block extension.
         */
        SubjectPGPPublicKeyInfo publicKeyExtension = new SubjectPGPPublicKeyInfo(publicKeyData);
        certBuilder.addExtension(SubjectPGPPublicKeyInfo.OID, false, publicKeyExtension);

        /*
         * Creates and sign this certificate with the private key
         * corresponding to the public key of the certificate
         * (hence the name "self-signed certificate").
         */
        X509CertificateHolder holder = certBuilder.build(signer);

        /*
         * Checks that this certificate has indeed been correctly signed.
         */
        X509Certificate cert = new JcaX509CertificateConverter().getCertificate(holder);
        cert.verify(pubKey);

        return cert;
    }

    public static X509Certificate load(byte[] certData) throws CertificateException, NoSuchProviderException {
        return load(new ByteArrayInputStream(certData));
    }

    public static X509Certificate load(InputStream certData) throws CertificateException, NoSuchProviderException {

        CertificateFactory certFactory = CertificateFactory.getInstance("X.509", PGP.PROVIDER);
        return (X509Certificate) certFactory.generateCertificate(certData);
    }

    public static KeyStore exportCertificate(X509Certificate certificate, PrivateKey privateKey)
            throws KeyStoreException, NoSuchProviderException, NoSuchAlgorithmException, CertificateException,
            IOException {

        KeyStore store = KeyStore.getInstance("PKCS12", PGP.PROVIDER);

        store.load(null, null);

        store.setKeyEntry("Kontalk Personal Key", privateKey, null, new Certificate[] { certificate });

        return store;
    }

}