Merge pull request #751 from nsacyber/v3_issue-737

[#737] Resolve RIM upload parse error
This commit is contained in:
chubtub 2024-05-15 23:07:51 -04:00 committed by GitHub
commit 77de04b45f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 355 additions and 170 deletions

View File

@ -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) {

View File

@ -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 {

View File

@ -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'

View File

@ -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.

View File

@ -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);
}
}

View File

@ -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>

View File

@ -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

View File

@ -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";

View File

@ -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 >}}
*

View File

@ -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";

View File

@ -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);