package com.ausregistry.jtoolkit2.se.tmch; import javax.xml.bind.DatatypeConverter; import javax.xml.crypto.MarshalException; import javax.xml.crypto.dsig.XMLSignature; import javax.xml.crypto.dsig.XMLSignatureException; import javax.xml.crypto.dsig.XMLSignatureFactory; import javax.xml.crypto.dsig.dom.DOMValidateContext; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import java.io.*; import java.security.InvalidAlgorithmParameterException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.*; import java.util.*; import com.ausregistry.jtoolkit2.se.tmch.exception.*; import com.ausregistry.jtoolkit2.xml.NamespaceContextImpl; import com.ausregistry.jtoolkit2.xml.ParsingException; import org.apache.commons.codec.DecoderException; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.xml.sax.SAXException; import static com.ausregistry.jtoolkit2.se.ExtendedObjectType.SIGNED_MARK_DATA; import static com.ausregistry.jtoolkit2.se.ExtendedObjectType.XML_DSIG; /** * This defines the operations to facilitate validation and parsing of signed mark data for TMCH. */ public class TmchValidatingParser extends TmchXmlParser { public static final String CERTIFICATE_BEGIN_DELIMITER = "-----BEGIN CERTIFICATE-----\n"; public static final String CERTIFICATE_END_DELIMITER = "\n-----END CERTIFICATE-----\n"; private static final String SMD_BASE_EXPR = "/" + SIGNED_MARK_DATA.getName() + ":"; private static final String DS_BASE_EXPR = "/" + XML_DSIG.getName() + ":"; private static final String SIGNED_MARK = "signedMark"; private static final String SMD_NOT_BEFORE_EXPR = SMD_BASE_EXPR + SIGNED_MARK + SMD_BASE_EXPR + "notBefore"; private static final String SMD_NOT_AFTER_EXPR = SMD_BASE_EXPR + SIGNED_MARK + SMD_BASE_EXPR + "notAfter"; private static final String SMD_ID_EXPR = SMD_BASE_EXPR + SIGNED_MARK + SMD_BASE_EXPR + "id"; private static final String SMD_DS_SIGNATURE_EXPR = SMD_BASE_EXPR + SIGNED_MARK + DS_BASE_EXPR + "Signature"; private static final String CERTIFICATE_XPATH_EXPR = SMD_BASE_EXPR + SIGNED_MARK + DS_BASE_EXPR + "Signature" + DS_BASE_EXPR + "KeyInfo" + DS_BASE_EXPR + "X509Data" + DS_BASE_EXPR + "X509Certificate"; private final List<String> smdrlIdList = new ArrayList<String>(); private final CRL certRevocationList; private final CertificateFactory certificateFactory; private final KeyStore icannCertificateTrustStore; private DocumentBuilderFactory documentBuilderFactory; private XPath xPath; private final XMLSignatureFactory xmlSignatureFactory; /** * Instantiate a TmchValidatingParser which validates and parses an encoded SMD. * * @param certificateRevocationList the Certificate Revocation List * @param smdRevocationList the SMD Revocation List * @param tmchIssuingAuthorityCert the SMD Issuing Authority Certificate * @throws CertificateException if an exception occurs while processing CRL or Issuing Authority certificate * @throws CRLException if an exception occurs while processing CRL * @throws IOException if an exception occurs while processing SMDRL or Issuing Authority certificate * @throws KeyStoreException if an exception occurs while processing Issuing Authority certificate * @throws NoSuchAlgorithmException if an exception occurs while processing Issuing Authority certificate */ public TmchValidatingParser(InputStream certificateRevocationList, InputStream smdRevocationList, InputStream tmchIssuingAuthorityCert) throws CertificateException, CRLException, IOException, KeyStoreException, NoSuchAlgorithmException { documentBuilderFactory = DocumentBuilderFactory.newInstance(); documentBuilderFactory.setNamespaceAware(true); certificateFactory = CertificateFactory.getInstance("X.509"); certRevocationList = certificateFactory.generateCRL(certificateRevocationList); xPath = XPathFactory.newInstance().newXPath(); xPath.setNamespaceContext(new NamespaceContextImpl()); readSmdRevocationList(smdRevocationList); Certificate icannTmchCACertificate = certificateFactory.generateCertificate(tmchIssuingAuthorityCert); icannCertificateTrustStore = KeyStore.getInstance(KeyStore.getDefaultType()); icannCertificateTrustStore.load(null, null); icannCertificateTrustStore.setCertificateEntry("tmchCA", icannTmchCACertificate); xmlSignatureFactory = XMLSignatureFactory.getInstance(); } private void readSmdRevocationList(InputStream smdRevocationList) throws IOException { BufferedReader smdrlReader = new BufferedReader(new InputStreamReader(smdRevocationList)); smdrlReader.readLine(); smdrlReader.readLine(); String line; while ((line = smdrlReader.readLine()) != null) { String[] smdrlTokens = line.split(","); smdrlIdList.add(smdrlTokens[0]); } } /** * Decodes and validates the provided base64-encoded SMD based on the provided date. * If the SMD passes validation, it is parsed into a SignedMarkData bean. * * @param encodedSignedMarkData Input stream to the base64-encoded SMD to be validated. * @param dateForValidation The date against which the input SMD needs to be validated against. * @return if the input SMD is valid the parsed SignedMarkDate object * @throws IOException In case the input stream cannot be read * @throws ParsingException In case an error occurs while parsing * @throws DecoderException In case the stream cannot be decoded * @throws XPathExpressionException if an exception occurs while parsing the decoded SMD * @throws ParserConfigurationException if an exception occurs while parsing the decoded SMD * @throws NoSuchAlgorithmException if an exception occurs while parsing the SMD certificate * @throws CertificateException if an exception occurs while validating SMD certificate * @throws KeyStoreException if an exception occurs while validating SMD certificate * @throws InvalidAlgorithmParameterException if an exception occurs while validating SMD certificate */ public SignedMarkData validateAndParseEncodedSignedMarkData(InputStream encodedSignedMarkData, Date dateForValidation) throws ParsingException, IOException, DecoderException, ParserConfigurationException, XPathExpressionException, NoSuchAlgorithmException, CertificateException, KeyStoreException, InvalidAlgorithmParameterException, SAXException { return validateEncodedSignedMarkDataForDate(encodedSignedMarkData, dateForValidation); } /** * Decodes and validates the provided base64-encoded SMD against the current date. * If the SMD passes validation, it is parsed into a SignedMarkData bean. * * @param encodedSignedMarkData Input stream to the base64-encoded SMD to be validated * @return if the input SMD is valid the parsed SignedMarkDate object * @throws IOException In case the input stream cannot be read * @throws ParsingException In case an error occurs while parsing * @throws DecoderException In case the stream cannot be decoded * @throws XPathExpressionException if an exception occurs while parsing the decoded SMD * @throws ParserConfigurationException if an exception occurs while parsing the decoded SMD * @throws NoSuchAlgorithmException if an exception occurs while parsing the SMD certificate * @throws CertificateException if an exception occurs while validating SMD certificate * @throws KeyStoreException if an exception occurs while validating SMD certificate * @throws InvalidAlgorithmParameterException if an exception occurs while validating SMD certificate */ public SignedMarkData validateAndParseEncodedSignedMarkData(InputStream encodedSignedMarkData) throws ParsingException, IOException, DecoderException, ParserConfigurationException, XPathExpressionException, NoSuchAlgorithmException, CertificateException, KeyStoreException, InvalidAlgorithmParameterException, SAXException { return validateEncodedSignedMarkDataForDate(encodedSignedMarkData, new Date()); } private SignedMarkData validateEncodedSignedMarkDataForDate(InputStream encodedSignedMarkData, Date dateForValidation) throws ParsingException, IOException, DecoderException, ParserConfigurationException, XPathExpressionException, NoSuchAlgorithmException, CertificateException, KeyStoreException, InvalidAlgorithmParameterException, SAXException { byte[] dataBytes = decodeSignedMarkData(encodedSignedMarkData); Document document = null; try { document = loadSmdXmlIntoDocument(new ByteArrayInputStream(dataBytes)); } catch (SAXException e) { throw new InvalidSignedMarkDataException(e); } Node signatureNode = extractSignatureNode(document); X509Certificate x509Certificate = extractCertificateFromDocument(document); validateSignature(signatureNode, x509Certificate); assertCertificateIsValid(dateForValidation, x509Certificate); assertCertificateNotRevoked(x509Certificate); assertSmdNotRevoked(document); Calendar notBeforeDate = DatatypeConverter.parseDate(xPath.evaluate(SMD_NOT_BEFORE_EXPR, document)); Calendar notAfterDate = DatatypeConverter.parseDate(xPath.evaluate(SMD_NOT_AFTER_EXPR, document)); if (dateForValidation.before(notBeforeDate.getTime())) { throw new NotYetValidSignedMarkDataException(notBeforeDate.getTime()); } if (dateForValidation.after(notAfterDate.getTime())) { throw new ExpiredSignedMarkDataException(notAfterDate.getTime()); } return parseDecodedSignedMarkData(new ByteArrayInputStream(dataBytes)); } private void assertSmdNotRevoked(Document document) throws XPathExpressionException { String smdId = xPath.evaluate(SMD_ID_EXPR, document); if (smdrlIdList.contains(smdId)) { throw new TmchSmdRevokedException(smdId); } } private Node extractSignatureNode(Document document) throws XPathExpressionException { Node signatureNode = (Node) xPath.evaluate(SMD_DS_SIGNATURE_EXPR, document, XPathConstants.NODE); if (signatureNode == null) { throw new SmdSignatureMissingException(); } return signatureNode; } private void validateSignature(Node signatureNode, X509Certificate x509Certificate) { DOMValidateContext validateContext = new DOMValidateContext(x509Certificate.getPublicKey(), signatureNode); signatureNode.getOwnerDocument().getDocumentElement().setIdAttribute("id", true); XMLSignature xmlSignature; try { xmlSignature = xmlSignatureFactory.unmarshalXMLSignature(validateContext); } catch (MarshalException e) { throw new InvalidSignedMarkDataException(e); } try { if (!xmlSignature.validate(validateContext)) { throw new SmdSignatureInvalidException(); } } catch (XMLSignatureException e) { throw new InvalidSignedMarkDataException(e); } } private void assertCertificateIsValid(Date currentDate, X509Certificate x509Certificate) throws NoSuchAlgorithmException, CertificateException, KeyStoreException, InvalidAlgorithmParameterException { CertPathValidator certPathValidator = CertPathValidator.getInstance("PKIX"); CertPath certPath = certificateFactory.generateCertPath(Arrays.asList(x509Certificate)); PKIXParameters pkixParameters = new PKIXParameters(icannCertificateTrustStore); pkixParameters.setRevocationEnabled(false); pkixParameters.setDate(currentDate); try { certPathValidator.validate(certPath, pkixParameters); } catch (CertPathValidatorException e) { throw new TmchInvalidCertificateException(x509Certificate, e); } } private X509Certificate assertCertificateNotRevoked(X509Certificate x509Certificate) throws XPathExpressionException { if (certRevocationList.isRevoked(x509Certificate)) { throw new TmchCertificateRevokedException(x509Certificate); } return x509Certificate; } private X509Certificate extractCertificateFromDocument(Document document) throws XPathExpressionException { String certificateString = xPath.evaluate(CERTIFICATE_XPATH_EXPR, document); String certificateEntireContent = CERTIFICATE_BEGIN_DELIMITER + certificateString + CERTIFICATE_END_DELIMITER; try { Certificate certificate = certificateFactory.generateCertificate(new ByteArrayInputStream( certificateEntireContent.getBytes())); if (!X509Certificate.class.isAssignableFrom(certificate.getClass())) { throw new TmchCertificateInvalidTypeException(certificate.getClass()); } return (X509Certificate) certificate; } catch (CertificateException e) { throw new InvalidSignedMarkDataException(e); } } }