mirror of
https://github.com/nsacyber/HIRS.git
synced 2024-12-19 21:17:59 +00:00
Merge pull request #751 from nsacyber/v3_issue-737
[#737] Resolve RIM upload parse error
This commit is contained in:
commit
77de04b45f
@ -91,7 +91,7 @@ public class BaseReferenceManifest extends ReferenceManifest {
|
||||
* @param rimBytes - the file content of the uploaded file.
|
||||
* @throws IOException - thrown if the file is invalid.
|
||||
*/
|
||||
public BaseReferenceManifest(final byte[] rimBytes) throws IOException {
|
||||
public BaseReferenceManifest(final byte[] rimBytes) throws UnmarshalException {
|
||||
this("", rimBytes);
|
||||
}
|
||||
|
||||
@ -104,7 +104,8 @@ public class BaseReferenceManifest extends ReferenceManifest {
|
||||
* @throws IOException if unable to unmarshal the string
|
||||
*/
|
||||
@SuppressWarnings("checkstyle:AvoidInlineConditionals")
|
||||
public BaseReferenceManifest(final String fileName, final byte[] rimBytes) throws IOException {
|
||||
public BaseReferenceManifest(final String fileName, final byte[] rimBytes)
|
||||
throws UnmarshalException {
|
||||
super(rimBytes);
|
||||
this.setRimType(BASE_RIM);
|
||||
this.setFileName(fileName);
|
||||
@ -116,10 +117,14 @@ public class BaseReferenceManifest extends ReferenceManifest {
|
||||
|
||||
// begin parsing valid swid tag
|
||||
if (document != null) {
|
||||
softwareIdentity = (Element) document.getElementsByTagName(SwidTagConstants.SOFTWARE_IDENTITY).item(0);
|
||||
entity = (Element) document.getElementsByTagName(SwidTagConstants.ENTITY).item(0);
|
||||
link = (Element) document.getElementsByTagName(SwidTagConstants.LINK).item(0);
|
||||
meta = (Element) document.getElementsByTagName(SwidTagConstants.META).item(0);
|
||||
softwareIdentity = (Element) document.getElementsByTagNameNS(
|
||||
SwidTagConstants.SWIDTAG_NAMESPACE, SwidTagConstants.SOFTWARE_IDENTITY).item(0);
|
||||
entity = (Element) document.getElementsByTagNameNS(
|
||||
SwidTagConstants.SWIDTAG_NAMESPACE, SwidTagConstants.ENTITY).item(0);
|
||||
link = (Element) document.getElementsByTagNameNS(
|
||||
SwidTagConstants.SWIDTAG_NAMESPACE, SwidTagConstants.LINK).item(0);
|
||||
meta = (Element) document.getElementsByTagNameNS(
|
||||
SwidTagConstants.SWIDTAG_NAMESPACE, SwidTagConstants.META).item(0);
|
||||
setTagId(softwareIdentity.getAttribute(SwidTagConstants.TAGID));
|
||||
this.swidName = softwareIdentity.getAttribute(SwidTagConstants.NAME);
|
||||
this.swidCorpus = Boolean.parseBoolean(softwareIdentity.getAttribute(SwidTagConstants.CORPUS)) ? 1 : 0;
|
||||
@ -215,15 +220,24 @@ public class BaseReferenceManifest extends ReferenceManifest {
|
||||
* @param byteArrayInputStream the location of the file to be validated
|
||||
*/
|
||||
private Element getDirectoryTag(final ByteArrayInputStream byteArrayInputStream) {
|
||||
Document document = unmarshallSwidTag(byteArrayInputStream);
|
||||
Element softwareIdentity =
|
||||
(Element) document.getElementsByTagName("SoftwareIdentity").item(0);
|
||||
if (softwareIdentity != null) {
|
||||
Element directory = (Element) document.getElementsByTagName("Directory").item(0);
|
||||
Document document = null;
|
||||
try {
|
||||
document = unmarshallSwidTag(byteArrayInputStream);
|
||||
} catch (UnmarshalException e) {
|
||||
log.error("Error while parsing Directory tag: " + e.getMessage());
|
||||
}
|
||||
if (document != null) {
|
||||
Element softwareIdentity =
|
||||
(Element) document.getElementsByTagNameNS(
|
||||
SwidTagConstants.SWIDTAG_NAMESPACE, "SoftwareIdentity").item(0);
|
||||
if (softwareIdentity != null) {
|
||||
Element directory = (Element) document.getElementsByTagNameNS(
|
||||
SwidTagConstants.SWIDTAG_NAMESPACE, "Directory").item(0);
|
||||
|
||||
return directory;
|
||||
} else {
|
||||
log.error("Invalid xml for validation, please verify ");
|
||||
return directory;
|
||||
} else {
|
||||
log.error("Invalid xml for validation, please verify ");
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -268,7 +282,8 @@ public class BaseReferenceManifest extends ReferenceManifest {
|
||||
* @param byteArrayInputStream to the input swidtag
|
||||
* @return the Document element at the root of the swidtag
|
||||
*/
|
||||
private Document unmarshallSwidTag(final ByteArrayInputStream byteArrayInputStream) {
|
||||
private Document unmarshallSwidTag(final ByteArrayInputStream byteArrayInputStream)
|
||||
throws UnmarshalException {
|
||||
InputStream is = null;
|
||||
Document document = null;
|
||||
Unmarshaller unmarshaller = null;
|
||||
@ -288,7 +303,7 @@ public class BaseReferenceManifest extends ReferenceManifest {
|
||||
} catch (SAXException e) {
|
||||
log.error("Error setting schema for validation!");
|
||||
} catch (UnmarshalException e) {
|
||||
log.error("Error validating swidtag file!");
|
||||
throw new UnmarshalException("Error validating swidtag file");
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.error("Input file empty.");
|
||||
} catch (JAXBException e) {
|
||||
|
@ -41,6 +41,7 @@ import hirs.utils.SwidResource;
|
||||
import hirs.utils.enums.DeviceInfoEnums;
|
||||
import hirs.utils.tpm.eventlog.TCGEventLog;
|
||||
import hirs.utils.tpm.eventlog.TpmPcrEvent;
|
||||
import jakarta.xml.bind.UnmarshalException;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
@ -420,8 +421,8 @@ public class IdentityClaimProcessor extends AbstractProcessor {
|
||||
}
|
||||
}
|
||||
tagId = dbBaseRim.getTagId();
|
||||
} catch (IOException ioEx) {
|
||||
log.error(ioEx);
|
||||
} catch (UnmarshalException e) {
|
||||
log.error(e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -47,6 +47,8 @@ dependencies {
|
||||
implementation libs.bouncycastle
|
||||
implementation libs.guava
|
||||
implementation libs.jakarta.servlet
|
||||
implementation libs.jakarta.api
|
||||
implementation libs.jakarta.xml
|
||||
|
||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||
|
@ -35,13 +35,16 @@ import java.security.KeyStore;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
@ -303,6 +306,16 @@ public class ReferenceManifestDetailsPageController extends PageController<Refer
|
||||
for (Certificate certificate : certificates) {
|
||||
caCert = (CertificateAuthorityCredential) certificate;
|
||||
KeyStore keystore = ValidationService.getCaChain(caCert, caCertificateRepository);
|
||||
try {
|
||||
List<X509Certificate> truststore =
|
||||
convertCACsToX509Certificates(ValidationService.getCaChainRec(caCert,
|
||||
Collections.emptySet(),
|
||||
caCertificateRepository));
|
||||
RIM_VALIDATOR.setTrustStore(truststore);
|
||||
} catch (IOException e) {
|
||||
log.error("Error building CA chain for " + caCert.getSubjectKeyIdentifier() + ": "
|
||||
+ e.getMessage());
|
||||
}
|
||||
if (RIM_VALIDATOR.validateXmlSignature(caCert.getX509Certificate().getPublicKey(),
|
||||
caCert.getSubjectKeyIdString(), caCert.getEncodedPublicKey())) {
|
||||
try {
|
||||
@ -333,6 +346,20 @@ public class ReferenceManifestDetailsPageController extends PageController<Refer
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method converts a Set<CertificateAuthorityCredential> to a List<X509Certificate>.
|
||||
* @param set of CACs to convert
|
||||
* @return list of X509Certificates
|
||||
*/
|
||||
private static List<X509Certificate> convertCACsToX509Certificates(Set<CertificateAuthorityCredential> set)
|
||||
throws IOException {
|
||||
ArrayList<X509Certificate> certs = new ArrayList<>(set.size());
|
||||
for (CertificateAuthorityCredential cac : set) {
|
||||
certs.add(cac.getX509Certificate());
|
||||
}
|
||||
return certs;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method takes the place of an entire class for a string builder.
|
||||
* Gathers all information and returns it for displays.
|
||||
|
@ -19,6 +19,7 @@ import hirs.utils.tpm.eventlog.TpmPcrEvent;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.xml.bind.UnmarshalException;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
@ -61,7 +62,8 @@ import java.util.zip.ZipOutputStream;
|
||||
@RequestMapping("/HIRS_AttestationCAPortal/portal/reference-manifests")
|
||||
public class ReferenceManifestPageController extends PageController<NoPageParams> {
|
||||
|
||||
private static final String LOG_FILE_PATTERN = "([^\\s]+(\\.(?i)(rimpcr|rimel|bin|log))$)";
|
||||
private static final String BASE_RIM_FILE_PATTERN = "([^\\s]+(\\.(?i)swidtag)$)";
|
||||
private static final String SUPPORT_RIM_FILE_PATTERN = "([^\\s]+(\\.(?i)(rimpcr|rimel|bin|log))$)";
|
||||
|
||||
@Autowired(required = false)
|
||||
private EntityManager entityManager;
|
||||
@ -155,21 +157,34 @@ public class ReferenceManifestPageController extends PageController<NoPageParams
|
||||
Map<String, Object> model = new HashMap<>();
|
||||
PageMessages messages = new PageMessages();
|
||||
String fileName;
|
||||
Pattern logPattern = Pattern.compile(LOG_FILE_PATTERN);
|
||||
Pattern baseRimPattern = Pattern.compile(BASE_RIM_FILE_PATTERN);
|
||||
Pattern supportRimPattern = Pattern.compile(SUPPORT_RIM_FILE_PATTERN);
|
||||
Matcher matcher;
|
||||
boolean supportRIM = false;
|
||||
List<BaseReferenceManifest> baseRims = new ArrayList<>();
|
||||
List<SupportReferenceManifest> supportRims = new ArrayList<>();
|
||||
log.info(String.format("Processing %s uploaded files", files.length));
|
||||
|
||||
// loop through the files
|
||||
for (MultipartFile file : files) {
|
||||
boolean isBaseRim;
|
||||
boolean isSupportRim = false;
|
||||
fileName = file.getOriginalFilename();
|
||||
matcher = logPattern.matcher(fileName);
|
||||
supportRIM = matcher.matches();
|
||||
|
||||
//Parse reference manifests
|
||||
parseRIM(file, supportRIM, messages, baseRims, supportRims);
|
||||
matcher = baseRimPattern.matcher(fileName);
|
||||
isBaseRim = matcher.matches();
|
||||
if (!isBaseRim) {
|
||||
matcher = supportRimPattern.matcher(fileName);
|
||||
isSupportRim = matcher.matches();
|
||||
}
|
||||
if (isBaseRim || isSupportRim) {
|
||||
parseRIM(file, isSupportRim, messages, baseRims, supportRims);
|
||||
} else {
|
||||
String errorString = "The file extension of " + fileName + " was not recognized." +
|
||||
" Base RIMs support the extension \".swidtag\", and support RIMs support " +
|
||||
"\".rimpcr\", \".rimel\", \".bin\", and \".log\". " +
|
||||
"Please verify your upload and retry.";
|
||||
log.error("File extension in " + fileName + " not recognized as base or support RIM.");
|
||||
messages.addError(errorString);
|
||||
}
|
||||
}
|
||||
baseRims.stream().forEach((rim) -> {
|
||||
log.info(String.format("Storing swidtag %s", rim.getFileName()));
|
||||
@ -393,23 +408,33 @@ public class ReferenceManifestPageController extends PageController<NoPageParams
|
||||
try {
|
||||
if (supportRIM) {
|
||||
supportRim = new SupportReferenceManifest(fileName, fileBytes);
|
||||
if (referenceManifestRepository.findByHexDecHashAndRimType(supportRim.getHexDecHash(),
|
||||
supportRim.getRimType()) == null) {
|
||||
if (referenceManifestRepository.findByHexDecHashAndRimType(
|
||||
supportRim.getHexDecHash(), supportRim.getRimType()) == null) {
|
||||
supportRims.add(supportRim);
|
||||
messages.addInfo("Saved Reference Manifest " + fileName);
|
||||
messages.addInfo("Saved support RIM " + fileName);
|
||||
}
|
||||
} else {
|
||||
baseRim = new BaseReferenceManifest(fileName, fileBytes);
|
||||
if (referenceManifestRepository.findByHexDecHashAndRimType(baseRim.getHexDecHash(),
|
||||
baseRim.getRimType()) == null) {
|
||||
if (referenceManifestRepository.findByHexDecHashAndRimType(
|
||||
baseRim.getHexDecHash(), baseRim.getRimType()) == null) {
|
||||
baseRims.add(baseRim);
|
||||
messages.addInfo("Saved base RIM " + fileName);
|
||||
}
|
||||
}
|
||||
} catch (IOException | NullPointerException ioEx) {
|
||||
final String failMessage
|
||||
= String.format("Failed to parse uploaded file (%s): ", fileName);
|
||||
= String.format("Failed to parse support RIM file (%s): ", fileName);
|
||||
log.error(failMessage, ioEx);
|
||||
messages.addError(failMessage + ioEx.getMessage());
|
||||
} catch (UnmarshalException e) {
|
||||
final String failMessage
|
||||
= String.format("Failed to parse base RIM file (%s): ", fileName);
|
||||
log.error(failMessage, e);
|
||||
messages.addError(failMessage + e.getMessage());
|
||||
} catch (Exception e) {
|
||||
final String failMessage
|
||||
= String.format("Failed to parse (%s): ", fileName);
|
||||
log.error(failMessage, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
<jsp:attribute name="pageHeaderTitle">Error - 404</jsp:attribute>
|
||||
|
||||
<jsp:body>
|
||||
<!--<div> Exception Message: <c:out value="${exception}"</c:out></div>
|
||||
<!--<div> Exception Message: <c:out value="${exception}"></c:out></div>
|
||||
<div> from URL -> <span th:text="${url}"</span></div>-->
|
||||
</jsp:body>
|
||||
</my:page>
|
@ -8,6 +8,7 @@ import jakarta.xml.bind.Unmarshaller;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.bouncycastle.asn1.x509.Extension;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
|
||||
import org.bouncycastle.jcajce.provider.asymmetric.X509;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
@ -23,6 +24,7 @@ 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.Reference;
|
||||
import javax.xml.crypto.dsig.XMLSignature;
|
||||
import javax.xml.crypto.dsig.XMLSignatureException;
|
||||
import javax.xml.crypto.dsig.XMLSignatureFactory;
|
||||
@ -154,12 +156,20 @@ public class ReferenceManifestValidator {
|
||||
|
||||
/**
|
||||
* Setter for the truststore file path.
|
||||
* @param trustStoreFile the truststore
|
||||
* @param trustStoreFile the file path to reference
|
||||
*/
|
||||
public void setTrustStoreFile(String trustStoreFile) {
|
||||
this.trustStoreFile = trustStoreFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for truststore
|
||||
* @param trustStore the List of X509Certificates
|
||||
*/
|
||||
public void setTrustStore(List<X509Certificate> trustStore) {
|
||||
this.trustStore = trustStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for rimel file path.
|
||||
* @param rimEventLog the rimel file
|
||||
@ -220,8 +230,7 @@ public class ReferenceManifestValidator {
|
||||
X509Certificate embeddedCert = parseCertFromPEMString(
|
||||
certElement.item(0).getTextContent());
|
||||
if (embeddedCert != null) {
|
||||
if (Arrays.equals(embeddedCert.getPublicKey().getEncoded(),
|
||||
encodedPublicKey)) {
|
||||
if (isCertChainValid(embeddedCert)) {
|
||||
context = new DOMValidateContext(new X509KeySelector(), nodes.item(0));
|
||||
}
|
||||
}
|
||||
@ -252,7 +261,8 @@ public class ReferenceManifestValidator {
|
||||
* @return true if both the file element and signature are valid, false otherwise
|
||||
*/
|
||||
public boolean validateRim(String signingCertPath) {
|
||||
Element fileElement = (Element) rim.getElementsByTagName("File").item(0);
|
||||
Element fileElement = (Element) rim.getElementsByTagNameNS(
|
||||
SwidTagConstants.SWIDTAG_NAMESPACE, "File").item(0);
|
||||
X509Certificate signingCert = parseCertificatesFromPem(signingCertPath).get(0);
|
||||
if (signingCert == null) {
|
||||
return failWithError("Unable to parse the signing cert from " + signingCertPath);
|
||||
@ -374,7 +384,12 @@ public class ReferenceManifestValidator {
|
||||
try {
|
||||
XMLSignatureFactory sigFactory = XMLSignatureFactory.getInstance("DOM");
|
||||
XMLSignature signature = sigFactory.unmarshalXMLSignature(context);
|
||||
return signature.validate(context);
|
||||
boolean isValid = signature.validate(context);
|
||||
if (isValid) {
|
||||
return true;
|
||||
} else {
|
||||
whySignatureInvalid(signature, context);
|
||||
}
|
||||
} catch (MarshalException e) {
|
||||
log.warn("Error while unmarshalling XML signature: " + e.getMessage());
|
||||
} catch (XMLSignatureException e) {
|
||||
@ -384,6 +399,149 @@ public class ReferenceManifestValidator {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method attempts to gather information on why a signature failed to validate.
|
||||
* The <SignatureValue> and each <Reference> is cryptographically validated and the
|
||||
* results are logged.
|
||||
*
|
||||
* @param signature the signature that failed to validate
|
||||
* @param context the context used for validation
|
||||
* @throws XMLSignatureException
|
||||
*/
|
||||
private void whySignatureInvalid(final XMLSignature signature, final DOMValidateContext context)
|
||||
throws XMLSignatureException{
|
||||
log.error("Verifying xml signature:");
|
||||
boolean cryptoValidity = signature.getSignatureValue().validate(context);
|
||||
if (cryptoValidity) {
|
||||
log.error("Signature value is valid.");
|
||||
} else {
|
||||
log.error("Signature value is invalid!");
|
||||
}
|
||||
Iterator itr = signature.getSignedInfo().getReferences().iterator();
|
||||
while (itr.hasNext()) {
|
||||
Reference reference = (Reference) itr.next();
|
||||
boolean refValidity = reference.validate(context);
|
||||
String refUri = reference.getURI();
|
||||
if (refUri.isEmpty()) {
|
||||
refUri = "whole document";
|
||||
}
|
||||
if (refValidity) {
|
||||
log.error("Reference for " + refUri + " is valid.");
|
||||
} else {
|
||||
log.error("Reference for " + refUri + " is invalid!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 chainCert = cert;
|
||||
boolean isChainCertValid;
|
||||
do {
|
||||
isChainCertValid = false;
|
||||
log.error("Validating " + chainCert.getSubjectX500Principal().getName());
|
||||
for (X509Certificate trustedCert : trustStore) {
|
||||
boolean isIssuer = areYouMyIssuer(chainCert, trustedCert);
|
||||
boolean isSigner = areYouMySigner(chainCert, trustedCert);
|
||||
if (isIssuer && isSigner) {
|
||||
if (isSelfSigned(trustedCert)) {
|
||||
log.info("Root CA found.");
|
||||
return true;
|
||||
} else {
|
||||
chainCert = trustedCert;
|
||||
isChainCertValid = true;
|
||||
log.info("Intermediate CA found.");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (!isIssuer) {
|
||||
errorMessage = "Issuer cert not found";
|
||||
} else if (!isSigner) {
|
||||
errorMessage = "Signing cert not found";
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (isChainCertValid);
|
||||
|
||||
log.error("CA chain validation failed to validate "
|
||||
+ chainCert.getSubjectX500Principal().getName());
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
log.error(error);
|
||||
} catch (CertificateException e) {
|
||||
throw new Exception("Encoding error: " + e.getMessage());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 selecting an X509 certificate embedded in a KeyInfo element.
|
||||
* It is passed as a parameter to a DOMValidateContext that uses it to validate
|
||||
@ -473,106 +631,6 @@ public class ReferenceManifestValidator {
|
||||
&& 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
|
||||
|
@ -22,6 +22,7 @@ public class SwidTagConstants {
|
||||
public static final String SCHEMA_PACKAGE = "hirs.swid.xjc";
|
||||
public static final String SCHEMA_LANGUAGE = XMLConstants.W3C_XML_SCHEMA_NS_URI;
|
||||
public static final String SCHEMA_URL = "swid_schema.xsd";
|
||||
public static final String SWIDTAG_NAMESPACE = "http://standards.iso.org/iso/19770/-2/2015/schema.xsd";
|
||||
|
||||
public static final String SOFTWARE_IDENTITY = "SoftwareIdentity";
|
||||
public static final String ENTITY = "Entity";
|
||||
|
@ -66,6 +66,8 @@ public class ObjectFactory {
|
||||
private final static QName _SoftwareIdentityLink_QNAME = new QName("http://standards.iso.org/iso/19770/-2/2015/schema.xsd", "Link");
|
||||
private final static QName _SoftwareIdentityEvidence_QNAME = new QName("http://standards.iso.org/iso/19770/-2/2015/schema.xsd", "Evidence");
|
||||
private final static QName _SoftwareIdentityPayload_QNAME = new QName("http://standards.iso.org/iso/19770/-2/2015/schema.xsd", "Payload");
|
||||
private final static QName _PayloadDirectory_QNAME = new QName("http://standards.iso.org/iso/19770/-2/2015/schema.xsd", "Directory");
|
||||
private final static QName _DirectoryFile_QNAME = new QName("http://standards.iso.org/iso/19770/-2/2015/schema.xsd", "File");
|
||||
private final static QName _SoftwareIdentityEntity_QNAME = new QName("http://standards.iso.org/iso/19770/-2/2015/schema.xsd", "Entity");
|
||||
private final static QName _SoftwareIdentityMeta_QNAME = new QName("http://standards.iso.org/iso/19770/-2/2015/schema.xsd", "Meta");
|
||||
private final static QName _SignatureMethodTypeHMACOutputLength_QNAME = new QName("http://www.w3.org/2000/09/xmldsig#", "HMACOutputLength");
|
||||
@ -666,6 +668,24 @@ public class ObjectFactory {
|
||||
return new JAXBElement<ResourceCollection>(_SoftwareIdentityPayload_QNAME, ResourceCollection.class, SoftwareIdentity.class, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of {@link JAXBElement }{@code <}{@link FilesystemItem }{@code >}}
|
||||
*
|
||||
*/
|
||||
@XmlElementDecl(namespace = "http://standards.iso.org/iso/19770/-2/2015/schema.xsd", name = "Directory", scope = ResourceCollection.class)
|
||||
public JAXBElement<FilesystemItem> createPayloadDirectory(FilesystemItem value) {
|
||||
return new JAXBElement<FilesystemItem>(_PayloadDirectory_QNAME, FilesystemItem.class, ResourceCollection.class, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of {@link JAXBElement }{@code <}{@link FilesystemItem }{@code >}}
|
||||
*
|
||||
*/
|
||||
@XmlElementDecl(namespace = "http://standards.iso.org/iso/19770/-2/2015/schema.xsd", name = "File", scope = ResourceCollection.class)
|
||||
public JAXBElement<FilesystemItem> createDirectoryFile(FilesystemItem value) {
|
||||
return new JAXBElement<FilesystemItem>(_DirectoryFile_QNAME, FilesystemItem.class, ResourceCollection.class, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of {@link JAXBElement }{@code <}{@link Entity }{@code >}}
|
||||
*
|
||||
|
@ -28,6 +28,7 @@ public class SwidTagConstants {
|
||||
public static final String SCHEMA_PACKAGE = "hirs.utils.xjc";
|
||||
public static final String SCHEMA_LANGUAGE = XMLConstants.W3C_XML_SCHEMA_NS_URI;
|
||||
public static final String SCHEMA_URL = "swid_schema.xsd";
|
||||
public static final String SWIDTAG_NAMESPACE = "http://standards.iso.org/iso/19770/-2/2015/schema.xsd";
|
||||
|
||||
public static final String SOFTWARE_IDENTITY = "SoftwareIdentity";
|
||||
public static final String ENTITY = "Entity";
|
||||
|
@ -3,22 +3,24 @@ package hirs.swid;
|
||||
import hirs.swid.utils.HashSwid;
|
||||
import hirs.utils.xjc.Directory;
|
||||
import hirs.utils.xjc.Entity;
|
||||
import hirs.utils.xjc.FilesystemItem;
|
||||
import hirs.utils.xjc.Link;
|
||||
import hirs.utils.xjc.ObjectFactory;
|
||||
import hirs.utils.xjc.ResourceCollection;
|
||||
import hirs.utils.xjc.SoftwareIdentity;
|
||||
import hirs.utils.xjc.SoftwareMeta;
|
||||
import jakarta.xml.bind.JAXBContext;
|
||||
import jakarta.xml.bind.JAXBElement;
|
||||
import jakarta.xml.bind.JAXBException;
|
||||
import jakarta.xml.bind.Marshaller;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import javax.json.Json;
|
||||
import javax.json.JsonException;
|
||||
import javax.json.JsonObject;
|
||||
import javax.json.JsonReader;
|
||||
import jakarta.xml.bind.JAXBContext;
|
||||
import jakarta.xml.bind.JAXBElement;
|
||||
import jakarta.xml.bind.JAXBException;
|
||||
import jakarta.xml.bind.Marshaller;
|
||||
import javax.xml.crypto.MarshalException;
|
||||
import javax.xml.crypto.XMLStructure;
|
||||
import javax.xml.crypto.dom.DOMStructure;
|
||||
@ -41,6 +43,7 @@ import javax.xml.crypto.dsig.keyinfo.X509Data;
|
||||
import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
|
||||
import javax.xml.crypto.dsig.spec.TransformParameterSpec;
|
||||
import javax.xml.namespace.QName;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.transform.OutputKeys;
|
||||
@ -91,6 +94,8 @@ public class SwidTagGateway {
|
||||
private String timestampFormat;
|
||||
private String timestampArgument;
|
||||
private String errorRequiredFields;
|
||||
private DocumentBuilderFactory dbf;
|
||||
private DocumentBuilder builder;
|
||||
|
||||
/**
|
||||
* Default constructor initializes jaxbcontext, marshaller, and unmarshaller
|
||||
@ -107,8 +112,15 @@ public class SwidTagGateway {
|
||||
timestampFormat = "";
|
||||
timestampArgument = "";
|
||||
errorRequiredFields = "";
|
||||
dbf = DocumentBuilderFactory.newInstance();
|
||||
dbf.setNamespaceAware(true);
|
||||
builder = dbf.newDocumentBuilder();
|
||||
} catch (JAXBException e) {
|
||||
System.out.println("Error initializing jaxbcontext: " + e.getMessage());
|
||||
} catch (ParserConfigurationException e) {
|
||||
System.out.println("Error instantiating Document object for parsing swidtag: "
|
||||
+ e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,49 +210,62 @@ public class SwidTagGateway {
|
||||
* @param filename
|
||||
*/
|
||||
public void generateSwidTag(final String filename) {
|
||||
SoftwareIdentity swidTag = null;
|
||||
Document swidtag = builder.newDocument();
|
||||
SoftwareIdentity softwareIdentity = null;
|
||||
try {
|
||||
InputStream is = new FileInputStream(attributesFile);
|
||||
JsonReader reader = Json.createReader(is);
|
||||
JsonObject configProperties = reader.readObject();
|
||||
reader.close();
|
||||
//SoftwareIdentity
|
||||
swidTag = createSwidTag(
|
||||
softwareIdentity = createSwidTag(
|
||||
configProperties.getJsonObject(SwidTagConstants.SOFTWARE_IDENTITY));
|
||||
//Entity
|
||||
JAXBElement<Entity> entity = objectFactory.createSoftwareIdentityEntity(
|
||||
createEntity(configProperties.getJsonObject(SwidTagConstants.ENTITY)));
|
||||
swidTag.getEntityOrEvidenceOrLink().add(entity);
|
||||
softwareIdentity.getEntityOrEvidenceOrLink().add(entity);
|
||||
//Link
|
||||
JAXBElement<Link> link = objectFactory.createSoftwareIdentityLink(
|
||||
createLink(configProperties.getJsonObject(SwidTagConstants.LINK)));
|
||||
swidTag.getEntityOrEvidenceOrLink().add(link);
|
||||
softwareIdentity.getEntityOrEvidenceOrLink().add(link);
|
||||
//Meta
|
||||
JAXBElement<SoftwareMeta> meta = objectFactory.createSoftwareIdentityMeta(
|
||||
createSoftwareMeta(configProperties.getJsonObject(SwidTagConstants.META)));
|
||||
swidTag.getEntityOrEvidenceOrLink().add(meta);
|
||||
//Payload
|
||||
ResourceCollection payload = createPayload(
|
||||
configProperties.getJsonObject(SwidTagConstants.PAYLOAD));
|
||||
//Directory
|
||||
Directory directory = createDirectory(
|
||||
configProperties.getJsonObject(SwidTagConstants.PAYLOAD)
|
||||
.getJsonObject(SwidTagConstants.DIRECTORY));
|
||||
softwareIdentity.getEntityOrEvidenceOrLink().add(meta);
|
||||
|
||||
swidtag = convertToDocument(objectFactory.createSoftwareIdentity(softwareIdentity));
|
||||
Element rootElement = swidtag.getDocumentElement();
|
||||
|
||||
//File
|
||||
hirs.utils.xjc.File file = createFile(
|
||||
configProperties.getJsonObject(SwidTagConstants.PAYLOAD)
|
||||
.getJsonObject(SwidTagConstants.DIRECTORY)
|
||||
.getJsonObject(SwidTagConstants.FILE));
|
||||
//Nest File in Directory in Payload
|
||||
directory.getDirectoryOrFile().add(file);
|
||||
payload.getDirectoryOrFileOrProcess().add(directory);
|
||||
JAXBElement<FilesystemItem> jaxbFile = objectFactory.createDirectoryFile(file);
|
||||
Document fileDoc = convertToDocument(jaxbFile);
|
||||
//Directory
|
||||
Directory directory = createDirectory(
|
||||
configProperties.getJsonObject(SwidTagConstants.PAYLOAD)
|
||||
.getJsonObject(SwidTagConstants.DIRECTORY));
|
||||
JAXBElement<FilesystemItem> jaxbDirectory = objectFactory.createPayloadDirectory(directory);
|
||||
Document dirDoc = convertToDocument(jaxbDirectory);
|
||||
Node fileNode = dirDoc.importNode(fileDoc.getDocumentElement(), true);
|
||||
dirDoc.getDocumentElement().appendChild(fileNode);
|
||||
//Payload
|
||||
ResourceCollection payload = createPayload(
|
||||
configProperties.getJsonObject(SwidTagConstants.PAYLOAD));
|
||||
JAXBElement<ResourceCollection> jaxbPayload =
|
||||
objectFactory.createSoftwareIdentityPayload(payload);
|
||||
swidTag.getEntityOrEvidenceOrLink().add(jaxbPayload);
|
||||
Document payloadDoc = convertToDocument(jaxbPayload);
|
||||
Node dirNode = payloadDoc.importNode(dirDoc.getDocumentElement(), true);
|
||||
payloadDoc.getDocumentElement().appendChild(dirNode);
|
||||
|
||||
Node payloadNode = swidtag.importNode(payloadDoc.getDocumentElement(), true);
|
||||
rootElement.appendChild(payloadNode);
|
||||
|
||||
//Signature
|
||||
if (errorRequiredFields.isEmpty()) {
|
||||
Document signedSoftwareIdentity = signXMLDocument(
|
||||
objectFactory.createSoftwareIdentity(swidTag));
|
||||
Document signedSoftwareIdentity = signXMLDocument(swidtag);
|
||||
writeSwidTagFile(signedSoftwareIdentity, filename);
|
||||
} else {
|
||||
System.out.println("The following fields cannot be empty or null: "
|
||||
@ -545,23 +570,31 @@ public class SwidTagGateway {
|
||||
}
|
||||
|
||||
/**
|
||||
* This method signs a SoftwareIdentity with an xmldsig in compatibility mode.
|
||||
* Current assumptions: digest method SHA256, signature method SHA256, enveloped signature
|
||||
* This method converts a JAXBElement object generated from the hirs.utils.xjc package into
|
||||
* a Document object.
|
||||
*
|
||||
* @param element to convert
|
||||
* @return a Document object
|
||||
*/
|
||||
private Document signXMLDocument(JAXBElement<SoftwareIdentity> swidTag) {
|
||||
private Document convertToDocument(JAXBElement element) {
|
||||
Document doc = null;
|
||||
try {
|
||||
doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
|
||||
marshaller.marshal(swidTag, doc);
|
||||
} catch (ParserConfigurationException e) {
|
||||
System.out.println("Error instantiating Document object for parsing swidtag: "
|
||||
+ e.getMessage());
|
||||
System.exit(1);
|
||||
doc = builder.newDocument();
|
||||
marshaller.marshal(element, doc);
|
||||
} catch (JAXBException e) {
|
||||
System.out.println("Error while marshaling swidtag: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method signs a SoftwareIdentity with an xmldsig in compatibility mode.
|
||||
* Current assumptions: digest method SHA256, signature method SHA256, enveloped signature
|
||||
*/
|
||||
private Document signXMLDocument(Document doc) {
|
||||
XMLSignatureFactory sigFactory = XMLSignatureFactory.getInstance("DOM");
|
||||
List xmlObjectList = null;
|
||||
String signatureId = null;
|
||||
@ -679,12 +712,13 @@ public class SwidTagGateway {
|
||||
* @return an XMLObject containing the timestamp element
|
||||
*/
|
||||
private XMLObject createXmlTimestamp(Document doc, XMLSignatureFactory sigFactory) {
|
||||
Element timeStampElement = doc.createElement("TimeStamp");
|
||||
Element timeStampElement = null;
|
||||
switch (timestampFormat.toUpperCase()) {
|
||||
case "RFC3852":
|
||||
try {
|
||||
byte[] counterSignature = Base64.getEncoder().encode(
|
||||
Files.readAllBytes(Paths.get(timestampArgument)));
|
||||
timeStampElement = doc.createElementNS(SwidTagConstants.RFC3852_NS, "TimeStamp");
|
||||
timeStampElement.setAttributeNS("http://www.w3.org/2000/xmlns/",
|
||||
"xmlns:" + SwidTagConstants.RFC3852_PFX,
|
||||
SwidTagConstants.RFC3852_NS);
|
||||
@ -696,6 +730,7 @@ public class SwidTagGateway {
|
||||
}
|
||||
break;
|
||||
case "RFC3339":
|
||||
timeStampElement = doc.createElementNS(SwidTagConstants.RFC3339_NS, "TimeStamp");
|
||||
timeStampElement.setAttributeNS("http://www.w3.org/2000/xmlns/",
|
||||
"xmlns:" + SwidTagConstants.RFC3339_PFX,
|
||||
SwidTagConstants.RFC3339_NS);
|
||||
|
Loading…
Reference in New Issue
Block a user