Moved KeyValue processing to KeySelector inner class

This commit is contained in:
chubtub 2022-04-14 11:04:57 -04:00
parent e5e6db75f4
commit 4b3c01f990

View File

@ -39,11 +39,9 @@ import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.math.BigInteger;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.Key; import java.security.Key;
import java.security.KeyException; import java.security.KeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException; import java.security.NoSuchProviderException;
import java.security.PublicKey; import java.security.PublicKey;
@ -52,9 +50,7 @@ import java.security.SignatureException;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException; import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPublicKeySpec;
import java.util.Arrays; import java.util.Arrays;
import java.util.Base64;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -66,6 +62,7 @@ public class SwidTagValidator {
private String rimEventLog; private String rimEventLog;
private String certificateFile; private String certificateFile;
private String trustStoreFile; private String trustStoreFile;
private List<X509Certificate> trustStore;
/** /**
* Ensure that BouncyCastle is configured as a javax.security.Security provider, as this * Ensure that BouncyCastle is configured as a javax.security.Security provider, as this
@ -159,7 +156,7 @@ public class SwidTagValidator {
DOMValidateContext context; DOMValidateContext context;
CredentialParser cp = new CredentialParser(); CredentialParser cp = new CredentialParser();
X509Certificate signingCert = null; X509Certificate signingCert = null;
List<X509Certificate> trustStore = cp.parseCertsFromPEM(trustStoreFile); trustStore = cp.parseCertsFromPEM(trustStoreFile);
X509KeySelector keySelector = new X509KeySelector(); X509KeySelector keySelector = new X509KeySelector();
NodeList nodes = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature"); NodeList nodes = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
if (nodes.getLength() == 0) { if (nodes.getLength() == 0) {
@ -167,59 +164,41 @@ public class SwidTagValidator {
} else { } else {
context = new DOMValidateContext(keySelector, nodes.item(0)); context = new DOMValidateContext(keySelector, nodes.item(0));
} }
NodeList embeddedCert = doc.getElementsByTagName("X509Certificate"); NodeList keyName = doc.getElementsByTagName("KeyName");
if (embeddedCert.getLength() > 0) { if (keyName.getLength() > 0) {
signingCert = cp.parseCertFromPEMString(embeddedCert.item(0).getTextContent()); String skId = keyName.item(0).getTextContent();
} else { if (skId != null && !skId.isEmpty()) {
NodeList keyValue = doc.getElementsByTagName("KeyValue");
if (keyValue.getLength() > 0) {
String modulus = doc.getElementsByTagName("Modulus").item(0).getTextContent();
String exponent =
doc.getElementsByTagName("Exponent").item(0).getTextContent();
PublicKey signingKey = calculatePublicKey(modulus, exponent);
for (X509Certificate trustedCert : trustStore) { for (X509Certificate trustedCert : trustStore) {
System.out.println(trustedCert.getPublicKey().toString()); String trustedSkId = cp.getCertificateSubjectKeyIdentifier(trustedCert);
if (Arrays.equals(trustedCert.getPublicKey().getEncoded(), if (skId.equals(trustedSkId)) {
signingKey.getEncoded())) {
signingCert = trustedCert; signingCert = trustedCert;
break; break;
} }
} }
if (signingCert == null) { if (signingCert != null) {
System.out.println("Calculated public key not found: " + signingKey.toString()); context = new DOMValidateContext(signingCert.getPublicKey(),
nodes.item(0));
} else {
System.out.println("Issuer certificate with subject key identifier = "
+ skId + " not found");
System.exit(1); System.exit(1);
} }
} else { } else {
String skId = doc.getElementsByTagName("KeyName").item(0).getTextContent(); System.out.println("Base RIM must have a non-empty, non-null " +
if (skId != null && !skId.isEmpty()) { "Subject Key Identifier (SKID) in the <KeyName> element");
for (X509Certificate trustedCert : trustStore) { System.exit(1);
String trustedSkId = cp.getCertificateSubjectKeyIdentifier(trustedCert);
if (skId.equals(trustedSkId)) {
signingCert = trustedCert;
break;
}
}
if (signingCert == null) {
System.out.println("Issuer certificate with subject key identifier = "
+ skId + " not found");
System.exit(1);
}
} else {
System.out.println("No credentials found with which to validate this swidtag");
System.exit(1);
}
context = new DOMValidateContext(signingCert.getPublicKey(), nodes.item(0));
} }
} }
cp.setCertificate(signingCert);
System.out.println(cp.getCertificateAuthorityInfoAccess());
XMLSignatureFactory sigFactory = XMLSignatureFactory.getInstance("DOM"); XMLSignatureFactory sigFactory = XMLSignatureFactory.getInstance("DOM");
XMLSignature signature = sigFactory.unmarshalXMLSignature(context); XMLSignature signature = sigFactory.unmarshalXMLSignature(context);
boolean signatureIsValid = signature.validate(context); boolean signatureIsValid = signature.validate(context);
boolean certChainIsValid = validateCertChain(signingCert, trustStore);
System.out.println("Signature validity: " + signatureIsValid); System.out.println("Signature validity: " + signatureIsValid);
System.out.println("Cert chain validity: " + certChainIsValid); if (signingCert == null) {
return signatureIsValid && certChainIsValid; signingCert = keySelector.getSigningCert();
}
cp.setCertificate(signingCert);
System.out.println(System.lineSeparator() + cp.getCertificateAuthorityInfoAccess());
return signatureIsValid;
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
System.out.println("Error parsing truststore: " + e.getMessage()); System.out.println("Error parsing truststore: " + e.getMessage());
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
@ -236,134 +215,21 @@ public class SwidTagValidator {
return false; return false;
} }
/**
* This method calculates an RSA public key from the <KeyValue> element of an XML
* signature block.
*
* @param mod the modulus string
* @param exp the exponent string
* @return the calculated public key
*/
private PublicKey calculatePublicKey(final String mod, final String exp)
throws NoSuchAlgorithmException, InvalidKeySpecException{
System.out.println("Decoding " + exp);
BigInteger exponent = new BigInteger(Base64.getMimeDecoder().decode(exp));
System.out.println("MIME: " + exponent);
System.out.println("Decoding " + mod);
BigInteger modulus = new BigInteger(Base64.getMimeDecoder().decode(mod));
System.out.println("MIME: " + modulus);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(new RSAPublicKeySpec(modulus, exponent));
}
/**
* This method validates the cert chain for a given certificate. The truststore is iterated
* over until a root CA is found, otherwise an error is returned.
* @param cert the certificate at the start of the chain
* @param trustStore from which to find the chain of intermediate and root CAs
* @return true if the chain is valid
* @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("Error while validating cert chain: " + 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) {
String error = "Error with signature: " + e.getMessage()
+ System.lineSeparator()
+ "Certificate needed for verification is missing: "
+ signer.getSubjectX500Principal().getName();
throw new Exception(error);
} 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());
}
/** /**
* This internal class handles parsing the public key from a KeyInfo element. * This internal class handles parsing the public key from a KeyInfo element.
*/ */
public class X509KeySelector extends KeySelector { public class X509KeySelector extends KeySelector {
PublicKey publicKey;
X509Certificate signingCert;
public PublicKey getPublicKey() {
return publicKey;
}
public X509Certificate getSigningCert() {
return signingCert;
}
/** /**
* This method extracts a public key from either an X509Certificate element * This method extracts a public key from either an X509Certificate element
* or a KeyValue element. If the public key's algorithm matches the declared * or a KeyValue element. If the public key's algorithm matches the declared
@ -381,8 +247,6 @@ public class SwidTagValidator {
final XMLCryptoContext context) final XMLCryptoContext context)
throws KeySelectorException { throws KeySelectorException {
Iterator keyinfoItr = keyinfo.getContent().iterator(); Iterator keyinfoItr = keyinfo.getContent().iterator();
PublicKey publicKey = null;
System.out.println("Parsing KeyInfo");
while(keyinfoItr.hasNext()) { while(keyinfoItr.hasNext()) {
XMLStructure element = (XMLStructure) keyinfoItr.next(); XMLStructure element = (XMLStructure) keyinfoItr.next();
if (element instanceof X509Data) { if (element instanceof X509Data) {
@ -391,20 +255,40 @@ public class SwidTagValidator {
while (dataItr.hasNext()) { while (dataItr.hasNext()) {
Object object = dataItr.next(); Object object = dataItr.next();
if (object instanceof X509Certificate) { if (object instanceof X509Certificate) {
publicKey = ((X509Certificate) object).getPublicKey(); X509Certificate embeddedCert = (X509Certificate) object;
try {
if (isCertChainValid(embeddedCert)) {
publicKey = ((X509Certificate) embeddedCert).getPublicKey();
signingCert = embeddedCert;
System.out.println("Certificate chain validity: true");
}
} catch (Exception e) {
System.out.println("Certificate chain invalid: "
+ e.getMessage());
}
} }
} }
} else if (element instanceof KeyValue) { } else if (element instanceof KeyValue) {
try { try {
publicKey = ((KeyValue) element).getPublicKey(); PublicKey pk = ((KeyValue) element).getPublicKey();
System.out.println("PK parsed from KeyValue: " + publicKey.toString()); if (isPublicKeyTrusted(pk)) {
publicKey = pk;
try {
System.out.println("Certificate chain validity: "
+ isCertChainValid(signingCert));
} catch (Exception e) {
System.out.println("Certificate chain invalid: "
+ e.getMessage());
}
}
} catch (KeyException e) { } catch (KeyException e) {
System.out.println("Unable to convert KeyValue data to PK."); System.out.println("Unable to convert KeyValue data to PK.");
} }
} }
if (publicKey != null) { if (publicKey != null) {
if (areAlgorithmsEqual(algorithm.getAlgorithm(), publicKey.getAlgorithm())) { if (areAlgorithmsEqual(algorithm.getAlgorithm(), publicKey.getAlgorithm())) {
return new SwidTagValidator.X509KeySelector.RIMKeySelectorResult(publicKey); return new
SwidTagValidator.X509KeySelector.RIMKeySelectorResult(publicKey);
} }
} }
} }
@ -422,6 +306,123 @@ public class SwidTagValidator {
&& name.equalsIgnoreCase("RSA"); && name.equalsIgnoreCase("RSA");
} }
/**
* This method validates the cert chain for a given certificate. The truststore is iterated
* over until a root CA is found, otherwise an error is returned.
* @param cert the certificate at the start of the chain
* @return true if the chain is valid
* @throws Exception if a valid chain is not found in the truststore
*/
private boolean isCertChainValid(final X509Certificate cert)
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("Error while validating cert chain: " + 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) {
String error = "Error with signature: " + e.getMessage()
+ System.lineSeparator()
+ "Certificate needed for verification is missing: "
+ signer.getSubjectX500Principal().getName();
throw new Exception(error);
} 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());
}
/**
* This method compares a public key against those in the truststore.
* @param pk a public key
* @return true if pk is found in the trust store, false otherwise
*/
private boolean isPublicKeyTrusted(final PublicKey pk) {
for (X509Certificate trustedCert : trustStore) {
if (Arrays.equals(trustedCert.getPublicKey().getEncoded(),
pk.getEncoded())) {
signingCert = trustedCert;
return true;
}
}
return false;
}
private class RIMKeySelectorResult implements KeySelectorResult { private class RIMKeySelectorResult implements KeySelectorResult {
private Key key; private Key key;