mirror of
https://github.com/nsacyber/HIRS.git
synced 2025-02-06 11:10:15 +00:00
Add cert path validation to SwidTagValidator class. Modify CredentialParser class to support changes.
This commit is contained in:
parent
495b2c2aa3
commit
6acfb13ce8
@ -19,6 +19,7 @@ import java.security.cert.CertificateFactory;
|
|||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.security.spec.InvalidKeySpecException;
|
import java.security.spec.InvalidKeySpecException;
|
||||||
import java.security.spec.PKCS8EncodedKeySpec;
|
import java.security.spec.PKCS8EncodedKeySpec;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class parses private key, public key, and certificate for use in their respective java.security objects.
|
* This class parses private key, public key, and certificate for use in their respective java.security objects.
|
||||||
@ -31,6 +32,8 @@ public class CredentialParser {
|
|||||||
private static final String PKCS1_FOOTER = "-----END RSA PRIVATE KEY-----";
|
private static final String PKCS1_FOOTER = "-----END RSA PRIVATE KEY-----";
|
||||||
private static final String PKCS8_HEADER = "-----BEGIN PRIVATE KEY-----";
|
private static final String PKCS8_HEADER = "-----BEGIN PRIVATE KEY-----";
|
||||||
private static final String PKCS8_FOOTER = "-----END PRIVATE KEY-----";
|
private static final String PKCS8_FOOTER = "-----END PRIVATE KEY-----";
|
||||||
|
private static final String CERTIFICATE_HEADER = "-----BEGIN CERTIFICATE-----";
|
||||||
|
private static final String CERTIFICATE_FOOTER = "-----END CERTIFICATE-----";
|
||||||
private X509Certificate certificate;
|
private X509Certificate certificate;
|
||||||
private PrivateKey privateKey;
|
private PrivateKey privateKey;
|
||||||
private PublicKey publicKey;
|
private PublicKey publicKey;
|
||||||
@ -61,8 +64,9 @@ public class CredentialParser {
|
|||||||
publicKey = certificate.getPublicKey();
|
publicKey = certificate.getPublicKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void parsePEMCredentials(String certificateFile, String privateKeyFile) throws CertificateException, FileNotFoundException {
|
public void parsePEMCredentials(String certificateFile, String privateKeyFile)
|
||||||
certificate = parsePEMCertificate(certificateFile);
|
throws CertificateException, FileNotFoundException {
|
||||||
|
certificate = parsePEMCertificates(certificateFile).get(0);
|
||||||
if (certificate.getIssuerX500Principal().equals(certificate.getSubjectX500Principal())) {
|
if (certificate.getIssuerX500Principal().equals(certificate.getSubjectX500Principal())) {
|
||||||
throw new CertificateException("Signing certificate cannot be self-signed!");
|
throw new CertificateException("Signing certificate cannot be self-signed!");
|
||||||
}
|
}
|
||||||
@ -70,24 +74,44 @@ public class CredentialParser {
|
|||||||
publicKey = certificate.getPublicKey();
|
publicKey = certificate.getPublicKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method extracts certificate bytes from a string. The bytes are assumed to be
|
||||||
|
* PEM format, and a header and footer are concatenated with the input string to
|
||||||
|
* facilitate proper parsing.
|
||||||
|
* @param pemString the input string
|
||||||
|
* @return an X509Certificate created from the string
|
||||||
|
* @throws CertificateException if instantiating the CertificateFactory errors
|
||||||
|
*/
|
||||||
|
public X509Certificate parseCertFromPEMString(String pemString) throws CertificateException {
|
||||||
|
try {
|
||||||
|
CertificateFactory factory = CertificateFactory.getInstance(X509);
|
||||||
|
InputStream inputStream = new ByteArrayInputStream((CERTIFICATE_HEADER
|
||||||
|
+ pemString
|
||||||
|
+ CERTIFICATE_FOOTER).getBytes());
|
||||||
|
return (X509Certificate) factory.generateCertificate(inputStream);
|
||||||
|
} catch (CertificateException e) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method returns the X509Certificate object from a PEM certificate file.
|
* This method returns the X509Certificate object from a PEM certificate file.
|
||||||
* @param certificateFile
|
* @param certificateFile
|
||||||
* @return
|
* @return
|
||||||
* @throws FileNotFoundException
|
* @throws FileNotFoundException
|
||||||
*/
|
*/
|
||||||
public X509Certificate parseCertFromPEM(String certificateFile) throws FileNotFoundException {
|
public List<X509Certificate> parseCertsFromPEM(String certificateFile)
|
||||||
return parsePEMCertificate(certificateFile);
|
throws FileNotFoundException {
|
||||||
|
return parsePEMCertificates(certificateFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method returns the X509Certificate found in a PEM file.
|
* This method returns the X509Certificate found in a PEM file.
|
||||||
* @param filename
|
* @param filename pem file
|
||||||
* @return
|
* @return a list containing all X509Certificates extracted
|
||||||
* @throws FileNotFoundException
|
|
||||||
*/
|
*/
|
||||||
private X509Certificate parsePEMCertificate(String filename) throws FileNotFoundException {
|
private List<X509Certificate> parsePEMCertificates(String filename) {
|
||||||
X509Certificate certificate = null;
|
List<X509Certificate> certificates = null;
|
||||||
FileInputStream fis = null;
|
FileInputStream fis = null;
|
||||||
BufferedInputStream bis = null;
|
BufferedInputStream bis = null;
|
||||||
try {
|
try {
|
||||||
@ -96,9 +120,12 @@ public class CredentialParser {
|
|||||||
CertificateFactory certificateFactory = CertificateFactory.getInstance(X509);
|
CertificateFactory certificateFactory = CertificateFactory.getInstance(X509);
|
||||||
|
|
||||||
while (bis.available() > 0) {
|
while (bis.available() > 0) {
|
||||||
certificate = (X509Certificate) certificateFactory.generateCertificate(bis);
|
certificates = (List<X509Certificate>) certificateFactory.generateCertificates(bis);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (certificates.size() < 1) {
|
||||||
|
System.out.println("ERROR: No certificates parsed from " + filename);
|
||||||
|
}
|
||||||
bis.close();
|
bis.close();
|
||||||
} catch (CertificateException e) {
|
} catch (CertificateException e) {
|
||||||
System.out.println("Error in certificate factory: " + e.getMessage());
|
System.out.println("Error in certificate factory: " + e.getMessage());
|
||||||
@ -117,7 +144,7 @@ public class CredentialParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return certificate;
|
return certificates;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3,7 +3,6 @@ package hirs.swid;
|
|||||||
import hirs.swid.utils.Commander;
|
import hirs.swid.utils.Commander;
|
||||||
import com.beust.jcommander.JCommander;
|
import com.beust.jcommander.JCommander;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
public class Main {
|
public class Main {
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
package hirs.swid;
|
package hirs.swid;
|
||||||
|
|
||||||
import hirs.swid.utils.HashSwid;
|
import hirs.swid.utils.HashSwid;
|
||||||
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
import org.w3c.dom.Element;
|
import org.w3c.dom.Element;
|
||||||
import org.w3c.dom.NodeList;
|
import org.w3c.dom.NodeList;
|
||||||
import org.xml.sax.SAXException;
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
|
import javax.security.auth.x500.X500Principal;
|
||||||
import javax.xml.bind.JAXBContext;
|
import javax.xml.bind.JAXBContext;
|
||||||
import javax.xml.bind.JAXBException;
|
import javax.xml.bind.JAXBException;
|
||||||
import javax.xml.bind.UnmarshalException;
|
import javax.xml.bind.UnmarshalException;
|
||||||
@ -35,10 +37,16 @@ import javax.xml.validation.SchemaFactory;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.NoSuchProviderException;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
|
import java.security.SignatureException;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class handles validating base Reference Integrity Manifest files.
|
* This class handles validating base Reference Integrity Manifest files.
|
||||||
@ -47,25 +55,39 @@ public class SwidTagValidator {
|
|||||||
private Unmarshaller unmarshaller;
|
private Unmarshaller unmarshaller;
|
||||||
private String rimEventLog;
|
private String rimEventLog;
|
||||||
private String certificateFile;
|
private String certificateFile;
|
||||||
|
private String trustStore;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setter for rimel file path.
|
* Setter for rimel file path.
|
||||||
* @param rimEventLog
|
* @param rimEventLog the rimel file
|
||||||
*/
|
*/
|
||||||
public void setRimEventLog(String rimEventLog) {
|
public void setRimEventLog(String rimEventLog) {
|
||||||
this.rimEventLog = rimEventLog;
|
this.rimEventLog = rimEventLog;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setter for signing cert file.
|
||||||
|
* @param certificateFile the signing cert
|
||||||
|
*/
|
||||||
public void setCertificateFile(String certificateFile) {
|
public void setCertificateFile(String certificateFile) {
|
||||||
this.certificateFile = certificateFile;
|
this.certificateFile = certificateFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setter for the truststore file path.
|
||||||
|
* @param trustStore the truststore
|
||||||
|
*/
|
||||||
|
public void setTrustStore(String trustStore) {
|
||||||
|
this.trustStore = trustStore;
|
||||||
|
}
|
||||||
|
|
||||||
public SwidTagValidator() {
|
public SwidTagValidator() {
|
||||||
try {
|
try {
|
||||||
JAXBContext jaxbContext = JAXBContext.newInstance(SwidTagConstants.SCHEMA_PACKAGE);
|
JAXBContext jaxbContext = JAXBContext.newInstance(SwidTagConstants.SCHEMA_PACKAGE);
|
||||||
unmarshaller = jaxbContext.createUnmarshaller();
|
unmarshaller = jaxbContext.createUnmarshaller();
|
||||||
rimEventLog = "";
|
rimEventLog = "";
|
||||||
certificateFile = "";
|
certificateFile = "";
|
||||||
|
trustStore = SwidTagConstants.DEFAULT_KEYSTORE_FILE;
|
||||||
} catch (JAXBException e) {
|
} catch (JAXBException e) {
|
||||||
System.out.println("Error initializing JAXBContext: " + e.getMessage());
|
System.out.println("Error initializing JAXBContext: " + e.getMessage());
|
||||||
}
|
}
|
||||||
@ -119,6 +141,8 @@ public class SwidTagValidator {
|
|||||||
*/
|
*/
|
||||||
private boolean validateSignedXMLDocument(Document doc) {
|
private boolean validateSignedXMLDocument(Document doc) {
|
||||||
DOMValidateContext context = null;
|
DOMValidateContext context = null;
|
||||||
|
CredentialParser cp = new CredentialParser();
|
||||||
|
X509Certificate signingCert = null;
|
||||||
boolean isValid = false;
|
boolean isValid = false;
|
||||||
try {
|
try {
|
||||||
NodeList nodes = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
|
NodeList nodes = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
|
||||||
@ -127,14 +151,14 @@ public class SwidTagValidator {
|
|||||||
}
|
}
|
||||||
NodeList embeddedCert = doc.getElementsByTagName("X509Data");
|
NodeList embeddedCert = doc.getElementsByTagName("X509Data");
|
||||||
if (embeddedCert.getLength() > 0) {
|
if (embeddedCert.getLength() > 0) {
|
||||||
context = new DOMValidateContext(new SwidTagValidator.X509KeySelector(), nodes.item(0));
|
context = new DOMValidateContext(new X509KeySelector(), nodes.item(0));
|
||||||
|
signingCert = cp.parseCertFromPEMString(embeddedCert.item(1).getTextContent());
|
||||||
} else {
|
} else {
|
||||||
CredentialParser cp = new CredentialParser();
|
|
||||||
if (!certificateFile.isEmpty()) {
|
if (!certificateFile.isEmpty()) {
|
||||||
X509Certificate certificate = cp.parseCertFromPEM(certificateFile);
|
signingCert = cp.parseCertsFromPEM(certificateFile).get(0);
|
||||||
cp.setCertificate(certificate);
|
cp.setCertificate(signingCert);
|
||||||
System.out.println(cp.getCertificateAuthorityInfoAccess());
|
System.out.println(cp.getCertificateAuthorityInfoAccess());
|
||||||
context = new DOMValidateContext(certificate.getPublicKey(), nodes.item(0));
|
context = new DOMValidateContext(signingCert.getPublicKey(), nodes.item(0));
|
||||||
} else {
|
} else {
|
||||||
System.out.println("Signing certificate not found for validation!");
|
System.out.println("Signing certificate not found for validation!");
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
@ -142,7 +166,8 @@ public class SwidTagValidator {
|
|||||||
}
|
}
|
||||||
XMLSignatureFactory sigFactory = XMLSignatureFactory.getInstance("DOM");
|
XMLSignatureFactory sigFactory = XMLSignatureFactory.getInstance("DOM");
|
||||||
XMLSignature signature = sigFactory.unmarshalXMLSignature(context);
|
XMLSignature signature = sigFactory.unmarshalXMLSignature(context);
|
||||||
isValid = signature.validate(context);
|
isValid = signature.validate(context) && validateCertChain(signingCert,
|
||||||
|
cp.parseCertsFromPEM(trustStore));
|
||||||
} catch (MarshalException | XMLSignatureException e) {
|
} catch (MarshalException | XMLSignatureException e) {
|
||||||
System.out.println(e.getMessage());
|
System.out.println(e.getMessage());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@ -152,6 +177,105 @@ public class SwidTagValidator {
|
|||||||
return isValid;
|
return isValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method validates the cert chain for a given certificate. The truststore is iterated
|
||||||
|
* over until a root CA is found. If a root CA is not found an error is returned describing
|
||||||
|
* the problem with validation.
|
||||||
|
* @param cert the certificate at the start of the chain
|
||||||
|
* @param trustStore the collection from which to find the chain of intermediate and root CAs
|
||||||
|
* @return true if the chain is valid; the false case throws the exception below
|
||||||
|
* @throws Exception if a valid chain is not found in the truststore
|
||||||
|
*/
|
||||||
|
private boolean validateCertChain(final X509Certificate cert,
|
||||||
|
final List<X509Certificate> trustStore)
|
||||||
|
throws Exception {
|
||||||
|
if (cert == null || trustStore == null) {
|
||||||
|
throw new Exception("Null certificate or truststore received");
|
||||||
|
} else if (trustStore.size() == 0) {
|
||||||
|
throw new Exception("Truststore is empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
final String INT_CA_ERROR = "Intermediate CA found, searching for root CA";
|
||||||
|
String errorMessage = "";
|
||||||
|
X509Certificate startOfChain = cert;
|
||||||
|
do {
|
||||||
|
for (X509Certificate trustedCert : trustStore) {
|
||||||
|
boolean isIssuer = areYouMyIssuer(startOfChain, trustedCert);
|
||||||
|
boolean isSigner = areYouMySigner(startOfChain, trustedCert);
|
||||||
|
if (isIssuer && isSigner) {
|
||||||
|
if (isSelfSigned(trustedCert)) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
startOfChain = trustedCert;
|
||||||
|
errorMessage = INT_CA_ERROR;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!isIssuer) {
|
||||||
|
errorMessage = "Issuer cert not found";
|
||||||
|
} else if (!isSigner) {
|
||||||
|
errorMessage = "Signing cert not found";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (errorMessage.equals(INT_CA_ERROR));
|
||||||
|
|
||||||
|
throw new Exception(errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method checks if cert's issuerDN matches issuer's subjectDN.
|
||||||
|
* @param cert the signed certificate
|
||||||
|
* @param issuer the signing certificate
|
||||||
|
* @return true if they match, false if not
|
||||||
|
* @throws Exception if either argument is null
|
||||||
|
*/
|
||||||
|
private boolean areYouMyIssuer(final X509Certificate cert, final X509Certificate issuer)
|
||||||
|
throws Exception {
|
||||||
|
if (cert == null || issuer == null) {
|
||||||
|
throw new Exception("Cannot verify issuer, null certificate received");
|
||||||
|
}
|
||||||
|
X500Principal issuerDN = new X500Principal(cert.getIssuerX500Principal().getName());
|
||||||
|
return issuer.getSubjectX500Principal().equals(issuerDN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method checks if cert's signature matches signer's public key.
|
||||||
|
* @param cert the signed certificate
|
||||||
|
* @param signer the signing certificate
|
||||||
|
* @return true if they match
|
||||||
|
* @throws Exception if an error occurs or there is no match
|
||||||
|
*/
|
||||||
|
private boolean areYouMySigner(final X509Certificate cert, final X509Certificate signer)
|
||||||
|
throws Exception {
|
||||||
|
if (cert == null || signer == null) {
|
||||||
|
throw new Exception("Cannot verify signature, null certificate received");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
cert.verify(signer.getPublicKey(), BouncyCastleProvider.PROVIDER_NAME);
|
||||||
|
return true;
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new Exception("Signing algorithm in signing cert not supported");
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
throw new Exception("Signing certificate key does not match signature");
|
||||||
|
} catch (NoSuchProviderException e) {
|
||||||
|
throw new Exception("Error with BouncyCastleProvider: " + e.getMessage());
|
||||||
|
} catch (SignatureException e) {
|
||||||
|
throw new Exception("Error with signature: " + e.getMessage());
|
||||||
|
} catch (CertificateException e) {
|
||||||
|
throw new Exception("Encoding error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method checks if a given certificate is self signed or not.
|
||||||
|
* @param cert the cert to check
|
||||||
|
* @return true if self signed, false if not
|
||||||
|
*/
|
||||||
|
private boolean isSelfSigned(final X509Certificate cert) {
|
||||||
|
return cert.getIssuerX500Principal().equals(cert.getSubjectX500Principal());
|
||||||
|
}
|
||||||
|
|
||||||
public class X509KeySelector extends KeySelector {
|
public class X509KeySelector extends KeySelector {
|
||||||
public KeySelectorResult select(KeyInfo keyinfo,
|
public KeySelectorResult select(KeyInfo keyinfo,
|
||||||
KeySelector.Purpose purpose,
|
KeySelector.Purpose purpose,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user