org.tdmx.client.crypto.certificate.PKIXCertificate.java Source code

Java tutorial

Introduction

Here is the source code for org.tdmx.client.crypto.certificate.PKIXCertificate.java

Source

/*
 * TDMX - Trusted Domain Messaging eXchange
 * 
 * Enterprise B2B messaging between separate corporations via interoperable cloud service providers.
 * 
 * Copyright (C) 2014 Peter Klauser (http://tdmx.org)
 * 
 * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero 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 Affero General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU Affero General Public License along with this program. If not, see
 * http://www.gnu.org/licenses/.
 */
package org.tdmx.client.crypto.certificate;

import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;

import javax.security.auth.x500.X500Principal;

import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x500.style.IETFUtils;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralSubtree;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.asn1.x509.NameConstraints;
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
import org.tdmx.client.crypto.algorithm.DigestAlgorithm;
import org.tdmx.client.crypto.converters.ByteArray;
import org.tdmx.client.crypto.scheme.CryptoException;
import org.tdmx.core.system.lang.StringUtils;

public class PKIXCertificate {
    // -------------------------------------------------------------------------
    // PUBLIC CONSTANTS
    // -------------------------------------------------------------------------

    // -------------------------------------------------------------------------
    // PROTECTED AND PRIVATE VARIABLES AND CONSTANTS
    // -------------------------------------------------------------------------
    private X509Certificate certificate;
    private JcaX509CertificateHolder holder;

    /**
     * The SHA256 fingerprint
     */
    private String fingerprint;

    private static Map<String, String> oidMap = new HashMap<>();
    static {
        oidMap.put(BCStyle.E.getId(), "EMAIL");
        oidMap.put(BCStyle.TELEPHONE_NUMBER.getId(), "TEL");
    }

    // TODO public key - type + leyken ie RSA(2048bit) AsymmetricEncryptionAlgorithm
    // TODO subject key identifier
    // TODO issuer key identifier

    // TODO TDMX domain and zone apex MUST be uppercase! address / username is case sensitive but not DomainNames.

    // -------------------------------------------------------------------------
    // CONSTRUCTORS
    // -------------------------------------------------------------------------
    public PKIXCertificate(X509Certificate cert) throws CryptoCertificateException {
        try {
            certificate = cert;

            holder = new JcaX509CertificateHolder(certificate);
            {
                byte[] tbsCert = cert.getTBSCertificate();
                byte[] sha1 = DigestAlgorithm.SHA_256.kdf(tbsCert);
                fingerprint = ByteArray.asHex(sha1);
            }

        } catch (CryptoException e) {
            throw new CryptoCertificateException(CertificateResultCode.ERROR_EXCEPTION, e);
        } catch (CertificateEncodingException e) {
            throw new CryptoCertificateException(CertificateResultCode.ERROR_ENCODING, e);
        }
    }

    // -------------------------------------------------------------------------
    // PUBLIC METHODS
    // -------------------------------------------------------------------------

    @Override
    public String toString() {
        return certificate.toString();
    }

    /**
     * The public key is always the first in the chain.
     * 
     * @param chain
     * @return
     */
    public static PKIXCertificate getPublicKey(PKIXCertificate[] chain) {
        if (chain != null && chain.length > 0) {
            return chain[0];
        }
        return null;
    }

    public static PKIXCertificate getIssuerPublicKey(PKIXCertificate[] chain) {
        if (chain != null && chain.length > 1) {
            return chain[1];
        }
        return null;
    }

    public static PKIXCertificate getZoneRootPublicKey(PKIXCertificate[] chain) {
        if (chain != null && chain.length > 1) {
            if (chain.length > 2) {
                return chain[2];
            }
            return chain[1];
        }
        return null;
    }

    // -------------------------------------------------------------------------
    // PROTECTED METHODS
    // -------------------------------------------------------------------------

    // -------------------------------------------------------------------------
    // PRIVATE METHODS
    // -------------------------------------------------------------------------

    private String getFirstRDN(X500Name x500name, ASN1ObjectIdentifier attributeType) {
        if (x500name == null) {
            return null;
        }
        RDN[] rdns = x500name.getRDNs(attributeType);
        if (rdns != null && rdns.length > 0) {
            return IETFUtils.valueToString(rdns[0].getFirst().getValue());
        }
        return null;
    }

    private String getSecondLastRDN(X500Name x500name, ASN1ObjectIdentifier attributeType) {
        if (x500name == null) {
            return null;
        }
        RDN[] rdns = x500name.getRDNs(attributeType);
        if (rdns != null && rdns.length > 1) {
            return IETFUtils.valueToString(rdns[rdns.length - 2].getFirst().getValue());
        }
        return null;
    }

    private String getLastRDN(X500Name x500name, ASN1ObjectIdentifier attributeType) {
        if (x500name == null) {
            return null;
        }
        RDN[] rdns = x500name.getRDNs(attributeType);
        if (rdns != null && rdns.length > 0) {
            return IETFUtils.valueToString(rdns[rdns.length - 1].getFirst().getValue());
        }
        return null;
    }

    private X500Name getSubjectNameConstraint() {
        Extension e = holder.getExtension(Extension.nameConstraints);
        if (e != null && e.isCritical()) {
            NameConstraints nc = NameConstraints.getInstance(e.getParsedValue());
            GeneralSubtree[] permitted = nc.getPermittedSubtrees();
            if (permitted != null && permitted.length > 0) {
                GeneralName base = permitted[0].getBase();
                if (base != null) {
                    if (GeneralName.directoryName == base.getTagNo()) {
                        X500Name baseName = X500Name.getInstance(base.getName());
                        return baseName;
                    }
                }
            }
        }
        return null;
    }

    private KeyUsage getKeyUsage() {
        Extension e = holder.getExtension(Extension.keyUsage);
        if (e != null) {
            KeyUsage ku = KeyUsage.getInstance(e.getParsedValue());
            return ku;
        }
        return null;
    }

    public TdmxZoneInfo getTdmxZoneInfo() {
        Extension e = holder.getExtension(TdmxZoneInfo.tdmxZoneInfo);
        if (e != null) {
            TdmxZoneInfo ku = TdmxZoneInfo.getInstance(e.getParsedValue());
            return ku;
        }
        return null;
    }

    // -------------------------------------------------------------------------
    // PUBLIC ACCESSORS (GETTERS / SETTERS)
    // -------------------------------------------------------------------------

    public X509Certificate getCertificate() {
        return certificate;
    }

    /**
     * Get the SHA256 fingerprint of this certificate.
     * 
     * @return the SHA256 fingerprint of this certificate.
     */
    public String getFingerprint() {
        return fingerprint;
    }

    public X500Name getIssuerName() {
        return holder.getIssuer();
    }

    public String getIssuer() {
        if (certificate.getIssuerX500Principal() != null) {
            return certificate.getIssuerX500Principal().getName(X500Principal.RFC2253, oidMap);
        }
        return null;
    }

    public X500Name getSubjectName() {
        return holder.getSubject();
    }

    public String getSubject() {
        if (certificate.getSubjectX500Principal() != null) {
            return certificate.getSubjectX500Principal().getName(X500Principal.RFC2253, oidMap);
        }
        return null;
    }

    public String getCommonName() {
        return getFirstRDN(holder.getSubject(), BCStyle.CN);
    }

    public String getTelephoneNumber() {
        return getFirstRDN(holder.getSubject(), BCStyle.TELEPHONE_NUMBER);
    }

    public String getEmailAddress() {
        return getFirstRDN(holder.getSubject(), BCStyle.E);
    }

    public String getOrganization() {
        return getFirstRDN(holder.getSubject(), BCStyle.O);
    }

    public String getOrgUnit() {
        String ou = getFirstRDN(holder.getSubject(), BCStyle.OU);
        return !CredentialUtils.TDMX_DOMAIN_CA_OU.equals(ou) ? ou : null;
    }

    public String getLocation() {
        return getFirstRDN(holder.getSubject(), BCStyle.L);
    }

    public String getCountry() {
        return getFirstRDN(holder.getSubject(), BCStyle.C);
    }

    public boolean isCA() {
        Extension e = holder.getExtension(Extension.basicConstraints);
        if (e != null && e.isCritical()) {
            BasicConstraints bc = BasicConstraints.getInstance(e.getParsedValue());
            return bc.isCA();
        }
        return false;
    }

    public int getCAPathLengthConstraint() {
        Extension e = holder.getExtension(Extension.basicConstraints);
        if (e != null && e.isCritical()) {
            BasicConstraints bc = BasicConstraints.getInstance(e.getParsedValue());
            if (bc.getPathLenConstraint() != null) {
                return bc.getPathLenConstraint().intValue();
            }
        }
        return -1;
    }

    public boolean isTdmxZoneAdminCertificate() {
        // critical basicConstraints CA=true, max path length=1
        boolean caConstrained = isCA() && 1 == getCAPathLengthConstraint();
        if (!caConstrained) {
            return false;
        }

        // keyusage keyCertSign + digitalSignature
        KeyUsage ku = getKeyUsage();
        if (ku == null) {
            return false;
        }
        if (!ku.hasUsages(KeyUsage.keyCertSign | KeyUsage.digitalSignature)) {
            return false;
        }

        // is self signed, ie. subject == issuer
        String subjectName = getSubject();
        String issuerName = getIssuer();
        if (subjectName == null || issuerName == null || !subjectName.equals(issuerName)) {
            return false;
        }
        // TODO subjectKey == issuerKey identifiers

        TdmxZoneInfo zi = getTdmxZoneInfo();
        if (zi == null || !StringUtils.isLowerCase(zi.getZoneRoot())) {
            return false;
        }

        // critical nameConstraint where subject(-DN)==namecontraint subtree
        X500Name snc = getSubjectNameConstraint();
        if (snc != null) {
            if (getCountry() != null) {
                String c = getFirstRDN(snc, BCStyle.C);
                if (!getCountry().equals(c)) {
                    return false;
                }
            }
            if (getLocation() != null) {
                String l = getFirstRDN(snc, BCStyle.L);
                if (!getLocation().equals(l)) {
                    return false;
                }
            }
            if (getOrganization() != null) {
                String o = getFirstRDN(snc, BCStyle.O);
                if (!getOrganization().equals(o)) {
                    return false;
                }
            }
            if (getOrgUnit() != null) {
                String ou = getFirstRDN(snc, BCStyle.OU);
                if (!getOrgUnit().equals(ou)) {
                    return false;
                }
            }
            String tdmxOU = getLastRDN(snc, BCStyle.OU);
            if (!CredentialUtils.TDMX_DOMAIN_CA_OU.equals(tdmxOU)) {
                return false;
            }
            return true;
        }

        return false;
    }

    public boolean isTdmxDomainAdminCertificate() {
        // critical basicConstraints CA=true, max path length=1
        boolean caConstrained = isCA() && 0 == getCAPathLengthConstraint();
        if (!caConstrained) {
            return false;
        }

        // keyusage keyCertSign + digitalSignature
        KeyUsage ku = getKeyUsage();
        if (ku == null) {
            return false;
        }
        if (!ku.hasUsages(KeyUsage.keyCertSign | KeyUsage.digitalSignature)) {
            return false;
        }

        // domain cert is NOT self signed, ie. subject != issuer
        String subjectName = getSubject();
        String issuerName = getIssuer();
        if (subjectName == null || issuerName == null || subjectName.equals(issuerName)) {
            return false;
        }
        // TODO subjectKey identifiers present
        // TODO issuerKey identifiers present

        TdmxZoneInfo zi = getTdmxZoneInfo();
        if (zi == null || !StringUtils.isLowerCase(zi.getZoneRoot())) {
            return false;
        }

        if (getCommonName() == null || !StringUtils.isLowerCase(getCommonName())) {
            return false;
        }
        if (!getCommonName().equals(zi.getZoneRoot()) && !getCommonName().endsWith("." + zi.getZoneRoot())) {
            // domain is subdomain of zone root
            return false;
        }
        // critical nameConstraint where subject(-DN)==namecontraint subtree
        X500Name snc = getSubjectNameConstraint();
        if (snc != null) {
            if (getCountry() != null) {
                String c = getFirstRDN(snc, BCStyle.C);
                if (!getCountry().equals(c)) {
                    return false;
                }
            }
            if (getLocation() != null) {
                String l = getFirstRDN(snc, BCStyle.L);
                if (!getLocation().equals(l)) {
                    return false;
                }
            }
            if (getOrganization() != null) {
                String o = getFirstRDN(snc, BCStyle.O);
                if (!getOrganization().equals(o)) {
                    return false;
                }
            }
            if (getOrgUnit() != null) {
                String ou = getFirstRDN(snc, BCStyle.OU);
                if (!getOrgUnit().equals(ou)) {
                    return false;
                }
            }
            String tdmxOU = getSecondLastRDN(snc, BCStyle.OU);
            if (!CredentialUtils.TDMX_DOMAIN_CA_OU.equals(tdmxOU)) {
                return false;
            }

            String domainOU = getLastRDN(snc, BCStyle.OU);
            if (!getCommonName().equals(domainOU)) {
                return false;
            }

            return true;
        }

        return false;
    }

    public boolean isTdmxUserCertificate() {
        // critical basicConstraints CA=true, max path length=1
        if (isCA()) {
            return false;
        }

        // keyusage keyCertSign + digitalSignature
        KeyUsage ku = getKeyUsage();
        if (ku == null) {
            return false;
        }
        if (!ku.hasUsages(KeyUsage.keyEncipherment | KeyUsage.digitalSignature | KeyUsage.nonRepudiation)) {
            return false;
        }

        // domain cert is NOT self signed, ie. subject != issuer
        String subjectName = getSubject();
        String issuerName = getIssuer();
        if (subjectName == null || issuerName == null || subjectName.equals(issuerName)) {
            return false;
        }
        // TODO subjectKey identifiers present
        // TODO issuerKey identifiers present

        TdmxZoneInfo zi = getTdmxZoneInfo();
        if (zi == null || !StringUtils.isLowerCase(zi.getZoneRoot())) {
            // we must have the zone root normalized to uppercase.
            return false;
        }

        // Last OU is the domainName which must be uppercase too
        String domainName = getLastRDN(getSubjectName(), BCStyle.OU);
        if (domainName == null || !StringUtils.isLowerCase(domainName)) {
            return false;
        }
        if (!domainName.equals(zi.getZoneRoot()) && !domainName.endsWith("." + zi.getZoneRoot())) {
            // domain is subdomain of zone root
            return false;
        }

        return true;
    }

    public Calendar getNotBefore() {
        if (holder.getNotBefore() != null) {
            Calendar notBefore = Calendar.getInstance();
            notBefore.setTime(holder.getNotBefore());
            return notBefore;
        }
        return null;
    }

    public Calendar getNotAfter() {
        if (holder.getNotAfter() != null) {
            Calendar notAfter = Calendar.getInstance();
            notAfter.setTime(holder.getNotAfter());
            return notAfter;
        }
        return null;
    }

    public String getSignatureAlgorithm() {
        if (holder.getSignatureAlgorithm() != null) {
            return IETFUtils.valueToString(holder.getSignatureAlgorithm());
        }
        return null;
    }

    public String getSignature() {
        if (holder.getSignature() != null) {
            return ByteArray.asHex(holder.getSignature());
        }
        return null;
    }

    public String getInfo() {
        return certificate.toString();
    }

    /**
     * Check if the X509 ASN1 encoded byte representation of the other cert is the same as this.
     * 
     * @param other
     * @return
     */
    public boolean isIdentical(PKIXCertificate other) {
        return ByteArray.equals(getX509Encoded(), other.getX509Encoded());
    }

    /**
     * @return the X509 ASN1 encoded byte representation of this certificate
     */
    public byte[] getX509Encoded() {
        try {
            return certificate.getEncoded();
        } catch (CertificateEncodingException e) {
            return null;
        }
    }

}