From 24cf71642d3b903cfaf1b0093381405e1d32d95a Mon Sep 17 00:00:00 2001 From: chubtub <43381989+chubtub@users.noreply.github.com> Date: Wed, 4 Nov 2020 15:21:48 -0500 Subject: [PATCH 1/4] Add validation for support RIM hash and base RIM signature. --- ...eferenceManifestDetailsPageController.java | 40 ++- .../main/webapp/WEB-INF/jsp/rim-details.jsp | 34 ++- .../data/persist/BaseReferenceManifest.java | 1 + .../utils/ReferenceManifestValidator.java | 274 ++++++++++++++++++ .../main/resources/identity_transform.xslt | 10 + 5 files changed, 355 insertions(+), 4 deletions(-) create mode 100644 HIRS_Utils/src/main/java/hirs/utils/ReferenceManifestValidator.java create mode 100644 HIRS_Utils/src/main/resources/identity_transform.xslt diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/ReferenceManifestDetailsPageController.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/ReferenceManifestDetailsPageController.java index 19e0df39..19bf4ec2 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/ReferenceManifestDetailsPageController.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/ReferenceManifestDetailsPageController.java @@ -4,6 +4,9 @@ import hirs.data.persist.BaseReferenceManifest; import hirs.data.persist.ReferenceManifest; import hirs.data.persist.SupportReferenceManifest; import hirs.data.persist.SwidResource; +import hirs.data.persist.certificate.Certificate; +import hirs.data.persist.certificate.CertificateAuthorityCredential; +import hirs.persist.CertificateManager; import hirs.persist.DBManagerException; import hirs.persist.ReferenceManifestManager; import hirs.tpm.eventlog.TCGEventLog; @@ -12,6 +15,7 @@ import hirs.attestationca.portal.page.PageController; import hirs.attestationca.portal.page.PageMessages; import hirs.attestationca.portal.page.params.ReferenceManifestDetailsPageParams; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; @@ -21,6 +25,7 @@ import java.util.List; import java.util.ArrayList; import java.util.UUID; +import hirs.utils.ReferenceManifestValidator; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; @@ -38,6 +43,7 @@ public class ReferenceManifestDetailsPageController extends PageController { private final ReferenceManifestManager referenceManifestManager; + private final CertificateManager certificateManager; private static final Logger LOGGER = LogManager.getLogger(ReferenceManifestDetailsPageController.class); @@ -48,9 +54,11 @@ public class ReferenceManifestDetailsPageController */ @Autowired public ReferenceManifestDetailsPageController( - final ReferenceManifestManager referenceManifestManager) { + final ReferenceManifestManager referenceManifestManager, + final CertificateManager certificateManager) { super(Page.RIM_DETAILS); this.referenceManifestManager = referenceManifestManager; + this.certificateManager = certificateManager; } /** @@ -80,7 +88,7 @@ public class ReferenceManifestDetailsPageController } else { try { UUID uuid = UUID.fromString(params.getId()); - data.putAll(getRimDetailInfo(uuid, referenceManifestManager)); + data.putAll(getRimDetailInfo(uuid, referenceManifestManager, certificateManager)); } catch (IllegalArgumentException iaEx) { String uuidError = "Failed to parse ID from: " + params.getId(); messages.addError(uuidError); @@ -114,7 +122,8 @@ public class ReferenceManifestDetailsPageController * @throws CertificateException if a certificate doesn't parse. */ public static HashMap getRimDetailInfo(final UUID uuid, - final ReferenceManifestManager referenceManifestManager) throws IOException, + final ReferenceManifestManager referenceManifestManager, + final CertificateManager certificateManager) throws IOException, CertificateException, NoSuchAlgorithmException { HashMap data = new HashMap<>(); @@ -183,11 +192,22 @@ public class ReferenceManifestDetailsPageController logProcessor = new TCGEventLog(support.getRimBytes()); } } + + ReferenceManifestValidator rimValidator = new ReferenceManifestValidator( + new ByteArrayInputStream(bRim.getRimBytes())); + // going to have to pull the filename and grab that from the DB // to get the id to make the link for (SwidResource swidRes : resources) { if (support != null && swidRes.getName() .equals(support.getFileName())) { + rimValidator.validateSupportRimHash(support.getRimBytes(), + rimValidator.getDocument()); + if (rimValidator.isSupportRimValid()) { + data.put("supportRimHashValid", true); + } else { + data.put("supportRimHashValid", false); + } swidRes.setPcrValues(Arrays.asList( logProcessor.getExpectedPCRValues())); break; @@ -198,6 +218,20 @@ public class ReferenceManifestDetailsPageController data.put("associatedRim", bRim.getAssociatedRim()); data.put("swidFiles", resources); + + rimValidator.validateXmlSignature(rimValidator.getDocument()); + data.put("signatureValid", rimValidator.isSignatureValid()); + if (rimValidator.isSignatureValid()) { + try { + Certificate certificate = + CertificateAuthorityCredential.select(certificateManager) + .byEncodedPublicKey(rimValidator.getPublicKey().getEncoded()) + .getCertificate(); + data.put("issuerID", certificate.getId().toString()); + } catch (NullPointerException e) { + LOGGER.info("Unable to get signing certificate link: " + e.getMessage()); + } + } } else { SupportReferenceManifest sRim = SupportReferenceManifest .select(referenceManifestManager) diff --git a/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/rim-details.jsp b/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/rim-details.jsp index 21ae4324..dab7c407 100644 --- a/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/rim-details.jsp +++ b/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/rim-details.jsp @@ -17,7 +17,13 @@ -
+ + + + + + +
@@ -171,6 +177,14 @@ ${resource.getName()} + + + + + + + + ${resource.getName()} @@ -237,6 +251,24 @@
+
+
Signature
+
+
Validity:  + + + + + + + + + + + +
+
+
diff --git a/HIRS_Utils/src/main/java/hirs/data/persist/BaseReferenceManifest.java b/HIRS_Utils/src/main/java/hirs/data/persist/BaseReferenceManifest.java index 00a544f2..e8fefe5d 100644 --- a/HIRS_Utils/src/main/java/hirs/data/persist/BaseReferenceManifest.java +++ b/HIRS_Utils/src/main/java/hirs/data/persist/BaseReferenceManifest.java @@ -151,6 +151,7 @@ public class BaseReferenceManifest extends ReferenceManifest { public BaseReferenceManifest(final byte[] rimBytes) throws IOException { super(rimBytes); this.setRimType(BASE_RIM); + this.setFileName(""); SoftwareIdentity si = validateSwidTag(new ByteArrayInputStream(rimBytes)); // begin parsing valid swid tag diff --git a/HIRS_Utils/src/main/java/hirs/utils/ReferenceManifestValidator.java b/HIRS_Utils/src/main/java/hirs/utils/ReferenceManifestValidator.java new file mode 100644 index 00000000..1980fb46 --- /dev/null +++ b/HIRS_Utils/src/main/java/hirs/utils/ReferenceManifestValidator.java @@ -0,0 +1,274 @@ +package hirs.utils; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import javax.xml.XMLConstants; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.UnmarshalException; +import javax.xml.bind.Unmarshaller; +import javax.xml.crypto.*; +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.crypto.dsig.keyinfo.KeyInfo; +import javax.xml.crypto.dsig.keyinfo.X509Data; +import javax.xml.transform.*; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.Key; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.cert.X509Certificate; +import java.util.Iterator; + +public class ReferenceManifestValidator { + private static final String SIGNATURE_ALGORITHM_RSA_SHA256 = + "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"; + private static final String SCHEMA_PACKAGE = "hirs.utils.xjc"; + private static final String SCHEMA_URL = "swid_schema.xsd"; + private static final String SCHEMA_LANGUAGE = XMLConstants.W3C_XML_SCHEMA_NS_URI; + private static final String IDENTITY_TRANSFORM = "identity_transform.xslt"; + private static final String SHA256 = "SHA-256"; + private static final Logger LOGGER = LogManager.getLogger(ReferenceManifestValidator.class); + + private Unmarshaller unmarshaller; + private PublicKey publicKey; + private Document document; + private boolean signatureValid, supportRimValid; + + public boolean isSignatureValid() { + return signatureValid; + } + + public boolean isSupportRimValid() { + return supportRimValid; + } + + public PublicKey getPublicKey() { return publicKey; } + + public Document getDocument() { return document; } + + public ReferenceManifestValidator() {} + + public ReferenceManifestValidator(InputStream input) { + try { + JAXBContext jaxbContext = JAXBContext.newInstance(SCHEMA_PACKAGE); + unmarshaller = jaxbContext.createUnmarshaller(); + signatureValid = false; + supportRimValid = false; + publicKey = null; + document = unmarshallSwidTag(removeXMLWhitespace(new StreamSource(input))); + } catch (JAXBException e) { + LOGGER.warn("Error initializing JAXBContext: " + e.getMessage()); + } catch (IOException e) { + LOGGER.warn("Error during unmarshalling: " + e.getMessage()); + } + } + + public void validateSupportRimHash(byte[] input, Document doc) { + String calculatedHash = getHashValue(input, SHA256); + Element file = (Element) doc.getElementsByTagName("File").item(0); + LOGGER.info("Calculated hash: " + calculatedHash + + ", actual: " + file.getAttribute("SHA256:hash")); + if (file.getAttribute("SHA256:hash").equals(calculatedHash)) { + supportRimValid = true; + } else { + supportRimValid = false; + } + } + + /** + * This method validates the signature block in the Document and stores the + * result for public access. + * + * @param doc the xml data in Document form. + */ + public void validateXmlSignature(Document doc) throws IOException { + signatureValid = validateSignedXMLDocument(doc); + } + + private String getHashValue(byte[] input, String sha) { + String resultString = null; + try { + MessageDigest md = MessageDigest.getInstance(sha); + byte[] bytes = md.digest(input); + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < bytes.length; i++) { + sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1)); + } + resultString = sb.toString(); + } catch (NoSuchAlgorithmException grex) { + LOGGER.warn(grex.getMessage()); + } + + return resultString; + } + + /** + * This method validates a Document with a signature element. + * + * @param doc + */ + private boolean validateSignedXMLDocument(Document doc) { + DOMValidateContext context; + boolean isValid = false; + try { + NodeList nodes = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature"); + if (nodes.getLength() == 0) { + throw new Exception("Signature element not found!"); + } + NodeList embeddedCert = doc.getElementsByTagName("X509Data"); + if (embeddedCert.getLength() > 0) { + X509KeySelector keySelector = new ReferenceManifestValidator.X509KeySelector(); + context = new DOMValidateContext(keySelector, nodes.item(0)); + XMLSignatureFactory sigFactory = XMLSignatureFactory.getInstance("DOM"); + XMLSignature signature = sigFactory.unmarshalXMLSignature(context); + isValid = signature.validate(context); + publicKey = keySelector.getPublicKey(); + } else { + LOGGER.info("Signing certificate not found for validation!"); + } + } catch (MarshalException | XMLSignatureException e) { + LOGGER.warn(e.getMessage()); + } catch (Exception e) { + LOGGER.warn(e.getMessage()); + LOGGER.info(e.getMessage()); + } + + return isValid; + } + + public class X509KeySelector extends KeySelector { + private PublicKey publicKey; + + public KeySelectorResult select(KeyInfo keyinfo, + KeySelector.Purpose purpose, + AlgorithmMethod algorithm, + XMLCryptoContext context) throws KeySelectorException { + Iterator keyinfoItr = keyinfo.getContent().iterator(); + while(keyinfoItr.hasNext()) { + XMLStructure element = (XMLStructure) keyinfoItr.next(); + if (element instanceof X509Data) { + X509Data data = (X509Data) element; + Iterator dataItr = data.getContent().iterator(); + while (dataItr.hasNext()) { + Object object = dataItr.next(); + if (object instanceof X509Certificate) { + publicKey = ((X509Certificate) object).getPublicKey(); + if (areAlgorithmsEqual(algorithm.getAlgorithm(), publicKey.getAlgorithm())) { + return new ReferenceManifestValidator.X509KeySelector.RIMKeySelectorResult(publicKey); + } + } + } + } + } + + throw new KeySelectorException("No key found!"); + } + + public boolean areAlgorithmsEqual(String uri, String name) { + if (uri.equals(SIGNATURE_ALGORITHM_RSA_SHA256) && name.equalsIgnoreCase("RSA")) { + return true; + } else { + return false; + } + } + + public PublicKey getPublicKey() { + return publicKey; + } + + private class RIMKeySelectorResult implements KeySelectorResult { + private Key key; + + public RIMKeySelectorResult(Key key) { + this.key = key; + } + + public Key getKey() { + return key; + } + } + } + + /** + * This method unmarshalls the swidtag found at [path] into a Document object + * and validates it according to the schema. + * + * @param doc of the input swidtag. + * @return document validated against the schema. + */ + private Document unmarshallSwidTag(Document doc) { + InputStream is = null; + try { + is = ReferenceManifestValidator.class + .getClassLoader().getResourceAsStream(SCHEMA_URL); + SchemaFactory schemaFactory = SchemaFactory.newInstance(SCHEMA_LANGUAGE); + Schema schema = schemaFactory.newSchema(new StreamSource(is)); + unmarshaller.setSchema(schema); + unmarshaller.unmarshal(doc); + } catch (SAXException e) { + LOGGER.warn("Error setting schema for validation!"); + } catch (UnmarshalException e) { + LOGGER.warn("Error validating swidtag file!"); + } catch (IllegalArgumentException e) { + LOGGER.warn("Input file empty."); + } catch (JAXBException e) { + e.printStackTrace(); + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + LOGGER.warn("Error closing input stream"); + } + } + } + + return doc; + } + + /** + * This method strips all whitespace from an xml file, including indents and spaces + * added for human-readability. + * @param source of the input xml. + * @return Document representation of the xml. + */ + private Document removeXMLWhitespace(StreamSource source) throws IOException { + TransformerFactory tf = TransformerFactory.newInstance(); + Source identitySource = new StreamSource( + ReferenceManifestValidator.class.getClassLoader() + .getResourceAsStream(IDENTITY_TRANSFORM)); + Document doc = null; + try { + Transformer transformer = tf.newTransformer(identitySource); + DOMResult result = new DOMResult(); + transformer.transform(source, result); + doc = (Document) result.getNode(); + } catch (TransformerConfigurationException e) { + LOGGER.warn("Error configuring transformer!"); + e.printStackTrace(); + } catch (TransformerException e) { + LOGGER.warn("Error transforming input!"); + e.printStackTrace(); + } + + return doc; + } +} diff --git a/HIRS_Utils/src/main/resources/identity_transform.xslt b/HIRS_Utils/src/main/resources/identity_transform.xslt new file mode 100644 index 00000000..d249ca61 --- /dev/null +++ b/HIRS_Utils/src/main/resources/identity_transform.xslt @@ -0,0 +1,10 @@ + + + + + + + + + + From 302ffd81ee7ea263dd70c93f4270dc551a4e006c Mon Sep 17 00:00:00 2001 From: chubtub <43381989+chubtub@users.noreply.github.com> Date: Thu, 5 Nov 2020 11:06:47 -0500 Subject: [PATCH 2/4] Load Schema object in ReferenceManifestValidator class with controller class instantiation to save time --- ...eferenceManifestDetailsPageController.java | 24 +- .../utils/ReferenceManifestValidator.java | 206 +++++++++++------- 2 files changed, 146 insertions(+), 84 deletions(-) diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/ReferenceManifestDetailsPageController.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/ReferenceManifestDetailsPageController.java index 19bf4ec2..508df472 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/ReferenceManifestDetailsPageController.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/ReferenceManifestDetailsPageController.java @@ -44,13 +44,16 @@ public class ReferenceManifestDetailsPageController private final ReferenceManifestManager referenceManifestManager; private final CertificateManager certificateManager; + private static final ReferenceManifestValidator RIM_VALIDATOR + = new ReferenceManifestValidator(); private static final Logger LOGGER = LogManager.getLogger(ReferenceManifestDetailsPageController.class); /** * Constructor providing the Page's display and routing specification. * - * @param referenceManifestManager the reference manifest manager + * @param referenceManifestManager the reference manifest manager. + * @param certificateManager the certificate manager. */ @Autowired public ReferenceManifestDetailsPageController( @@ -116,6 +119,7 @@ public class ReferenceManifestDetailsPageController * * @param uuid database reference for the requested RIM. * @param referenceManifestManager the reference manifest manager. + * @param certificateManager the certificate manager. * @return mapping of the RIM information from the database. * @throws java.io.IOException error for reading file bytes. * @throws NoSuchAlgorithmException If an unknown Algorithm is encountered. @@ -193,17 +197,14 @@ public class ReferenceManifestDetailsPageController } } - ReferenceManifestValidator rimValidator = new ReferenceManifestValidator( - new ByteArrayInputStream(bRim.getRimBytes())); - // going to have to pull the filename and grab that from the DB // to get the id to make the link for (SwidResource swidRes : resources) { if (support != null && swidRes.getName() .equals(support.getFileName())) { - rimValidator.validateSupportRimHash(support.getRimBytes(), - rimValidator.getDocument()); - if (rimValidator.isSupportRimValid()) { + RIM_VALIDATOR.validateSupportRimHash(support.getRimBytes(), + swidRes.getHashValue()); + if (RIM_VALIDATOR.isSupportRimValid()) { data.put("supportRimHashValid", true); } else { data.put("supportRimHashValid", false); @@ -219,13 +220,14 @@ public class ReferenceManifestDetailsPageController data.put("associatedRim", bRim.getAssociatedRim()); data.put("swidFiles", resources); - rimValidator.validateXmlSignature(rimValidator.getDocument()); - data.put("signatureValid", rimValidator.isSignatureValid()); - if (rimValidator.isSignatureValid()) { + RIM_VALIDATOR.validateXmlSignature(new ByteArrayInputStream(bRim.getRimBytes())); + data.put("signatureValid", RIM_VALIDATOR.isSignatureValid()); + if (RIM_VALIDATOR.isSignatureValid()) { + LOGGER.info("Public key: " + RIM_VALIDATOR.getPublicKey().toString()); try { Certificate certificate = CertificateAuthorityCredential.select(certificateManager) - .byEncodedPublicKey(rimValidator.getPublicKey().getEncoded()) + .byEncodedPublicKey(RIM_VALIDATOR.getPublicKey().getEncoded()) .getCertificate(); data.put("issuerID", certificate.getId().toString()); } catch (NullPointerException e) { diff --git a/HIRS_Utils/src/main/java/hirs/utils/ReferenceManifestValidator.java b/HIRS_Utils/src/main/java/hirs/utils/ReferenceManifestValidator.java index 1980fb46..7cebb19e 100644 --- a/HIRS_Utils/src/main/java/hirs/utils/ReferenceManifestValidator.java +++ b/HIRS_Utils/src/main/java/hirs/utils/ReferenceManifestValidator.java @@ -3,7 +3,6 @@ package hirs.utils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.w3c.dom.Document; -import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; @@ -12,23 +11,30 @@ import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.UnmarshalException; import javax.xml.bind.Unmarshaller; -import javax.xml.crypto.*; +import javax.xml.crypto.AlgorithmMethod; +import javax.xml.crypto.KeySelector; +import javax.xml.crypto.KeySelectorException; +import javax.xml.crypto.KeySelectorResult; +import javax.xml.crypto.MarshalException; +import javax.xml.crypto.XMLCryptoContext; +import javax.xml.crypto.XMLStructure; 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.crypto.dsig.keyinfo.KeyInfo; import javax.xml.crypto.dsig.keyinfo.X509Data; -import javax.xml.transform.*; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMResult; import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import java.io.IOException; import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.nio.file.Files; -import java.nio.file.Paths; import java.security.Key; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -36,6 +42,12 @@ import java.security.PublicKey; import java.security.cert.X509Certificate; import java.util.Iterator; + +/** + * This class handles validation functions of RIM files. + * Currently supports validation of support RIM hashes and + * base RIM signatures. + */ public class ReferenceManifestValidator { private static final String SIGNATURE_ALGORITHM_RSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"; @@ -44,65 +56,96 @@ public class ReferenceManifestValidator { private static final String SCHEMA_LANGUAGE = XMLConstants.W3C_XML_SCHEMA_NS_URI; private static final String IDENTITY_TRANSFORM = "identity_transform.xslt"; private static final String SHA256 = "SHA-256"; + private static final int EIGHT_BIT_MASK = 0xff; + private static final int LEFT_SHIFT = 0x100; + private static final int RADIX = 16; private static final Logger LOGGER = LogManager.getLogger(ReferenceManifestValidator.class); private Unmarshaller unmarshaller; private PublicKey publicKey; - private Document document; + private Schema schema; private boolean signatureValid, supportRimValid; + /** + * Getter for signatureValid. + * + * @return true if valid, false if not. + */ public boolean isSignatureValid() { return signatureValid; } + /** + * Getter for supportRimValid. + * + * @return true if valid, false if not. + */ public boolean isSupportRimValid() { return supportRimValid; } - public PublicKey getPublicKey() { return publicKey; } + /** + * Getter for certificate PublicKey. + * + * @return PublicKey + */ + public PublicKey getPublicKey() { + return publicKey; + } - public Document getDocument() { return document; } - - public ReferenceManifestValidator() {} - - public ReferenceManifestValidator(InputStream input) { + /** + * This default constructor creates the Schema object from SCHEMA_URL immediately to save + * time during validation calls later. + */ + public ReferenceManifestValidator() { try { - JAXBContext jaxbContext = JAXBContext.newInstance(SCHEMA_PACKAGE); - unmarshaller = jaxbContext.createUnmarshaller(); + InputStream is = ReferenceManifestValidator.class + .getClassLoader().getResourceAsStream(SCHEMA_URL); + SchemaFactory schemaFactory = SchemaFactory.newInstance(SCHEMA_LANGUAGE); + schema = schemaFactory.newSchema(new StreamSource(is)); signatureValid = false; supportRimValid = false; publicKey = null; - document = unmarshallSwidTag(removeXMLWhitespace(new StreamSource(input))); - } catch (JAXBException e) { - LOGGER.warn("Error initializing JAXBContext: " + e.getMessage()); - } catch (IOException e) { - LOGGER.warn("Error during unmarshalling: " + e.getMessage()); - } - } - - public void validateSupportRimHash(byte[] input, Document doc) { - String calculatedHash = getHashValue(input, SHA256); - Element file = (Element) doc.getElementsByTagName("File").item(0); - LOGGER.info("Calculated hash: " + calculatedHash - + ", actual: " + file.getAttribute("SHA256:hash")); - if (file.getAttribute("SHA256:hash").equals(calculatedHash)) { - supportRimValid = true; - } else { - supportRimValid = false; + } catch (SAXException e) { + LOGGER.warn("Error setting schema for validation!"); } } /** - * This method validates the signature block in the Document and stores the - * result for public access. + * This method calculates the SHA256 hash of the input byte array and compares it against + * the value passed in. * - * @param doc the xml data in Document form. + * @param input byte array to hash. + * @param expected value to compare against. */ - public void validateXmlSignature(Document doc) throws IOException { - signatureValid = validateSignedXMLDocument(doc); + public void validateSupportRimHash(final byte[] input, final String expected) { + String calculatedHash = getHashValue(input, SHA256); + LOGGER.info("Calculated hash: " + calculatedHash + ", actual: " + expected); + supportRimValid = calculatedHash.equals(expected); } - private String getHashValue(byte[] input, String sha) { + /** + * This method validates the xml signature in the stream and stores the + * result for public access. + * + * @param input the xml data stream. + */ + public void validateXmlSignature(final InputStream input) { + try { + Document doc = unmarshallSwidTag(removeXMLWhitespace(new StreamSource(input))); + signatureValid = validateSignedXMLDocument(doc); + } catch (IOException e) { + LOGGER.warn("Error during unmarshal: " + e.getMessage()); + } + } + + /** + * This method calculates the digest of a byte array based on the hashing algorithm passed in. + * @param input byte array. + * @param sha hash algorithm. + * @return String digest. + */ + private String getHashValue(final byte[] input, final String sha) { String resultString = null; try { MessageDigest md = MessageDigest.getInstance(sha); @@ -110,7 +153,8 @@ public class ReferenceManifestValidator { StringBuilder sb = new StringBuilder(); for (int i = 0; i < bytes.length; i++) { - sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1)); + sb.append(Integer.toString((bytes[i] & EIGHT_BIT_MASK) + + LEFT_SHIFT, RADIX).substring(1)); } resultString = sb.toString(); } catch (NoSuchAlgorithmException grex) { @@ -125,7 +169,7 @@ public class ReferenceManifestValidator { * * @param doc */ - private boolean validateSignedXMLDocument(Document doc) { + private boolean validateSignedXMLDocument(final Document doc) { DOMValidateContext context; boolean isValid = false; try { @@ -154,15 +198,31 @@ public class ReferenceManifestValidator { return isValid; } + /** + * This internal class handles selecting an X509 certificate embedded in a KeyInfo element. + * It is passed as a parameter to a DOMValidateContext that uses it to validate + * an XML signature. + */ public class X509KeySelector extends KeySelector { private PublicKey publicKey; - public KeySelectorResult select(KeyInfo keyinfo, - KeySelector.Purpose purpose, - AlgorithmMethod algorithm, - XMLCryptoContext context) throws KeySelectorException { + /** + * This method selects an X509 cert based on the provided algorithm. + * + * @param keyinfo object containing the cert. + * @param purpose purpose. + * @param algorithm algorithm. + * @param context XMLCryptoContext. + * @return KeySelectorResult holding the PublicKey. + * @throws KeySelectorException exception. + */ + public KeySelectorResult select(final KeyInfo keyinfo, + final KeySelector.Purpose purpose, + final AlgorithmMethod algorithm, + final XMLCryptoContext context) + throws KeySelectorException { Iterator keyinfoItr = keyinfo.getContent().iterator(); - while(keyinfoItr.hasNext()) { + while (keyinfoItr.hasNext()) { XMLStructure element = (XMLStructure) keyinfoItr.next(); if (element instanceof X509Data) { X509Data data = (X509Data) element; @@ -171,8 +231,10 @@ public class ReferenceManifestValidator { Object object = dataItr.next(); if (object instanceof X509Certificate) { publicKey = ((X509Certificate) object).getPublicKey(); - if (areAlgorithmsEqual(algorithm.getAlgorithm(), publicKey.getAlgorithm())) { - return new ReferenceManifestValidator.X509KeySelector.RIMKeySelectorResult(publicKey); + if (areAlgorithmsEqual(algorithm.getAlgorithm(), + publicKey.getAlgorithm())) { + return new ReferenceManifestValidator.X509KeySelector + .RIMKeySelectorResult(publicKey); } } } @@ -182,22 +244,33 @@ public class ReferenceManifestValidator { throw new KeySelectorException("No key found!"); } - public boolean areAlgorithmsEqual(String uri, String name) { - if (uri.equals(SIGNATURE_ALGORITHM_RSA_SHA256) && name.equalsIgnoreCase("RSA")) { - return true; - } else { - return false; - } + /** + * This method checks if two strings refer to the same algorithm. + * + * @param uri string 1 + * @param name string 2 + * @return true if equal, false if not + */ + public boolean areAlgorithmsEqual(final String uri, final String name) { + return uri.equals(SIGNATURE_ALGORITHM_RSA_SHA256) && name.equalsIgnoreCase("RSA"); } + /** + * Getter for the public key that is parsed in the select() method above. + * + * @return PublicKey encoded in the X509 cert. + */ public PublicKey getPublicKey() { return publicKey; } + /** + * This internal class creates a KeySelectorResult from the public key. + */ private class RIMKeySelectorResult implements KeySelectorResult { private Key key; - public RIMKeySelectorResult(Key key) { + RIMKeySelectorResult(final Key key) { this.key = key; } @@ -208,37 +281,23 @@ public class ReferenceManifestValidator { } /** - * This method unmarshalls the swidtag found at [path] into a Document object - * and validates it according to the schema. + * This method unmarshalls the Document object and validates it against the schema. * * @param doc of the input swidtag. * @return document validated against the schema. */ - private Document unmarshallSwidTag(Document doc) { - InputStream is = null; + private Document unmarshallSwidTag(final Document doc) { try { - is = ReferenceManifestValidator.class - .getClassLoader().getResourceAsStream(SCHEMA_URL); - SchemaFactory schemaFactory = SchemaFactory.newInstance(SCHEMA_LANGUAGE); - Schema schema = schemaFactory.newSchema(new StreamSource(is)); + JAXBContext jaxbContext = JAXBContext.newInstance(SCHEMA_PACKAGE); + unmarshaller = jaxbContext.createUnmarshaller(); unmarshaller.setSchema(schema); unmarshaller.unmarshal(doc); - } catch (SAXException e) { - LOGGER.warn("Error setting schema for validation!"); } catch (UnmarshalException e) { LOGGER.warn("Error validating swidtag file!"); } catch (IllegalArgumentException e) { LOGGER.warn("Input file empty."); } catch (JAXBException e) { e.printStackTrace(); - } finally { - if (is != null) { - try { - is.close(); - } catch (IOException e) { - LOGGER.warn("Error closing input stream"); - } - } } return doc; @@ -247,10 +306,11 @@ public class ReferenceManifestValidator { /** * This method strips all whitespace from an xml file, including indents and spaces * added for human-readability. + * * @param source of the input xml. * @return Document representation of the xml. */ - private Document removeXMLWhitespace(StreamSource source) throws IOException { + private Document removeXMLWhitespace(final StreamSource source) throws IOException { TransformerFactory tf = TransformerFactory.newInstance(); Source identitySource = new StreamSource( ReferenceManifestValidator.class.getClassLoader() From d096aebe1282e35a33f50a344e36086c34871583 Mon Sep 17 00:00:00 2001 From: chubtub <43381989+chubtub@users.noreply.github.com> Date: Thu, 5 Nov 2020 11:27:41 -0500 Subject: [PATCH 3/4] Add support RIM and signature validation checks to SupplyChainValidationServiceImpl class --- .../SupplyChainValidationServiceImpl.java | 190 +++++++++++------- 1 file changed, 113 insertions(+), 77 deletions(-) diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/service/SupplyChainValidationServiceImpl.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/service/SupplyChainValidationServiceImpl.java index 33769908..763e0134 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/service/SupplyChainValidationServiceImpl.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/service/SupplyChainValidationServiceImpl.java @@ -1,5 +1,6 @@ package hirs.attestationca.service; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.security.KeyStore; import java.security.KeyStoreException; @@ -9,11 +10,13 @@ import java.security.cert.CertificateException; import hirs.data.persist.BaseReferenceManifest; import hirs.data.persist.EventLogMeasurements; import hirs.data.persist.SupportReferenceManifest; +import hirs.data.persist.SwidResource; import hirs.data.persist.TPMMeasurementRecord; import hirs.data.persist.PCRPolicy; import hirs.data.persist.ArchivableEntity; import hirs.tpm.eventlog.TCGEventLog; import hirs.tpm.eventlog.TpmPcrEvent; +import hirs.utils.ReferenceManifestValidator; import hirs.validation.SupplyChainCredentialValidator; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -340,6 +343,8 @@ public class SupplyChainValidationServiceImpl implements SupplyChainValidationSe .byManufacturer(manufacturer).getRIM(); supportReferenceManifest = SupportReferenceManifest.select(referenceManifestManager) .byManufacturer(manufacturer).getRIM(); + List resources = + ((BaseReferenceManifest) baseReferenceManifest).parseResource(); measurement = EventLogMeasurements.select(referenceManifestManager) .byManufacturer(manufacturer).includeArchived().getRIM(); @@ -360,97 +365,128 @@ public class SupplyChainValidationServiceImpl implements SupplyChainValidationSe if (passed) { fwStatus = new AppraisalStatus(PASS, SupplyChainCredentialValidator.FIRMWARE_VALID); - TCGEventLog logProcessor; - try { - logProcessor = new TCGEventLog(supportReferenceManifest.getRimBytes()); - baseline = logProcessor.getExpectedPCRValues(); - } catch (CertificateException cEx) { - LOGGER.error(cEx); - } catch (NoSuchAlgorithmException noSaEx) { - LOGGER.error(noSaEx); - } catch (IOException ioEx) { - LOGGER.error(ioEx); + + // verify signatures + ReferenceManifestValidator referenceManifestValidator = + new ReferenceManifestValidator(); + + for (SwidResource swidRes : resources) { + if (swidRes.getName().equals(supportReferenceManifest.getFileName())) { + referenceManifestValidator.validateSupportRimHash( + supportReferenceManifest.getRimBytes(), swidRes.getHashValue()); + } } - // part 1 of firmware validation check: PCR baseline match - pcrPolicy.setBaselinePcrs(baseline); + referenceManifestValidator.validateXmlSignature( + new ByteArrayInputStream(baseReferenceManifest.getRimBytes())); - if (baseline.length > 0) { - String pcrContent = ""; - pcrContent = new String(device.getDeviceInfo().getTPMInfo().getPcrValues()); + if (!referenceManifestValidator.isSignatureValid()) { + passed = false; + fwStatus = new AppraisalStatus(FAIL, + "Firmware validation failed: Signature validation " + + "failed for Base RIM."); + } - if (pcrContent.isEmpty()) { - fwStatus = new AppraisalStatus(FAIL, - "Firmware validation failed: Client did not " - + "provide pcr values."); - LOGGER.warn(String.format( - "Firmware validation failed: Client (%s) did not " - + "provide pcr values.", device.getName())); - } else { - // we have a full set of PCR values - int algorithmLength = baseline[0].length(); - String[] storedPcrs = buildStoredPcrs(pcrContent, algorithmLength); + if (passed && !referenceManifestValidator.isSupportRimValid()) { + passed = false; + fwStatus = new AppraisalStatus(FAIL, + "Firmware validation failed: Hash validation " + + "failed for Support RIM."); + } - if (storedPcrs[0].isEmpty()) { - // validation fail + if (passed) { + TCGEventLog logProcessor; + try { + logProcessor = new TCGEventLog(supportReferenceManifest.getRimBytes()); + baseline = logProcessor.getExpectedPCRValues(); + } catch (CertificateException cEx) { + LOGGER.error(cEx); + } catch (NoSuchAlgorithmException noSaEx) { + LOGGER.error(noSaEx); + } catch (IOException ioEx) { + LOGGER.error(ioEx); + } + + // part 1 of firmware validation check: PCR baseline match + pcrPolicy.setBaselinePcrs(baseline); + + if (baseline.length > 0) { + String pcrContent = ""; + pcrContent = new String(device.getDeviceInfo().getTPMInfo().getPcrValues()); + + if (pcrContent.isEmpty()) { fwStatus = new AppraisalStatus(FAIL, - "Firmware validation failed: " - + "Client provided PCR " - + "values are not the same algorithm " - + "as associated RIM."); + "Firmware validation failed: Client did not " + + "provide pcr values."); + LOGGER.warn(String.format( + "Firmware validation failed: Client (%s) did not " + + "provide pcr values.", device.getName())); } else { - StringBuilder sb = pcrPolicy.validatePcrs(storedPcrs); - if (sb.length() > 0) { - level = Level.ERROR; - fwStatus = new AppraisalStatus(FAIL, sb.toString()); + // we have a full set of PCR values + int algorithmLength = baseline[0].length(); + String[] storedPcrs = buildStoredPcrs(pcrContent, algorithmLength); + + if (storedPcrs[0].isEmpty()) { + // validation fail + fwStatus = new AppraisalStatus(FAIL, + "Firmware validation failed: " + + "Client provided PCR " + + "values are not the same algorithm " + + "as associated RIM."); } else { - level = Level.INFO; - } - } - // part 2 of firmware validation check: bios measurements - // vs baseline tcg event log - // find the measurement - TCGEventLog tcgEventLog; - TCGEventLog tcgMeasurementLog; - LinkedList tpmPcrEvents = new LinkedList<>(); - try { - if (measurement.getPlatformManufacturer().equals(manufacturer)) { - tcgMeasurementLog = new TCGEventLog(measurement.getRimBytes()); - tcgEventLog = new TCGEventLog( - supportReferenceManifest.getRimBytes()); - for (TpmPcrEvent tpe : tcgEventLog.getEventList()) { - if (!tpe.eventCompare( - tcgMeasurementLog.getEventByNumber( - tpe.getEventNumber()))) { - tpmPcrEvents.add(tpe); - } + StringBuilder sb = pcrPolicy.validatePcrs(storedPcrs); + if (sb.length() > 0) { + level = Level.ERROR; + fwStatus = new AppraisalStatus(FAIL, sb.toString()); + } else { + level = Level.INFO; } } - } catch (CertificateException cEx) { - LOGGER.error(cEx); - } catch (NoSuchAlgorithmException noSaEx) { - LOGGER.error(noSaEx); - } catch (IOException ioEx) { - LOGGER.error(ioEx); - } + // part 2 of firmware validation check: bios measurements + // vs baseline tcg event log + // find the measurement + TCGEventLog tcgEventLog; + TCGEventLog tcgMeasurementLog; + LinkedList tpmPcrEvents = new LinkedList<>(); + try { + if (measurement.getPlatformManufacturer().equals(manufacturer)) { + tcgMeasurementLog = new TCGEventLog(measurement.getRimBytes()); + tcgEventLog = new TCGEventLog( + supportReferenceManifest.getRimBytes()); + for (TpmPcrEvent tpe : tcgEventLog.getEventList()) { + if (!tpe.eventCompare( + tcgMeasurementLog.getEventByNumber( + tpe.getEventNumber()))) { + tpmPcrEvents.add(tpe); + } + } + } + } catch (CertificateException cEx) { + LOGGER.error(cEx); + } catch (NoSuchAlgorithmException noSaEx) { + LOGGER.error(noSaEx); + } catch (IOException ioEx) { + LOGGER.error(ioEx); + } - if (!tpmPcrEvents.isEmpty()) { - StringBuilder sb = new StringBuilder(); - for (TpmPcrEvent tpe : tpmPcrEvents) { - sb.append(String.format("Event %s - %s%n", - tpe.getEventNumber(), - tpe.getEventTypeStr())); - } - if (fwStatus.getAppStatus().equals(FAIL)) { - fwStatus = new AppraisalStatus(FAIL, String.format("%s%n%s", - fwStatus.getMessage(), sb.toString())); - } else { - fwStatus = new AppraisalStatus(FAIL, sb.toString()); + if (!tpmPcrEvents.isEmpty()) { + StringBuilder sb = new StringBuilder(); + for (TpmPcrEvent tpe : tpmPcrEvents) { + sb.append(String.format("Event %s - %s%n", + tpe.getEventNumber(), + tpe.getEventTypeStr())); + } + if (fwStatus.getAppStatus().equals(FAIL)) { + fwStatus = new AppraisalStatus(FAIL, String.format("%s%n%s", + fwStatus.getMessage(), sb.toString())); + } else { + fwStatus = new AppraisalStatus(FAIL, sb.toString()); + } } } + } else { + fwStatus = new AppraisalStatus(FAIL, "The RIM baseline could not be found."); } - } else { - fwStatus = new AppraisalStatus(FAIL, "The RIM baseline could not be found."); } } else { fwStatus = new AppraisalStatus(FAIL, String.format("Firmware Validation failed: " From 623da2ce8005f07c437a756bd44af7cd0dd539e9 Mon Sep 17 00:00:00 2001 From: chubtub <43381989+chubtub@users.noreply.github.com> Date: Thu, 5 Nov 2020 13:44:09 -0500 Subject: [PATCH 4/4] Overload RIM validator class for faster signature checking --- .../SupplyChainValidationServiceImpl.java | 6 ++--- .../utils/ReferenceManifestValidator.java | 25 +++++++++++++++---- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/service/SupplyChainValidationServiceImpl.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/service/SupplyChainValidationServiceImpl.java index 763e0134..8771f938 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/service/SupplyChainValidationServiceImpl.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/service/SupplyChainValidationServiceImpl.java @@ -368,7 +368,8 @@ public class SupplyChainValidationServiceImpl implements SupplyChainValidationSe // verify signatures ReferenceManifestValidator referenceManifestValidator = - new ReferenceManifestValidator(); + new ReferenceManifestValidator( + new ByteArrayInputStream(baseReferenceManifest.getRimBytes())); for (SwidResource swidRes : resources) { if (swidRes.getName().equals(supportReferenceManifest.getFileName())) { @@ -377,9 +378,6 @@ public class SupplyChainValidationServiceImpl implements SupplyChainValidationSe } } - referenceManifestValidator.validateXmlSignature( - new ByteArrayInputStream(baseReferenceManifest.getRimBytes())); - if (!referenceManifestValidator.isSignatureValid()) { passed = false; fwStatus = new AppraisalStatus(FAIL, diff --git a/HIRS_Utils/src/main/java/hirs/utils/ReferenceManifestValidator.java b/HIRS_Utils/src/main/java/hirs/utils/ReferenceManifestValidator.java index 7cebb19e..d8963592 100644 --- a/HIRS_Utils/src/main/java/hirs/utils/ReferenceManifestValidator.java +++ b/HIRS_Utils/src/main/java/hirs/utils/ReferenceManifestValidator.java @@ -111,6 +111,21 @@ public class ReferenceManifestValidator { } } + /** + * This constructor is used for a quick signature check which bypasses the loading of + * the schema url into memory. As a result the full stream is not validated against the schema. + * + * @param input xml data byte array. + */ + public ReferenceManifestValidator(final InputStream input) { + try { + signatureValid = validateSignedXMLDocument( + removeXMLWhitespace(new StreamSource(input))); + } catch (IOException e) { + LOGGER.warn("Error during unmarshal: " + e.getMessage()); + } + } + /** * This method calculates the SHA256 hash of the input byte array and compares it against * the value passed in. @@ -132,7 +147,7 @@ public class ReferenceManifestValidator { */ public void validateXmlSignature(final InputStream input) { try { - Document doc = unmarshallSwidTag(removeXMLWhitespace(new StreamSource(input))); + Document doc = validateSwidtagSchema(removeXMLWhitespace(new StreamSource(input))); signatureValid = validateSignedXMLDocument(doc); } catch (IOException e) { LOGGER.warn("Error during unmarshal: " + e.getMessage()); @@ -203,7 +218,7 @@ public class ReferenceManifestValidator { * It is passed as a parameter to a DOMValidateContext that uses it to validate * an XML signature. */ - public class X509KeySelector extends KeySelector { + public static class X509KeySelector extends KeySelector { private PublicKey publicKey; /** @@ -267,7 +282,7 @@ public class ReferenceManifestValidator { /** * This internal class creates a KeySelectorResult from the public key. */ - private class RIMKeySelectorResult implements KeySelectorResult { + private static class RIMKeySelectorResult implements KeySelectorResult { private Key key; RIMKeySelectorResult(final Key key) { @@ -281,12 +296,12 @@ public class ReferenceManifestValidator { } /** - * This method unmarshalls the Document object and validates it against the schema. + * This method validates the Document against the schema. * * @param doc of the input swidtag. * @return document validated against the schema. */ - private Document unmarshallSwidTag(final Document doc) { + private Document validateSwidtagSchema(final Document doc) { try { JAXBContext jaxbContext = JAXBContext.newInstance(SCHEMA_PACKAGE); unmarshaller = jaxbContext.createUnmarshaller();