Java tutorial
/* * BouncyCastleNotaryFactory.java * PROJECT: JDigiDoc * DESCRIPTION: Digi Doc functions for creating * handling OCSP requests and responses. * AUTHOR: Sander Aiaots <saiaots@itcollege.ee> * Adopted from Sanders version and converted to * latest level of service by Veiko Sinivee *================================================== * Copyright (C) AS Sertifitseerimiskeskus * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * This library 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 * Lesser General Public License for more details. * GNU Lesser General Public Licence is available at * http://www.gnu.org/copyleft/lesser.html *================================================== */ package ee.sk.digidoc.factory; import ee.sk.digidoc.DigiDocException; import ee.sk.digidoc.SignedDoc; //import ee.sk.digidoc.CertID; import ee.sk.utils.ConfigManager; import ee.sk.digidoc.Base64Util; import ee.sk.digidoc.Notary; import ee.sk.digidoc.Signature; //import ee.sk.digidoc.SignatureValue; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.security.PrivateKey; //import java.security.PublicKey; import java.security.Provider; import java.security.Security; import java.security.KeyStore; import java.math.BigInteger; import java.util.Date; import java.util.Enumeration; import java.util.Hashtable; //import java.text.SimpleDateFormat; import java.net.*; import java.io.*; import org.bouncycastle.asn1.ASN1OctetString; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.BERConstructedOctetString; import org.bouncycastle.asn1.DERInteger; import org.bouncycastle.asn1.DERObjectIdentifier; import org.bouncycastle.asn1.DERTaggedObject; import org.bouncycastle.asn1.ocsp.CertID; import org.bouncycastle.asn1.ocsp.ResponderID; import org.bouncycastle.asn1.x509.AlgorithmIdentifier; import org.bouncycastle.asn1.x509.GeneralName; import org.bouncycastle.asn1.x509.X509Extension; import org.bouncycastle.asn1.x509.X509Extensions; import org.bouncycastle.asn1.x509.X509Name; import org.bouncycastle.jce.PrincipalUtil; import org.bouncycastle.jce.X509Principal; import org.bouncycastle.ocsp.BasicOCSPResp; import org.bouncycastle.ocsp.CertificateID; import org.bouncycastle.ocsp.OCSPReq; import org.bouncycastle.ocsp.OCSPReqGenerator; import org.bouncycastle.ocsp.OCSPResp; import org.bouncycastle.ocsp.OCSPRespStatus; import org.bouncycastle.ocsp.SingleResp; //import org.bouncycastle.asn1.ocsp.CertStatus; import org.bouncycastle.ocsp.RevokedStatus; import org.bouncycastle.ocsp.UnknownStatus; import org.bouncycastle.asn1.DEROctetString; // Logging classes import org.apache.log4j.Logger; /** * Implements the NotaryFactory by using * BouncyCastle JCE toolkit * @author Sander Saiaots, Veiko Sinivee * @version 1.0 */ public class BouncyCastleNotaryFactory implements NotaryFactory { /** NONCE extendion oid */ public static final String nonceOid = "1.3.6.1.5.5.7.48.1.2"; /** cert used to sign to all OCSP requests */ private X509Certificate m_signCert; /** key used to sign all OCSP requests */ private PrivateKey m_signKey; private boolean m_bSignRequests; private Logger m_logger = null; private Hashtable m_ocspCerts; private Hashtable m_ocspCACerts; /** Creates new BouncyCastleNotaryFactory */ public BouncyCastleNotaryFactory() { m_signCert = null; m_signKey = null; m_ocspCerts = new Hashtable(); m_ocspCACerts = new Hashtable(); m_bSignRequests = false; m_logger = Logger.getLogger(BouncyCastleNotaryFactory.class); } /** * Returns the n-th OCSP responders certificate if there are many * certificates registered for this responder. * @param responderCN responder-id's CN * @param idx certificate index starting with 0 * @returns OCSP responders certificate or null if not found */ public X509Certificate findNotaryCertByIndex(String responderCN, int idx) { X509Certificate cert = null; if (m_logger.isInfoEnabled()) m_logger.info("Find responder for: " + responderCN + " index: " + idx); String certKey = null; if (idx == 0) certKey = responderCN; else certKey = responderCN + "-" + idx; if (m_logger.isInfoEnabled()) m_logger.info("Searching responder: " + certKey); cert = (X509Certificate) m_ocspCerts.get(certKey); if (m_logger.isInfoEnabled() && cert != null && certKey != null) m_logger.info("Selecting cert " + cert.getSerialNumber().toString() + " key: " + certKey + " valid until: " + cert.getNotAfter().toString()); return cert; } /** * 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 getNotaryCert(String responderCN, String specificCertNr) { X509Certificate cert1 = null, cert2 = null; Date d1 = null; String key = null; if (m_logger.isInfoEnabled()) m_logger.info("Find responder for: " + responderCN + " cert: " + ((specificCertNr != null) ? specificCertNr : "NEWEST")); int i = 0; do { cert2 = null; String certKey = null; if (i == 0) certKey = responderCN; else certKey = responderCN + "-" + i; if (m_logger.isInfoEnabled()) m_logger.info("Searching responder: " + certKey); cert2 = (X509Certificate) m_ocspCerts.get(certKey); if (cert2 != null) { if (specificCertNr != null) { // specific cert String certNr = cert2.getSerialNumber().toString(); if (certNr.equals(specificCertNr)) { if (m_logger.isInfoEnabled()) m_logger.info("Found specific responder: " + specificCertNr); return cert2; } } else { // just the freshest Date d2 = cert2.getNotAfter(); if (cert1 == null || d1 == null || d1.before(d2)) { d1 = d2; key = certKey; cert1 = cert2; if (m_logger.isDebugEnabled()) m_logger.debug("Newer cert valid until: " + d2); } } } else { if (m_logger.isDebugEnabled()) m_logger.debug("Responder: " + certKey + " not found! "); } i++; } while (cert2 != null || i < 2); if (m_logger.isInfoEnabled() && cert1 != null && key != null) m_logger.info("Selecting cert " + cert1.getSerialNumber().toString() + " key: " + key + " valid until: " + cert1.getNotAfter().toString()); return cert1; } // VS: 02.01.2009 - fix finding ocsp responders cert /** * Finds notary cert by certificates public key hash * @param certHash cert hast to search for * @return certificate if fond or null if not */ public X509Certificate findNotaryCertByKeyHash(byte[] certHash) { if (m_logger.isInfoEnabled()) m_logger.info("find notary cert by hash: " + Base64Util.encode(certHash)); Enumeration eCerts = m_ocspCerts.elements(); while (eCerts.hasMoreElements()) { X509Certificate cert = (X509Certificate) eCerts.nextElement(); byte[] hash = SignedDoc.getCertFingerprint(cert); if (m_logger.isInfoEnabled()) m_logger.info("Cert: " + cert.getSubjectDN().getName() + " fingerprint: " + Base64Util.encode(hash) + " len: " + hash.length + " compare: " + Base64Util.encode(certHash)); if (SignedDoc.compareDigests(hash, certHash)) return cert; } return null; // not found } // VS: 02.01.2009 - fix finding ocsp responders cert /** * Returns the OCSP responders CA certificate * @param responderCN responder-id's CN * @returns OCSP responders CA certificate */ public X509Certificate getCACert(String responderCN) { return (responderCN != null) ? (X509Certificate) m_ocspCACerts.get(responderCN) : null; } /** * Get confirmation from AS Sertifitseerimiskeskus * by creating an OCSP request and parsing the returned * OCSP response * @param nonce signature nonce * @param signersCert signature owners cert * @param notId new id for Notary object * @returns Notary object */ public Notary getConfirmation(byte[] nonce, X509Certificate signersCert, String notId) throws DigiDocException { return getConfirmation(nonce, signersCert, getCACert(SignedDoc.getCommonName(signersCert.getIssuerX500Principal().getName("RFC1779"))), getNotaryCert(SignedDoc.getCommonName(signersCert.getIssuerX500Principal().getName("RFC1779")), null), notId); } /** * Get confirmation from AS Sertifitseerimiskeskus * by creating an OCSP request and parsing the returned * OCSP response * @param nonce signature nonce * @param signersCert signature owners cert * @param caCert CA cert for this signer * @param notaryCert notarys own cert * @param notId new id for Notary object * @returns Notary object */ public Notary getConfirmation(byte[] nonce, X509Certificate signersCert, X509Certificate caCert, X509Certificate notaryCert, String notId) // TODO: remove param notaryCert throws DigiDocException { Notary not = null; try { if (m_logger.isDebugEnabled()) m_logger.debug("getConfirmation, nonce " + Base64Util.encode(nonce, 0) + " cert: " + ((signersCert != null) ? signersCert.getSerialNumber().toString() : "NULL") + " CA: " + ((caCert != null) ? caCert.getSerialNumber().toString() : "NULL") + " responder: " + ((notaryCert != null) ? notaryCert.getSerialNumber().toString() : "NULL") + " notId: " + notId + " signRequest: " + m_bSignRequests); if (m_logger.isDebugEnabled()) { m_logger.debug( "Check cert: " + ((signersCert != null) ? signersCert.getSubjectDN().getName() : "NULL")); m_logger.debug("Check CA cert: " + ((caCert != null) ? caCert.getSubjectDN().getName() : "NULL")); } // create the request - sign the request if necessary OCSPReq req = createOCSPRequest(nonce, signersCert, caCert, m_bSignRequests); //debugWriteFile("req.der", req.getEncoded()); if (m_logger.isDebugEnabled()) m_logger.debug("REQUEST:\n" + Base64Util.encode(req.getEncoded(), 0)); // send it OCSPResp resp = sendRequest(req); //debugWriteFile("resp.der", resp.getEncoded()); if (m_logger.isDebugEnabled()) m_logger.debug("RESPONSE:\n" + Base64Util.encode(resp.getEncoded(), 0)); // check response status verifyRespStatus(resp); // check the result not = parseAndVerifyResponse(null, notId, signersCert, resp, nonce); if (m_logger.isDebugEnabled()) m_logger.debug("Confirmation OK!"); } catch (DigiDocException ex) { throw ex; } catch (Exception ex) { DigiDocException.handleException(ex, DigiDocException.ERR_OCSP_GET_CONF); } return not; } /** * Get confirmation from AS Sertifitseerimiskeskus * by creating an OCSP request and parsing the returned * OCSP response * @param sig Signature object. * @param signersCert signature owners cert * @param caCert CA cert for this signer * @returns Notary object */ public Notary getConfirmation(Signature sig, X509Certificate signersCert, X509Certificate caCert) throws DigiDocException { Notary not = null; try { String notId = sig.getId().replace('S', 'N'); // calculate the nonce byte[] nonce = SignedDoc.digest(sig.getSignatureValue().getValue()); X509Certificate notaryCert = null; if (sig.getUnsignedProperties() != null) notaryCert = sig.getUnsignedProperties().getRespondersCertificate(); // check the result not = getConfirmation(nonce, signersCert, caCert, notaryCert, notId); // add cert to signature if (notaryCert == null && sig != null && sig.getUnsignedProperties() != null) { OCSPResp resp = new OCSPResp(sig.getUnsignedProperties().getNotary().getOcspResponseData()); if (resp != null && resp.getResponseObject() != null) { // VS: 02.01.2009 - fix finding ocsp responders cert notaryCert = findNotaryCertByResponderId((BasicOCSPResp) resp.getResponseObject()); if (m_logger.isDebugEnabled()) m_logger.debug("Using notary cert: " + ((notaryCert != null) ? notaryCert.getSubjectDN().getName() : "NULL")); if (notaryCert == null) throw new DigiDocException(DigiDocException.ERR_OCSP_VERIFY, "OCSP responders cert not found", null); // VS: 02.01.2009 - fix finding ocsp responders cert sig.getUnsignedProperties().setRespondersCertificate(notaryCert); } } } catch (DigiDocException ex) { throw ex; } catch (Exception ex) { DigiDocException.handleException(ex, DigiDocException.ERR_OCSP_GET_CONF); } return not; } /** * Get confirmation from AS Sertifitseerimiskeskus * by creating an OCSP request and parsing the returned * OCSP response. CA and reponders certs are read * using paths in the config file or maybe from * a keystore etc. * @param sig Signature object * @param signersCert signature owners cert * @returns Notary object */ public Notary getConfirmation(Signature sig, X509Certificate signersCert) throws DigiDocException { String notId = sig.getId().replace('S', 'N'); byte[] nonce = SignedDoc.digest(sig.getSignatureValue().getValue()); return getConfirmation(nonce, signersCert, getCACert(SignedDoc.getCommonName(signersCert.getIssuerX500Principal().getName("RFC1779"))), null, //((sig.getUnsignedProperties() != null) ? sig.getUnsignedProperties().getRespondersCertificate() : null), notId); } /*private String ocspFileName(X509Certificate cert) { StringBuffer sb = new StringBuffer(cert.getSerialNumber().toString()); sb.append("_"); Date dtNow = new Date(); SimpleDateFormat df = new SimpleDateFormat("HHmmss"); sb.append(df.format(dtNow)); return sb.toString(); }*/ /** * Verifies the certificate by creating an OCSP request * and sending it to SK server. * @param cert certificate to verify * @throws DigiDocException if the certificate is not valid */ public void checkCertificate(X509Certificate cert) throws DigiDocException { try { String verifier = ConfigManager.instance().getStringProperty("DIGIDOC_CERT_VERIFIER", "OCSP"); if (verifier != null && verifier.equals("OCSP")) { // create the request DigiDocFactory ddocFac = ConfigManager.instance().getDigiDocFactory(); X509Certificate caCert = ddocFac.findCAforCertificate(cert); if (m_logger.isDebugEnabled()) { m_logger.debug("Find CA for: " + SignedDoc.getCommonName(cert.getIssuerX500Principal().getName("RFC1779"))); m_logger.debug("Check cert: " + cert.getSubjectDN().getName()); m_logger.debug("Check CA cert: " + caCert.getSubjectDN().getName()); } String strTime = new java.util.Date().toString(); byte[] nonce1 = SignedDoc.digest(strTime.getBytes()); OCSPReq req = createOCSPRequest(nonce1, cert, caCert, m_bSignRequests); //debugWriteFile("req1.der", req.getEncoded()); if (m_logger.isDebugEnabled()) { m_logger.debug("Sending ocsp request: " + req.getEncoded().length + " bytes"); m_logger.debug("REQUEST:\n" + Base64Util.encode(req.getEncoded(), 0)); } // send it OCSPResp resp = sendRequest(req); debugWriteFile("resp1.der", resp.getEncoded()); if (m_logger.isDebugEnabled()) { m_logger.debug("Got ocsp response: " + resp.getEncoded().length + " bytes"); m_logger.debug("RESPONSE:\n" + Base64Util.encode(resp.getEncoded(), 0)); } // check response status verifyRespStatus(resp); // now read the info from the response BasicOCSPResp basResp = (BasicOCSPResp) resp.getResponseObject(); byte[] nonce2 = getNonce(basResp); if (!SignedDoc.compareDigests(nonce1, nonce2)) throw new DigiDocException(DigiDocException.ERR_OCSP_UNSUCCESSFULL, "Invalid nonce value! Possible replay attack!", null); // verify the response try { // VS: 02.01.2009 - fix finding ocsp responders cert X509Certificate notaryCert = findNotaryCertByResponderId(basResp); if (m_logger.isDebugEnabled()) m_logger.debug("Using notary cert: " + ((notaryCert != null) ? notaryCert.getSubjectDN().getName() : "NULL")); if (notaryCert != null) basResp.verify(notaryCert.getPublicKey(), "BC"); else throw new DigiDocException(DigiDocException.ERR_OCSP_VERIFY, "OCSP responders cert not found", null); // VS: 02.01.2009 - fix finding ocsp responders cert } catch (Exception ex) { m_logger.error("OCSP Signature verification error!!!", ex); DigiDocException.handleException(ex, DigiDocException.ERR_OCSP_VERIFY); } // check the response about this certificate checkCertStatus(cert, basResp); } else if (verifier != null && verifier.equals("CRL")) { CRLFactory crlFac = ConfigManager.instance().getCRLFactory(); crlFac.checkCertificate(cert, new Date()); } } catch (DigiDocException ex) { throw ex; } catch (Exception ex) { DigiDocException.handleException(ex, DigiDocException.ERR_OCSP_GET_CONF); } } /** * Verifies the certificate. * @param cert certificate to verify * @param bUseOcsp flag: use OCSP to verify cert. If false then use CRL instead * @throws DigiDocException if the certificate is not valid */ public void checkCertificateOcspOrCrl(X509Certificate cert, boolean bUseOcsp) throws DigiDocException { try { if (bUseOcsp) { // create the request DigiDocFactory ddocFac = ConfigManager.instance().getDigiDocFactory(); X509Certificate caCert = ddocFac.findCAforCertificate(cert); if (m_logger.isDebugEnabled()) { m_logger.debug("Find CA for: " + SignedDoc.getCommonName(cert.getIssuerX500Principal().getName("RFC1779"))); m_logger.debug("Check cert: " + cert.getSubjectDN().getName()); m_logger.debug("Check CA cert: " + caCert.getSubjectDN().getName()); } String strTime = new java.util.Date().toString(); byte[] nonce1 = SignedDoc.digest(strTime.getBytes()); OCSPReq req = createOCSPRequest(nonce1, cert, caCert, m_bSignRequests); //debugWriteFile("req1.der", req.getEncoded()); if (m_logger.isDebugEnabled()) { m_logger.debug("Sending ocsp request: " + req.getEncoded().length + " bytes"); m_logger.debug("REQUEST:\n" + Base64Util.encode(req.getEncoded(), 0)); } // send it OCSPResp resp = sendRequest(req); //debugWriteFile("resp1.der", resp.getEncoded()); if (m_logger.isDebugEnabled()) { m_logger.debug("Got ocsp response: " + resp.getEncoded().length + " bytes"); m_logger.debug("RESPONSE:\n" + Base64Util.encode(resp.getEncoded(), 0)); } // check response status verifyRespStatus(resp); // now read the info from the response BasicOCSPResp basResp = (BasicOCSPResp) resp.getResponseObject(); byte[] nonce2 = getNonce(basResp); if (!SignedDoc.compareDigests(nonce1, nonce2)) throw new DigiDocException(DigiDocException.ERR_OCSP_UNSUCCESSFULL, "Invalid nonce value! Possible replay attack!", null); // verify the response try { // VS: 02.01.2009 - fix finding ocsp responders cert X509Certificate notaryCert = findNotaryCertByResponderId(basResp); if (m_logger.isDebugEnabled()) m_logger.debug("Using notary cert: " + ((notaryCert != null) ? notaryCert.getSubjectDN().getName() : "NULL")); if (notaryCert != null) basResp.verify(notaryCert.getPublicKey(), "BC"); else throw new DigiDocException(DigiDocException.ERR_OCSP_VERIFY, "OCSP responders cert not found", null); // VS: 02.01.2009 - fix finding ocsp responders cert } catch (Exception ex) { m_logger.error("OCSP Signature verification error!!!", ex); DigiDocException.handleException(ex, DigiDocException.ERR_OCSP_VERIFY); } // check the response about this certificate checkCertStatus(cert, basResp); } else { CRLFactory crlFac = ConfigManager.instance().getCRLFactory(); crlFac.checkCertificate(cert, new Date()); } } catch (DigiDocException ex) { throw ex; } catch (Exception ex) { DigiDocException.handleException(ex, DigiDocException.ERR_OCSP_GET_CONF); } } /** * Check the response and parse it's data. * @param sig Signature object * @param resp OCSP response * @param nonce1 nonve value used for request * @param notaryCert notarys own cert * @returns Notary object */ private Notary parseAndVerifyResponse(Signature sig, OCSPResp resp, byte[] nonce1/*, X509Certificate notaryCert*/) throws DigiDocException { String notId = sig.getId().replace('S', 'N'); X509Certificate sigCert = sig.getKeyInfo().getSignersCertificate(); return parseAndVerifyResponse(sig, notId, sigCert, resp, nonce1); } /** * Check the response and parse it's data * @param sig Signature object * @param notId new id for Notary object * @param signersCert signature owners certificate * @param resp OCSP response * @param nonce1 nonve value used for request * @returns Notary object */ private Notary parseAndVerifyResponse(Signature sig, String notId, X509Certificate signersCert, OCSPResp resp, byte[] nonce1) throws DigiDocException { Notary not = null; X509Certificate notaryCert = null; // check the result if (resp == null || resp.getStatus() != OCSPRespStatus.SUCCESSFUL) throw new DigiDocException(DigiDocException.ERR_OCSP_UNSUCCESSFULL, "OCSP response unsuccessfull!", null); try { // now read the info from the response BasicOCSPResp basResp = (BasicOCSPResp) resp.getResponseObject(); // find real notary cert suitable for this response int nNotIdx = 0; String respondIDstr = responderIDtoString(basResp); String notIdCN = SignedDoc.getCommonName(respondIDstr); Exception exVerify = null; boolean bOk = false; do { exVerify = null; if (m_logger.isInfoEnabled()) m_logger.info("Find notary cert for: " + notIdCN + " index: " + nNotIdx); notaryCert = findNotaryCertByIndex(notIdCN, nNotIdx); if (notaryCert != null) { try { bOk = basResp.verify(notaryCert.getPublicKey(), "BC"); if (m_logger.isInfoEnabled()) m_logger.info("Verification with cert: " + notaryCert.getSerialNumber().toString() + " idx: " + nNotIdx + " RC: " + bOk); } catch (Exception ex) { exVerify = ex; if (m_logger.isInfoEnabled()) m_logger.info("Notary cert index: " + nNotIdx + " is not usable for this response!"); } } nNotIdx++; } while (notaryCert != null && (exVerify != null || !bOk)); // if no suitable found the report error if (exVerify != null) { m_logger.error("OCSP verification error!!!", exVerify); DigiDocException.handleException(exVerify, DigiDocException.ERR_OCSP_VERIFY); } if (m_logger.isInfoEnabled() && notaryCert != null) m_logger.info("Using responder cert: " + notaryCert.getSerialNumber().toString()); // 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 byte[] nonce2 = getNonce(basResp); boolean ok = true; if (nonce1.length != nonce2.length) ok = false; for (int i = 0; i < nonce1.length; i++) if (nonce1[i] != nonce2[i]) ok = false; if (!ok && sig != null && !sig.getSignedDoc().getVersion().equals(SignedDoc.VERSION_1_4)) { m_logger.error( "DDOC ver: " + sig.getSignedDoc().getVersion() + " SIG: " + sig.getId() + " Real nonce: " + Base64Util.encode(nonce2, 0) + " My nonce: " + Base64Util.encode(nonce1, 0)); throw new DigiDocException(DigiDocException.ERR_OCSP_NONCE, "OCSP response's nonce doesn't match the requests nonce!", null); } // check the response on our cert checkCertStatus(signersCert, basResp); // create notary not = new Notary(notId, resp.getEncoded(), respondIDstr, basResp.getResponseData().getProducedAt()); if (notaryCert != null) not.setCertNr(notaryCert.getSerialNumber().toString()); } catch (DigiDocException ex) { throw ex; } catch (Exception ex) { DigiDocException.handleException(ex, DigiDocException.ERR_OCSP_PARSE); } return not; } /** * 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); } /** * 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) throws DigiDocException { try { if (m_logger.isDebugEnabled()) m_logger.debug("Checking response status, CERT: " + cert.getSubjectDN().getName() + " SEARCH: " + SignedDoc.getCommonName(cert.getIssuerX500Principal().getName("RFC1779"))); // check the response on our cert DigiDocFactory ddocFac = ConfigManager.instance().getDigiDocFactory(); X509Certificate caCert = ddocFac.findCAforCertificate(cert); //X509Certificate caCert = (X509Certificate)m_ocspCACerts. // get(SignedDoc.getCommonName(cert.getIssuerX500Principal().getName("RFC1779"))); if (m_logger.isDebugEnabled()) { m_logger.debug("CA cert: " + ((caCert == null) ? "NULL" : "OK")); m_logger.debug("RESP: " + basResp); m_logger.debug("CERT: " + cert.getSubjectDN().getName() + " ISSUER: " + cert.getIssuerX500Principal().getName("RFC1779")); if (caCert != null) m_logger.debug("CA CERT: " + caCert.getSubjectDN().getName()); } SingleResp[] sresp = basResp.getResponseData().getResponses(); CertificateID rc = creatCertReq(cert, caCert); //ertificateID certId = creatCertReq(signersCert, caCert); if (m_logger.isDebugEnabled()) m_logger.debug("Search alg: " + rc.getHashAlgOID() + " 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_OCSP_RESP_STATUS, "Certificate has been revoked!", null); } if (status instanceof UnknownStatus) { m_logger.error("Certificate status is unknown!"); throw new DigiDocException(DigiDocException.ERR_OCSP_RESP_STATUS, "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); } //System.out.println("Response status OK!"); } 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); } } private void debugWriteFile(String str, byte[] data) { try { System.out.println("Writing debug file: " + str); FileOutputStream fos = new FileOutputStream(str); fos.write(data); fos.close(); } catch (Exception ex) { System.out.println("Error: " + ex); ex.printStackTrace(System.out); } } /** * 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 { // DEBUG //debugWriteFile("respin.resp", not.getOcspResponseData()); OCSPResp resp = new OCSPResp(not.getOcspResponseData()); // now read the info from the response BasicOCSPResp basResp = (BasicOCSPResp) resp.getResponseObject(); // verify the response try { //X509Certificate notaryCert = sig.getUnsignedProperties().getRespondersCertificate(); 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")); X509Certificate notCer = sig.getUnsignedProperties().getRespondersCertificate(); if (notCer != null) m_logger.debug( "NotCer: " + notCer.getSerialNumber() + " - " + notCer.getSubjectDN().getName()); ee.sk.digidoc.CertID cid = sig.getCertID(ee.sk.digidoc.CertID.CERTID_TYPE_RESPONDER); if (cid != null) m_logger.debug("CID: " + cid.getType() + " id: " + cid.getId() + ", " + cid.getSerial() + " issuer: " + cid.getIssuer()); } String ddocRespCertNr = sig.getUnsignedProperties().getRespondersCertificate().getSerialNumber() .toString(); X509Certificate notaryCert = getNotaryCert(SignedDoc.getCommonName(respondIDstr), ddocRespCertNr); if (notaryCert == null) throw new DigiDocException(DigiDocException.ERR_OCSP_RECPONDER_NOT_TRUSTED, "No certificate for responder: \'" + respondIDstr + "\' found in local certificate store!", null); if (m_logger.isDebugEnabled()) m_logger.debug("Verify using responders cert: " + ((notaryCert != null) ? "OK" : "NULL")); //X509Certificate notaryCert = getNotaryCert(SignedDoc.getCommonName(respondIDstr)); basResp.verify(notaryCert.getPublicKey(), "BC"); } catch (Exception ex) { m_logger.error("Signature verification error: " + ex); DigiDocException.handleException(ex, DigiDocException.ERR_OCSP_VERIFY); } // 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 byte[] nonce1 = SignedDoc.digest(sig.getSignatureValue().getValue()); byte[] nonce2 = getNonce(basResp); boolean ok = true; if (nonce1.length != nonce2.length) ok = false; for (int i = 0; i < nonce1.length; i++) if (nonce1[i] != nonce2[i]) ok = false; // TODO: investigate further /*if(!ok && sig.getSignedDoc() != null && !sig.getSignedDoc().getVersion().equals(SignedDoc.VERSION_1_4)) { 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.error("DDOC ver: " + sig.getSignedDoc().getVersion() + " SIG: " + sig.getId() + " NOT: " + not.getId() + " Real nonce: " + Base64Util.encode(nonce2, 0) + " My nonce: " + Base64Util.encode(nonce1, 0)); m_logger.error("SIG:\n---\n" + sig.toString() + "\n--\n"); throw new DigiDocException(DigiDocException.ERR_OCSP_NONCE, "OCSP response's nonce doesn't match the requests nonce!", null); }*/ // check the response on our cert checkCertStatus(sig, basResp); not.setProducedAt(basResp.getResponseData().getProducedAt()); not.setResponderId(responderIDtoString(basResp)); } catch (DigiDocException ex) { throw ex; } catch (Exception ex) { DigiDocException.handleException(ex, DigiDocException.ERR_OCSP_PARSE); } return not; } // VS: 02.01.2009 - fix finding ocsp responders cert /** * Finds notary cert by responder-id field in basic ocsp response * @param basResp basic ocsp response * @return notary cert or null if not found */ private X509Certificate findNotaryCertByResponderId(BasicOCSPResp basResp) { if (basResp != null) { ResponderID respid = basResp.getResponderId().toASN1Object(); Object o = ((DERTaggedObject) respid.toASN1Object()).getObject(); System.out.println("resp-id-test: " + o.toString()); if (o instanceof ASN1Sequence) { X509Name name = new X509Name((ASN1Sequence) o); String dn = name.toString(); String cn = SignedDoc.getCommonName(dn); System.out.println("Find notary for: " + dn + " -> " + cn); return getNotaryCert(cn, null); } else if (o instanceof DEROctetString) { DEROctetString dHash = (DEROctetString) o; byte[] cHash = null; byte[] cHash2 = null; byte[] cHash3 = null; try { cHash = dHash.getOctets(); cHash2 = dHash.getDEREncoded(); cHash3 = dHash.getEncoded(); } catch (Exception ex) { m_logger.error("Error: " + ex); } System.out.println("Find notary for octects: " + Base64Util.encode(cHash) + " len: " + cHash.length + " hex: " + SignedDoc.bin2hex(cHash) + " der: " + Base64Util.encode(cHash2) + " len: " + cHash2.length + " enc: " + Base64Util.encode(cHash3) + " len: " + cHash3.length); return findNotaryCertByKeyHash(cHash); } else { return null; } } else return null; } // VS: 02.01.2009 - fix finding ocsp responders cert /** * Get String represetation of ResponderID * @param basResp * @return stringified responder ID */ private String responderIDtoString(BasicOCSPResp basResp) { if (basResp != null) { ResponderID respid = basResp.getResponderId().toASN1Object(); Object o = ((DERTaggedObject) respid.toASN1Object()).getObject(); //System.out.println("resp-id-test: " + o.toString()); if (o instanceof ASN1Sequence) { X509Name name = new X509Name((ASN1Sequence) o); return "byName: " + name.toString(); } else if (o instanceof DEROctetString) { // TODO: fix ... return "byKey: " + o.toString(); } else { return null; } } else return null; } /** * Method to get NONCE array from responce * @param basResp * @return OCSP nonce value */ private byte[] getNonce(BasicOCSPResp basResp) { if (basResp != null) { X509Extensions ext = basResp.getResponseData().getResponseExtensions(); X509Extension ex1 = ext.getExtension(new DERObjectIdentifier(nonceOid)); byte[] nonce2 = ex1.getValue().getOctets(); return nonce2; } else return null; } /** * Helper method to verify response status * @param resp OCSP response * @throws DigiDocException if the response status is not ok */ private void verifyRespStatus(OCSPResp resp) throws DigiDocException { int status = resp.getStatus(); switch (status) { case OCSPRespStatus.INTERNAL_ERROR: m_logger.error("An internal error occured in the OCSP Server!"); break; case OCSPRespStatus.MALFORMED_REQUEST: m_logger.error("Your request did not fit the RFC 2560 syntax!"); break; case OCSPRespStatus.SIGREQUIRED: m_logger.error("Your request was not signed!"); break; case OCSPRespStatus.TRY_LATER: m_logger.error("The server was too busy to answer you!"); break; case OCSPRespStatus.UNAUTHORIZED: m_logger.error("The server could not authenticate you!"); break; case OCSPRespStatus.SUCCESSFUL: break; default: m_logger.error("Unknown OCSPResponse status code! " + status); } if (resp == null || resp.getStatus() != OCSPRespStatus.SUCCESSFUL) throw new DigiDocException(DigiDocException.ERR_OCSP_UNSUCCESSFULL, "OCSP response unsuccessfull! ", null); } /** * Method for creating CertificateID for OCSP request * @param signersCert * @param caCert * @param provider * @return * @throws NoSuchAlgorithmException * @throws NoSuchProviderException * @throws CertificateEncodingException */ private CertificateID creatCertReq(X509Certificate signersCert, X509Certificate caCert) throws NoSuchAlgorithmException, NoSuchProviderException, CertificateEncodingException, DigiDocException { // TODO: checks this OID !!! MessageDigest digest = MessageDigest.getInstance("1.3.14.3.2.26", "BC"); if (m_logger.isDebugEnabled()) m_logger.debug("CA cert: " + ((caCert != null) ? caCert.toString() : "NULL")); X509Principal issuerName = PrincipalUtil.getSubjectX509Principal(caCert); if (m_logger.isDebugEnabled()) m_logger.debug("CA issuer: " + ((issuerName != null) ? issuerName.getName() : "NULL")); //Issuer name hash digest.update(issuerName.getEncoded()); ASN1OctetString issuerNameHash = new BERConstructedOctetString(digest.digest()); //Issuer key hash will be readed out from X509extendions // 4 first bytes are not useful for me, oid 2.5.29.15 contains keyid byte[] arr = caCert.getExtensionValue("2.5.29.14"); if (m_logger.isDebugEnabled()) m_logger.debug("Issuer key hash: " + ((arr != null) ? arr.length : 0)); if (arr == null || arr.length == 0) throw new DigiDocException(DigiDocException.ERR_CA_CERT_READ, "CA certificate has no SubjectKeyIdentifier extension!", null); byte[] arr2 = new byte[arr.length - 4]; System.arraycopy(arr, 4, arr2, 0, arr2.length); ASN1OctetString issuerKeyHash = new BERConstructedOctetString(arr2); CertID cerid = new CertID(new AlgorithmIdentifier("1.3.14.3.2.26"), issuerNameHash, issuerKeyHash, new DERInteger(signersCert.getSerialNumber())); return new CertificateID(cerid); } /** * Creates a new OCSP request * @param nonce 128 byte RSA+SHA1 signatures digest * Use null if you want to verify only the certificate * and this is not related to any signature * @param signersCert signature owners cert * @param caCert CA cert for this signer * @param bSigned flag signed request or not */ private OCSPReq createOCSPRequest(byte[] nonce, X509Certificate signersCert, X509Certificate caCert, boolean bSigned) throws DigiDocException { OCSPReq req = null; OCSPReqGenerator ocspRequest = new OCSPReqGenerator(); try { //Create certificate id, for OCSP request CertificateID certId = creatCertReq(signersCert, caCert); if (m_logger.isDebugEnabled()) m_logger.debug("Request for: " + certId.getHashAlgOID() + " serial: " + certId.getSerialNumber() + " issuer: " + Base64Util.encode(certId.getIssuerKeyHash()) + " subject: " + Base64Util.encode(certId.getIssuerNameHash())); ocspRequest.addRequest(certId); if (nonce != null) { ASN1OctetString ocset = new BERConstructedOctetString(nonce); X509Extension ext = new X509Extension(false, ocset); //nonce Identifier DERObjectIdentifier nonceIdf = new DERObjectIdentifier(nonceOid); Hashtable tbl = new Hashtable(1); tbl.put(nonceIdf, ext); // create extendions, with one extendion(NONCE) X509Extensions extensions = new X509Extensions(tbl); ocspRequest.setRequestExtensions(extensions); } //X509Name n = new X509Name() GeneralName name = null; if (bSigned) { if (m_logger.isDebugEnabled()) m_logger.debug("SignCert: " + ((m_signCert != null) ? m_signCert.toString() : "NULL")); name = new GeneralName(PrincipalUtil.getSubjectX509Principal(m_signCert)); } else { name = new GeneralName(PrincipalUtil.getSubjectX509Principal(signersCert)); // VS: Mihhails patch for accepting Hansa's cert /* Hashtable myLookUp=new Hashtable(X509Name.DefaultLookUp); DERObjectIdentifier SERIALNUMBER = new DERObjectIdentifier("2.5.4.5"); myLookUp.put(SERIALNUMBER, "SERIALNUMBER"); name = new GeneralName(new X509Name(X509Name.DefaultReverse, myLookUp,signersCert.getSubjectDN().toString())); */ } ocspRequest.setRequestorName(name); if (bSigned) { // lets generate signed request X509Certificate[] chain = { m_signCert }; req = ocspRequest.generate("SHA1WITHRSA", m_signKey, chain, "BC"); if (!req.verify(m_signCert.getPublicKey(), "BC")) { m_logger.error("Verify failed"); } } else { // unsigned request req = ocspRequest.generate(); } } catch (Exception ex) { DigiDocException.handleException(ex, DigiDocException.ERR_OCSP_REQ_CREATE); } return req; } /** * Sends the OCSP request to Notary and * retrieves the response * @param req OCSP request * @returns OCSP response */ private OCSPResp sendRequest(OCSPReq req) throws DigiDocException { OCSPResp resp = null; try { byte[] breq = req.getEncoded(); // debugWriteFile("request-bc.req", breq); String responderUrl = ConfigManager.instance().getProperty("DIGIDOC_OCSP_RESPONDER_URL"); URL url = new URL(responderUrl); URLConnection con = url.openConnection(); con.setAllowUserInteraction(false); con.setUseCaches(false); con.setDoOutput(true); con.setDoInput(true); // send the OCSP request con.setRequestProperty("Content-Type", "application/ocsp-request"); OutputStream os = con.getOutputStream(); os.write(breq); os.close(); // read the response InputStream is = con.getInputStream(); int cl = con.getContentLength(); byte[] bresp = null; //System.out.println("Content: " + cl + " bytes"); if (cl > 0) { int avail = 0; do { avail = is.available(); byte[] data = new byte[avail]; int rc = is.read(data); if (bresp == null) { bresp = new byte[rc]; System.arraycopy(data, 0, bresp, 0, rc); } else { byte[] tmp = new byte[bresp.length + rc]; System.arraycopy(bresp, 0, tmp, 0, bresp.length); System.arraycopy(data, 0, tmp, bresp.length, rc); bresp = tmp; } //System.out.println("Got: " + avail + "/" + rc + " bytes!"); cl -= rc; } while (cl > 0); } is.close(); if (bresp != null) { //debugWriteFile("response-bc.resp", bresp); resp = new OCSPResp(bresp); //System.out.println("Response: " + resp.toString()); } } catch (Exception ex) { DigiDocException.handleException(ex, DigiDocException.ERR_OCSP_REQ_SEND); } return resp; } /** * initializes the implementation class */ public void init() throws DigiDocException { 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); } String sigFlag = ConfigManager.instance().getProperty("SIGN_OCSP_REQUESTS"); m_bSignRequests = (sigFlag != null && sigFlag.equals("true")); // only need this if we must sign the requests Provider prv = (Provider) Class .forName(ConfigManager.instance().getProperty("DIGIDOC_SECURITY_PROVIDER")).newInstance(); //System.out.println("Provider"); //prv.list(System.out); Security.addProvider(prv); if (m_bSignRequests) { // load the cert & private key for OCSP signing String p12file = ConfigManager.instance().getProperty("DIGIDOC_PKCS12_CONTAINER"); String p12paswd = ConfigManager.instance().getProperty("DIGIDOC_PKCS12_PASSWD"); if (p12file != null && p12paswd != null) { FileInputStream fi = new FileInputStream(p12file); KeyStore store = KeyStore.getInstance("PKCS12", "BC"); store.load(fi, p12paswd.toCharArray()); java.util.Enumeration en = store.aliases(); // find the key alias String pName = null; while (en.hasMoreElements()) { String n = (String) en.nextElement(); if (store.isKeyEntry(n)) { pName = n; } } m_signKey = (PrivateKey) store.getKey(pName, null); m_signCert = (X509Certificate) store.getCertificate(pName); if (m_logger.isInfoEnabled()) { m_logger.info( "p12cert subject: " + m_signCert.getSubjectX500Principal().getName("RFC1779")); m_logger.info("p12cert issuer: " + m_signCert.getIssuerX500Principal().getName("RFC1779")); m_logger.info("p12cert serial: " + m_signCert.getSerialNumber()); } } } // OCSP certs int nCerts = ConfigManager.instance().getIntProperty("DIGIDOC_OCSP_COUNT", 0); for (int i = 1; i <= nCerts; i++) { String ocspCN = ConfigManager.instance().getProperty("DIGIDOC_OCSP" + i + "_CN"); String ocspCertFile = ConfigManager.instance().getFilePathProperty("DIGIDOC_OCSP" + i + "_CERT"); String ocspCAFile = ConfigManager.instance().getFilePathProperty("DIGIDOC_OCSP" + i + "_CA_CERT"); String ocspCACN = ConfigManager.instance().getProperty("DIGIDOC_OCSP" + i + "_CA_CN"); if (m_logger.isDebugEnabled()) { m_logger.debug("Responder: " + ocspCN + " cert: " + ocspCertFile + " CA: " + ocspCACN + " ca-cert: " + ocspCAFile); } //System.out.println("Responder: " + ocspCN + " cert: " + // ocspCertFile + " ca-cert: " + ocspCAFile); if (ocspCertFile != null) m_ocspCerts.put(ocspCN, SignedDoc.readCertificate(ocspCertFile)); m_ocspCACerts.put(ocspCACN, SignedDoc.readCertificate(ocspCAFile)); // read any further certs if they exist int j = 1; String certFile = null; do { certFile = ConfigManager.instance().getFilePathProperty("DIGIDOC_OCSP" + i + "_CERT_" + j); if (certFile != null) { if (m_logger.isDebugEnabled()) { m_logger.debug( "Responder: " + ocspCN + " cert: " + ocspCertFile + " ca-cert: " + ocspCAFile); } m_ocspCerts.put(ocspCN + "-" + j, SignedDoc.readCertificate(certFile)); } j++; } while (certFile != null); } } catch (Exception ex) { DigiDocException.handleException(ex, DigiDocException.ERR_NOT_FAC_INIT); } } }