All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.digidoc4j.ddoc.factory.BouncyCastleNotaryFactory Maven / Gradle / Ivy

Go to download

DDoc4J is Java Library for validating DDOC documents. It's not recommended to use it directly but rather through DigiDoc4J's API.

The newest version!
package org.digidoc4j.ddoc.factory;

import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ocsp.BasicOCSPResponse;
import org.bouncycastle.asn1.ocsp.ResponderID;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.ocsp.BasicOCSPResp;
import org.bouncycastle.cert.ocsp.CertificateID;
import org.bouncycastle.cert.ocsp.OCSPResp;
import org.bouncycastle.cert.ocsp.RevokedStatus;
import org.bouncycastle.cert.ocsp.SingleResp;
import org.bouncycastle.cert.ocsp.UnknownStatus;
import org.bouncycastle.operator.ContentVerifier;
import org.bouncycastle.operator.ContentVerifierProvider;
import org.bouncycastle.operator.DigestCalculatorProvider;
import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.digidoc4j.ddoc.Base64Util;
import org.digidoc4j.ddoc.CertID;
import org.digidoc4j.ddoc.CertValue;
import org.digidoc4j.ddoc.DigiDocException;
import org.digidoc4j.ddoc.Notary;
import org.digidoc4j.ddoc.Signature;
import org.digidoc4j.ddoc.SignedDoc;
import org.digidoc4j.ddoc.utils.BouncyCastleNotaryUtil;
import org.digidoc4j.ddoc.utils.ConfigManager;
import org.digidoc4j.ddoc.utils.ConvertUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.FileInputStream;
import java.io.OutputStream;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Provider;
import java.security.Security;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.List;

public class BouncyCastleNotaryFactory implements NotaryFactory
{
    private static final Logger m_logger = LoggerFactory.getLogger(BouncyCastleNotaryFactory.class);

    /**
     * Returns the OCSP responders certificate
     * @param responderCN responder-id's CN
     * @param specificCertNr specific cert number that we search.
     * If this parameter is null then the newest cert is seleced (if many exist)
     * @returns OCSP responders certificate
     */
    public X509Certificate[] getNotaryCerts(String responderCN, String specificCertNr)
    {
        try {
            TrustServiceFactory tslFac = ConfigManager.instance().getTslFactory();
            return tslFac.findOcspsByCNAndNr(responderCN, true, specificCertNr);
        } catch(Exception ex) {
            m_logger.error("Error searching responder cert for: " + responderCN + " - " + ex);
        }
        return null;
    }

    public boolean isSignatureValid(BasicOCSPResp resp, ContentVerifierProvider verifierProvider)
            throws Exception
    {
        try
        {

            ContentVerifier verifier = verifierProvider.get(resp.getSignatureAlgorithmID());
            OutputStream vOut = verifier.getOutputStream();
            vOut.write(resp.getTBSResponseData());
            vOut.close();
            ASN1Primitive obj = ASN1Primitive.fromByteArray(resp.getEncoded());
            BasicOCSPResponse bresp = BasicOCSPResponse.getInstance(obj);
            boolean bOk = verifier.verify(bresp.getSignature().getBytes());
            if(m_logger.isDebugEnabled())
                m_logger.debug("Verify ocsp sig: " + ConvertUtils.bin2hex(bresp.getSignature().getBytes()) + " RC: " + bOk);
            return bOk;
        }
        catch (Exception ex)
        {
            m_logger.error("ocsp exception: " + ex);
            m_logger.error("Trace; " + ConvertUtils.getTrace(ex));
            throw ex;
        }
    }

    /**
     * Verifies that the OCSP response is about our signers
     * cert and the response status is successfull
     * @param sig Signature object
     * @param basResp OCSP Basic response
     * @throws DigiDocException if the response is not successfull
     */
    private void checkCertStatus(Signature sig, BasicOCSPResp basResp)
            throws DigiDocException
    {
        checkCertStatus(sig.getKeyInfo().getSignersCertificate(), basResp, null);
    }


    /**
     * Verifies that the OCSP response is about our signers
     * cert and the response status is successfull
     * @param sig Signature object
     * @param basResp OCSP Basic response
     * @throws DigiDocException if the response is not successfull
     */
    private void checkCertStatus(X509Certificate cert, BasicOCSPResp basResp, X509Certificate caCert)
            throws DigiDocException
    {
        try {
            if(m_logger.isDebugEnabled())
                m_logger.debug("Checking response status, CERT: " + ((cert != null) ? cert.getSubjectDN().getName() : "NULL") +
                        " SEARCH: " + ((cert != null) ? SignedDoc.getCommonName(ConvertUtils.convX509Name(cert.getIssuerX500Principal())) : "NULL"));
            if(cert == null)
                throw new DigiDocException(DigiDocException.ERR_CERT_UNKNOWN,
                        "No certificate to check! Error reading certificate from file?", null);
            // check the response on our cert
            TrustServiceFactory tslFac = ConfigManager.instance().getTslFactory();
            if(caCert == null)
                caCert = tslFac.findCaForCert(cert, true, null);
            if(m_logger.isDebugEnabled()) {
                m_logger.debug("CA cert: " + ((caCert != null) ? caCert.getSubjectDN().getName() : "NULL"));
                m_logger.debug("RESP: " + basResp);
                m_logger.debug("CERT: " + cert.getSubjectDN().getName() +
                        " ISSUER: " + ConvertUtils.convX509Name(cert.getIssuerX500Principal()) +
                        " nr: " + ((caCert != null) ? ConvertUtils.bin2hex(caCert.getSerialNumber().toByteArray()) : "NULL"));
            }
            if(caCert == null)
                throw new DigiDocException(DigiDocException.ERR_CERT_UNKNOWN, "Unknown CA cert: " + cert.getIssuerDN().getName(), null);
            SingleResp[] sresp = basResp.getResponses();
            CertificateID rc = creatCertReq(cert, caCert);
            if(m_logger.isDebugEnabled())
                m_logger.debug("Search alg: " + rc.getHashAlgOID() + " cert ser: " + cert.getSerialNumber().toString() +
                        " serial: " + rc.getSerialNumber() + " issuer: " + Base64Util.encode(rc.getIssuerKeyHash()) +
                        " subject: " + Base64Util.encode(rc.getIssuerNameHash()));
            boolean ok = false;
            for(int i=0;i < sresp.length;i++) {
                CertificateID id = sresp[i].getCertID();
                if(id != null) {
                    if(m_logger.isDebugEnabled())
                        m_logger.debug("Got alg: " + id.getHashAlgOID() +
                                " serial: " + id.getSerialNumber() +
                                " issuer: " + Base64Util.encode(id.getIssuerKeyHash()) +
                                " subject: " + Base64Util.encode(id.getIssuerNameHash()));
                    if(rc.getHashAlgOID().equals(id.getHashAlgOID()) &&
                            rc.getSerialNumber().equals(id.getSerialNumber()) &&
                            SignedDoc.compareDigests(rc.getIssuerKeyHash(), id.getIssuerKeyHash()) &&
                            SignedDoc.compareDigests(rc.getIssuerNameHash(), id.getIssuerNameHash())) {
                        if(m_logger.isDebugEnabled())
                            m_logger.debug("Found it!");
                        ok = true;
                        Object status = sresp[i].getCertStatus();
                        if(status != null) {
                            if(m_logger.isDebugEnabled())
                                m_logger.debug("CertStatus: " + status.getClass().getName());
                            if(status instanceof RevokedStatus) {
                                m_logger.error("Certificate has been revoked!");
                                throw new DigiDocException(DigiDocException.ERR_CERT_REVOKED,
                                        "Certificate has been revoked!", null);
                            }
                            if(status instanceof UnknownStatus) {
                                m_logger.error("Certificate status is unknown!");
                                throw new DigiDocException(DigiDocException.ERR_CERT_UNKNOWN,
                                        "Certificate status is unknown!", null);
                            }

                        }
                        break;
                    }
                }
            }

            if(!ok) {
                if(m_logger.isDebugEnabled())
                    m_logger.debug("Error checkCertStatus - not found ");
                throw new DigiDocException(DigiDocException.ERR_OCSP_RESP_STATUS,
                        "Bad OCSP response status!", null);
            }
        } catch(DigiDocException ex) {
            throw ex;
        } catch(Exception ex) {
            m_logger.error("Error checkCertStatus: " + ex);
            ex.printStackTrace();
            throw new DigiDocException(DigiDocException.ERR_OCSP_RESP_STATUS,
                    "Error checking OCSP response status!", null);
        }
    }

    /**
     * Check the response and parse it's data
     * Used by UnsignedProperties.verify()
     * @param not initial Notary object that contains only the
     * raw bytes of an OCSP response
     * @returns Notary object with data parsed from OCSP response
     */
    public Notary parseAndVerifyResponse(Signature sig, Notary not)
            throws DigiDocException
    {
        try {;
            OCSPResp  resp = new OCSPResp(not.getOcspResponseData());
            // now read the info from the response
            BasicOCSPResp basResp = (BasicOCSPResp)resp.getResponseObject();
            // verify the response
            X509Certificate[] lNotCerts = null;
            try {
                String respondIDstr = responderIDtoString(basResp);

                if(m_logger.isDebugEnabled()) {
                    m_logger.debug("SIG: " + ((sig == null) ? "NULL" : sig.getId()));
                    m_logger.debug("UP: " + ((sig.getUnsignedProperties() == null) ? "NULL" : "OK: " + sig.getUnsignedProperties().getNotary().getId()));
                    m_logger.debug("RESP-CERT: " + ((sig.getUnsignedProperties().
                            getRespondersCertificate() == null) ? "NULL" : "OK"));
                    m_logger.debug("RESP-ID: " + respondIDstr);
                    CertID cid = sig.getCertID(CertID.CERTID_TYPE_RESPONDER);
                    if(cid != null)
                        m_logger.debug("CID: " + cid.getType() + " id: " + cid.getId() +
                                ", " + cid.getSerial() + " issuer: " + cid.getIssuer());
                    m_logger.debug("RESP: " + Base64Util.encode(resp.getEncoded()));
                }
                if(lNotCerts == null && sig != null) {
                    String respSrch = respondIDstr;
                    if((respSrch.indexOf("CN") != -1))
                        respSrch = ConvertUtils.getCommonName(respondIDstr);
                    if(respSrch.startsWith("byKey: "))
                        respSrch = respSrch.substring("byKey: ".length());
                    int n1 = respSrch.indexOf(',');
                    if(n1 > 0)
                        respSrch = respSrch.substring(0, n1);
                    if(m_logger.isDebugEnabled())
                        m_logger.debug("Search not cert by: " + respSrch);
                    // TODO: get multiple certs
                    lNotCerts = getNotaryCerts(respSrch, null /*ddocRespCertNr*/);
                }
                if(lNotCerts == null || lNotCerts.length == 0)
                    throw new DigiDocException(DigiDocException.ERR_OCSP_RECPONDER_NOT_TRUSTED,
                            "No certificate for responder: \'" + respondIDstr + "\' found in local certificate store!", null);
                boolean bOk = false;
                for(int j = 0; (lNotCerts != null) && (j < lNotCerts.length) && !bOk; j++) {
                    X509Certificate cert = lNotCerts[j];
                    if(m_logger.isDebugEnabled())
                        m_logger.debug("Verify using responders cert: " +
                                ((cert != null) ? ConvertUtils.getCommonName(cert.getSubjectDN().getName()) + " nr: " + cert.getSerialNumber().toString() : "NULL"));
                    if(cert != null) {
                        X509CertificateHolder ch = new X509CertificateHolder(cert.getEncoded());
                        bOk = isSignatureValid(basResp, new JcaContentVerifierProviderBuilder().setProvider("BC").build(ch));
                    } else bOk = false;
                    if(m_logger.isDebugEnabled())
                        m_logger.debug("OCSP resp: " + ((basResp != null) ? responderIDtoString(basResp) : "NULL") +
                                " verify using: " + ((cert != null) ? ConvertUtils.getCommonName(cert.getSubjectDN().getName()) : "NULL") +
                                " verify: " + bOk);
                }
                if(bOk) {
                    CertValue cvOcsp = sig.getCertValueOfType(CertValue.CERTVAL_TYPE_RESPONDER);
                    if(cvOcsp != null) {
                        X509Certificate rCert = cvOcsp.getCert();
                        if(rCert != null) {
                            X509CertificateHolder ch = new X509CertificateHolder(rCert.getEncoded());
                            bOk = isSignatureValid(basResp, new JcaContentVerifierProviderBuilder().setProvider("BC").build(ch));
                            if(m_logger.isDebugEnabled())
                                m_logger.debug("OCSP resp: " + ((basResp != null) ? responderIDtoString(basResp) : "NULL") +
                                        " verify using cert in xml: " + ConvertUtils.getCommonName(rCert.getSubjectDN().getName()) +
                                        " verify: " + bOk);
                        }
                    }
                }
                if(!bOk)
                    throw new DigiDocException(DigiDocException.ERR_OCSP_VERIFY, "OCSP verification error!", null);
            } catch (Exception ex) {
                m_logger.error("Signature verification error: " + ex);
                ex.printStackTrace();
                DigiDocException.handleException(ex, DigiDocException.ERR_OCSP_VERIFY);
            }
            try{
                String ocspResponderCommonName = ConvertUtils.getCommonName(responderIDtoString(basResp));
                List allowedOcspProviders = ConfigManager.instance().getAllowedOcspProviders();
                if(!allowedOcspProviders.contains(ocspResponderCommonName)) {
                    throw new DigiDocException(DigiDocException.ERR_OCSP_RESPONDER_TM, "OCSP Responder does not meet TM requirements", null);
                }

            }catch (Exception ex) {
                DigiDocException.handleException(ex, DigiDocException.ERR_OCSP_RESPONDER_TM);
            }

            // done't care about SingleResponses because we have
            // only one response and the whole response was successfull
            // but we should verify that the nonce hasn't changed
            // calculate the nonce
            if(m_logger.isDebugEnabled())
                m_logger.debug("Verif sig: " + sig.getId() + " format: " + sig.getSignedDoc().getFormat() + " nonce policy: " + sig.hasBdoc2NoncePolicy());
            boolean ok = true;
            if(BouncyCastleNotaryUtil.isApplicableFormatForOcspNonce(sig.getSignedDoc())) {
                byte[] nonce1 = SignedDoc.digestOfType(sig.getSignatureValue().getValue(), SignedDoc.SHA1_DIGEST_TYPE);
                byte[] nonce2 = BouncyCastleNotaryUtil.getNonce(basResp, sig.getSignedDoc());
                if(nonce1 == null || nonce2 == null || nonce1.length != nonce2.length)
                    ok = false;
                for(int i = 0; (nonce1 != null) && (nonce2 != null) && (i < nonce1.length) && (i < nonce2.length); i++)
                    if(nonce1[i] != nonce2[i])
                        ok = false;
                // TODO: investigate further
                if(!ok && sig.getSignedDoc() != null) {
                    if(m_logger.isDebugEnabled()) {
                        m_logger.debug("SigVal\n---\n" + Base64Util.encode(sig.getSignatureValue().getValue()) +
                                "\n---\nOCSP\n---\n" + Base64Util.encode(not.getOcspResponseData()) + "\n---\n");
                        m_logger.debug("DDOC ver: " + sig.getSignedDoc().getVersion() +
                                " SIG: " + sig.getId() + " NOT: " + not.getId() +
                                " Real nonce: " + ((nonce2 != null) ? Base64Util.encode(nonce2, 0) : "NULL") + " noncelen: " + ((nonce2 != null) ? nonce2.length : 0)
                                + " SigVal hash: " + ((nonce1 != null) ? Base64Util.encode(nonce1, 0) : "NULL")
                                + " SigVal hash hex: " + ((nonce1 != null) ? ConvertUtils.bin2hex(nonce1) : "NULL")
                                + " svlen: " + ((nonce1 != null) ? nonce1.length : 0));
                    }
                    throw new DigiDocException(DigiDocException.ERR_OCSP_NONCE,
                            "OCSP response's nonce doesn't match the requests nonce!", null);
                }
            }
            if(m_logger.isDebugEnabled())
                m_logger.debug("Verify not: " + not.getId());
            checkCertStatus(sig, basResp);
            not.setProducedAt(basResp.getProducedAt());
            not.setResponderId(responderIDtoString(basResp));
        } catch(DigiDocException ex) {
            throw ex;
        } catch(Exception ex) {
            DigiDocException.handleException(ex, DigiDocException.ERR_OCSP_PARSE);
        }
        return not;
    }


    /**
     * Get String represetation of ResponderID
     * @param basResp
     * @return stringified responder ID
     */
    static String responderIDtoString(BasicOCSPResp basResp) {
        if(basResp != null) {
            ResponderID respid = basResp.getResponderId().toASN1Primitive();
            if(respid.getKeyHash() != null) {
                return "byKey: " + SignedDoc.bin2hex(respid.getKeyHash());
            } else {
                return "byName: " + respid.getName().toString();
            }
        }
        else
            return null;
    }

    /**
     * Method for creating CertificateID for OCSP request
     * @param signersCert
     * @param caCert
     * @return
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws CertificateEncodingException
     */
    private CertificateID creatCertReq(X509Certificate signersCert, X509Certificate caCert)
            throws Exception
    {
        DigestCalculatorProvider dcp = new JcaDigestCalculatorProviderBuilder().setProvider("BC").build();
        X509CertificateHolder caCertHolder = new X509CertificateHolder(caCert.getEncoded());
        return new CertificateID(dcp.get(CertificateID.HASH_SHA1), caCertHolder, signersCert.getSerialNumber());
    }

    /**
     * initializes the implementation class
     */
    public void init()
            throws DigiDocException
    {
        FileInputStream fi = null;
        try {
            String proxyHost = ConfigManager.instance().
                    getProperty("DIGIDOC_PROXY_HOST");
            String proxyPort = ConfigManager.instance().
                    getProperty("DIGIDOC_PROXY_PORT");
            if(proxyHost != null && proxyPort != null) {
                System.setProperty("http.proxyHost", proxyHost);
                System.setProperty("http.proxyPort", proxyPort);
            }
            // only need this if we must sign the requests
            Provider prv = (Provider)Class.forName(ConfigManager.
                    instance().getProperty("DIGIDOC_SECURITY_PROVIDER")).newInstance();
            Security.addProvider(prv);
        } catch(Exception ex) {
            DigiDocException.handleException(ex, DigiDocException.ERR_NOT_FAC_INIT);
        } finally {
            if(fi != null) {
                try {
                    fi.close();
                } catch(Exception ex2) {
                    m_logger.error("Error closing input stream: " + ex2);
                }
            }
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy