From 6acfb13ce8aa0f5dcf744e4ba543363021718eaf Mon Sep 17 00:00:00 2001 From: chubtub <43381989+chubtub@users.noreply.github.com> Date: Thu, 1 Jul 2021 10:16:27 -0400 Subject: [PATCH 1/5] Add cert path validation to SwidTagValidator class. Modify CredentialParser class to support changes. --- .../main/java/hirs/swid/CredentialParser.java | 49 +++++-- .../src/main/java/hirs/swid/Main.java | 1 - .../main/java/hirs/swid/SwidTagValidator.java | 138 +++++++++++++++++- 3 files changed, 169 insertions(+), 19 deletions(-) diff --git a/tools/tcg_rim_tool/src/main/java/hirs/swid/CredentialParser.java b/tools/tcg_rim_tool/src/main/java/hirs/swid/CredentialParser.java index 9f24ace0..707e611b 100644 --- a/tools/tcg_rim_tool/src/main/java/hirs/swid/CredentialParser.java +++ b/tools/tcg_rim_tool/src/main/java/hirs/swid/CredentialParser.java @@ -19,6 +19,7 @@ import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; +import java.util.List; /** * This class parses private key, public key, and certificate for use in their respective java.security objects. @@ -31,6 +32,8 @@ public class CredentialParser { private static final String PKCS1_FOOTER = "-----END RSA PRIVATE KEY-----"; private static final String PKCS8_HEADER = "-----BEGIN PRIVATE KEY-----"; private static final String PKCS8_FOOTER = "-----END PRIVATE KEY-----"; + private static final String CERTIFICATE_HEADER = "-----BEGIN CERTIFICATE-----"; + private static final String CERTIFICATE_FOOTER = "-----END CERTIFICATE-----"; private X509Certificate certificate; private PrivateKey privateKey; private PublicKey publicKey; @@ -61,8 +64,9 @@ public class CredentialParser { publicKey = certificate.getPublicKey(); } - public void parsePEMCredentials(String certificateFile, String privateKeyFile) throws CertificateException, FileNotFoundException { - certificate = parsePEMCertificate(certificateFile); + public void parsePEMCredentials(String certificateFile, String privateKeyFile) + throws CertificateException, FileNotFoundException { + certificate = parsePEMCertificates(certificateFile).get(0); if (certificate.getIssuerX500Principal().equals(certificate.getSubjectX500Principal())) { throw new CertificateException("Signing certificate cannot be self-signed!"); } @@ -70,24 +74,44 @@ public class CredentialParser { publicKey = certificate.getPublicKey(); } + /** + * This method extracts certificate bytes from a string. The bytes are assumed to be + * PEM format, and a header and footer are concatenated with the input string to + * facilitate proper parsing. + * @param pemString the input string + * @return an X509Certificate created from the string + * @throws CertificateException if instantiating the CertificateFactory errors + */ + public X509Certificate parseCertFromPEMString(String pemString) throws CertificateException { + try { + CertificateFactory factory = CertificateFactory.getInstance(X509); + InputStream inputStream = new ByteArrayInputStream((CERTIFICATE_HEADER + + pemString + + CERTIFICATE_FOOTER).getBytes()); + return (X509Certificate) factory.generateCertificate(inputStream); + } catch (CertificateException e) { + throw e; + } + } + /** * This method returns the X509Certificate object from a PEM certificate file. * @param certificateFile * @return * @throws FileNotFoundException */ - public X509Certificate parseCertFromPEM(String certificateFile) throws FileNotFoundException { - return parsePEMCertificate(certificateFile); + public List<X509Certificate> parseCertsFromPEM(String certificateFile) + throws FileNotFoundException { + return parsePEMCertificates(certificateFile); } /** * This method returns the X509Certificate found in a PEM file. - * @param filename - * @return - * @throws FileNotFoundException + * @param filename pem file + * @return a list containing all X509Certificates extracted */ - private X509Certificate parsePEMCertificate(String filename) throws FileNotFoundException { - X509Certificate certificate = null; + private List<X509Certificate> parsePEMCertificates(String filename) { + List<X509Certificate> certificates = null; FileInputStream fis = null; BufferedInputStream bis = null; try { @@ -96,9 +120,12 @@ public class CredentialParser { CertificateFactory certificateFactory = CertificateFactory.getInstance(X509); while (bis.available() > 0) { - certificate = (X509Certificate) certificateFactory.generateCertificate(bis); + certificates = (List<X509Certificate>) certificateFactory.generateCertificates(bis); } + if (certificates.size() < 1) { + System.out.println("ERROR: No certificates parsed from " + filename); + } bis.close(); } catch (CertificateException e) { System.out.println("Error in certificate factory: " + e.getMessage()); @@ -117,7 +144,7 @@ public class CredentialParser { } } - return certificate; + return certificates; } /** diff --git a/tools/tcg_rim_tool/src/main/java/hirs/swid/Main.java b/tools/tcg_rim_tool/src/main/java/hirs/swid/Main.java index 56c911e0..73cda086 100644 --- a/tools/tcg_rim_tool/src/main/java/hirs/swid/Main.java +++ b/tools/tcg_rim_tool/src/main/java/hirs/swid/Main.java @@ -3,7 +3,6 @@ package hirs.swid; import hirs.swid.utils.Commander; import com.beust.jcommander.JCommander; -import java.io.FileNotFoundException; import java.io.IOException; public class Main { diff --git a/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidTagValidator.java b/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidTagValidator.java index 7c580dae..a2eb1b83 100644 --- a/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidTagValidator.java +++ b/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidTagValidator.java @@ -1,11 +1,13 @@ package hirs.swid; import hirs.swid.utils.HashSwid; +import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; +import javax.security.auth.x500.X500Principal; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.UnmarshalException; @@ -35,10 +37,16 @@ import javax.xml.validation.SchemaFactory; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.security.InvalidKeyException; import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; import java.security.PublicKey; +import java.security.SignatureException; +import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Iterator; +import java.util.List; /** * This class handles validating base Reference Integrity Manifest files. @@ -47,25 +55,39 @@ public class SwidTagValidator { private Unmarshaller unmarshaller; private String rimEventLog; private String certificateFile; + private String trustStore; /** * Setter for rimel file path. - * @param rimEventLog + * @param rimEventLog the rimel file */ public void setRimEventLog(String rimEventLog) { this.rimEventLog = rimEventLog; } + /** + * Setter for signing cert file. + * @param certificateFile the signing cert + */ public void setCertificateFile(String certificateFile) { this.certificateFile = certificateFile; } + /** + * Setter for the truststore file path. + * @param trustStore the truststore + */ + public void setTrustStore(String trustStore) { + this.trustStore = trustStore; + } + public SwidTagValidator() { try { JAXBContext jaxbContext = JAXBContext.newInstance(SwidTagConstants.SCHEMA_PACKAGE); unmarshaller = jaxbContext.createUnmarshaller(); rimEventLog = ""; certificateFile = ""; + trustStore = SwidTagConstants.DEFAULT_KEYSTORE_FILE; } catch (JAXBException e) { System.out.println("Error initializing JAXBContext: " + e.getMessage()); } @@ -119,6 +141,8 @@ public class SwidTagValidator { */ private boolean validateSignedXMLDocument(Document doc) { DOMValidateContext context = null; + CredentialParser cp = new CredentialParser(); + X509Certificate signingCert = null; boolean isValid = false; try { NodeList nodes = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature"); @@ -127,14 +151,14 @@ public class SwidTagValidator { } NodeList embeddedCert = doc.getElementsByTagName("X509Data"); if (embeddedCert.getLength() > 0) { - context = new DOMValidateContext(new SwidTagValidator.X509KeySelector(), nodes.item(0)); + context = new DOMValidateContext(new X509KeySelector(), nodes.item(0)); + signingCert = cp.parseCertFromPEMString(embeddedCert.item(1).getTextContent()); } else { - CredentialParser cp = new CredentialParser(); if (!certificateFile.isEmpty()) { - X509Certificate certificate = cp.parseCertFromPEM(certificateFile); - cp.setCertificate(certificate); + signingCert = cp.parseCertsFromPEM(certificateFile).get(0); + cp.setCertificate(signingCert); System.out.println(cp.getCertificateAuthorityInfoAccess()); - context = new DOMValidateContext(certificate.getPublicKey(), nodes.item(0)); + context = new DOMValidateContext(signingCert.getPublicKey(), nodes.item(0)); } else { System.out.println("Signing certificate not found for validation!"); System.exit(1); @@ -142,7 +166,8 @@ public class SwidTagValidator { } XMLSignatureFactory sigFactory = XMLSignatureFactory.getInstance("DOM"); XMLSignature signature = sigFactory.unmarshalXMLSignature(context); - isValid = signature.validate(context); + isValid = signature.validate(context) && validateCertChain(signingCert, + cp.parseCertsFromPEM(trustStore)); } catch (MarshalException | XMLSignatureException e) { System.out.println(e.getMessage()); } catch (Exception e) { @@ -152,6 +177,105 @@ public class SwidTagValidator { return isValid; } + /** + * This method validates the cert chain for a given certificate. The truststore is iterated + * over until a root CA is found. If a root CA is not found an error is returned describing + * the problem with validation. + * @param cert the certificate at the start of the chain + * @param trustStore the collection from which to find the chain of intermediate and root CAs + * @return true if the chain is valid; the false case throws the exception below + * @throws Exception if a valid chain is not found in the truststore + */ + private boolean validateCertChain(final X509Certificate cert, + final List<X509Certificate> trustStore) + throws Exception { + if (cert == null || trustStore == null) { + throw new Exception("Null certificate or truststore received"); + } else if (trustStore.size() == 0) { + throw new Exception("Truststore is empty"); + } + + final String INT_CA_ERROR = "Intermediate CA found, searching for root CA"; + String errorMessage = ""; + X509Certificate startOfChain = cert; + do { + for (X509Certificate trustedCert : trustStore) { + boolean isIssuer = areYouMyIssuer(startOfChain, trustedCert); + boolean isSigner = areYouMySigner(startOfChain, trustedCert); + if (isIssuer && isSigner) { + if (isSelfSigned(trustedCert)) { + return true; + } else { + startOfChain = trustedCert; + errorMessage = INT_CA_ERROR; + break; + } + } else { + if (!isIssuer) { + errorMessage = "Issuer cert not found"; + } else if (!isSigner) { + errorMessage = "Signing cert not found"; + } + } + } + } while (errorMessage.equals(INT_CA_ERROR)); + + throw new Exception(errorMessage); + } + + /** + * This method checks if cert's issuerDN matches issuer's subjectDN. + * @param cert the signed certificate + * @param issuer the signing certificate + * @return true if they match, false if not + * @throws Exception if either argument is null + */ + private boolean areYouMyIssuer(final X509Certificate cert, final X509Certificate issuer) + throws Exception { + if (cert == null || issuer == null) { + throw new Exception("Cannot verify issuer, null certificate received"); + } + X500Principal issuerDN = new X500Principal(cert.getIssuerX500Principal().getName()); + return issuer.getSubjectX500Principal().equals(issuerDN); + } + + /** + * This method checks if cert's signature matches signer's public key. + * @param cert the signed certificate + * @param signer the signing certificate + * @return true if they match + * @throws Exception if an error occurs or there is no match + */ + private boolean areYouMySigner(final X509Certificate cert, final X509Certificate signer) + throws Exception { + if (cert == null || signer == null) { + throw new Exception("Cannot verify signature, null certificate received"); + } + try { + cert.verify(signer.getPublicKey(), BouncyCastleProvider.PROVIDER_NAME); + return true; + } catch (NoSuchAlgorithmException e) { + throw new Exception("Signing algorithm in signing cert not supported"); + } catch (InvalidKeyException e) { + throw new Exception("Signing certificate key does not match signature"); + } catch (NoSuchProviderException e) { + throw new Exception("Error with BouncyCastleProvider: " + e.getMessage()); + } catch (SignatureException e) { + throw new Exception("Error with signature: " + e.getMessage()); + } catch (CertificateException e) { + throw new Exception("Encoding error: " + e.getMessage()); + } + } + + /** + * This method checks if a given certificate is self signed or not. + * @param cert the cert to check + * @return true if self signed, false if not + */ + private boolean isSelfSigned(final X509Certificate cert) { + return cert.getIssuerX500Principal().equals(cert.getSubjectX500Principal()); + } + public class X509KeySelector extends KeySelector { public KeySelectorResult select(KeyInfo keyinfo, KeySelector.Purpose purpose, From cfd04ae15a7c39e2743f7e18e5579ee4d63df76a Mon Sep 17 00:00:00 2001 From: chubtub <43381989+chubtub@users.noreply.github.com> Date: Tue, 6 Jul 2021 08:10:10 -0400 Subject: [PATCH 2/5] Rename keystore to truststore. Read truststore file for validation if given. --- .../main/java/hirs/swid/CredentialParser.java | 3 ++ .../src/main/java/hirs/swid/Main.java | 14 +++-- .../main/java/hirs/swid/SwidTagGateway.java | 8 +-- .../main/java/hirs/swid/SwidTagValidator.java | 9 ++++ .../main/java/hirs/swid/utils/Commander.java | 51 ++++--------------- .../java/hirs/swid/TestSwidTagGateway.java | 6 +-- .../test/resources/generated_no_cert.swidtag | 16 +++--- .../resources/generated_with_cert.swidtag | 50 +++++++++--------- 8 files changed, 69 insertions(+), 88 deletions(-) diff --git a/tools/tcg_rim_tool/src/main/java/hirs/swid/CredentialParser.java b/tools/tcg_rim_tool/src/main/java/hirs/swid/CredentialParser.java index 707e611b..41e25d56 100644 --- a/tools/tcg_rim_tool/src/main/java/hirs/swid/CredentialParser.java +++ b/tools/tcg_rim_tool/src/main/java/hirs/swid/CredentialParser.java @@ -107,9 +107,12 @@ public class CredentialParser { /** * This method returns the X509Certificate found in a PEM file. + * Unchecked typcase warnings are suppressed because the CertificateFactory + * implements X509Certificate objects explicitly. * @param filename pem file * @return a list containing all X509Certificates extracted */ + @SuppressWarnings("unchecked") private List<X509Certificate> parsePEMCertificates(String filename) { List<X509Certificate> certificates = null; FileInputStream fis = null; diff --git a/tools/tcg_rim_tool/src/main/java/hirs/swid/Main.java b/tools/tcg_rim_tool/src/main/java/hirs/swid/Main.java index 73cda086..60e6cb82 100644 --- a/tools/tcg_rim_tool/src/main/java/hirs/swid/Main.java +++ b/tools/tcg_rim_tool/src/main/java/hirs/swid/Main.java @@ -24,6 +24,7 @@ public class Main { String verifyFile = commander.getVerifyFile(); String rimel = commander.getRimEventLog(); String certificateFile = commander.getPublicCertificate(); + String trustStore = commander.getTruststoreFile(); if (!verifyFile.isEmpty()) { if (!rimel.isEmpty()) { validator.setRimEventLog(rimel); @@ -31,6 +32,9 @@ public class Main { if (!certificateFile.isEmpty()) { validator.setCertificateFile(certificateFile); } + if (!trustStore.isEmpty()) { + validator.setTrustStore(trustStore); + } try { validator.validateSwidTag(verifyFile); } catch (IOException e) { @@ -46,7 +50,7 @@ public class Main { System.out.println(commander.toString()); String createType = commander.getCreateType().toUpperCase(); String attributesFile = commander.getAttributesFile(); - String jksKeystoreFile = commander.getKeystoreFile(); + String jksTruststoreFile = commander.getTruststoreFile(); String certificateFile = commander.getPublicCertificate(); String privateKeyFile = commander.getPrivateKeyFile(); String rimEventLog = commander.getRimEventLog(); @@ -55,16 +59,16 @@ public class Main { if (!attributesFile.isEmpty()) { gateway.setAttributesFile(attributesFile); } - if (!jksKeystoreFile.isEmpty()) { + if (!jksTruststoreFile.isEmpty()) { gateway.setDefaultCredentials(true); - gateway.setJksKeystoreFile(jksKeystoreFile); + gateway.setJksTruststoreFile(jksTruststoreFile); } else if (!certificateFile.isEmpty() && !privateKeyFile.isEmpty()) { gateway.setDefaultCredentials(false); gateway.setPemCertificateFile(certificateFile); gateway.setPemPrivateKeyFile(privateKeyFile); } else { gateway.setDefaultCredentials(true); - gateway.setJksKeystoreFile(SwidTagConstants.DEFAULT_KEYSTORE_FILE); + gateway.setJksTruststoreFile(SwidTagConstants.DEFAULT_KEYSTORE_FILE); } if (rimEventLog.isEmpty()) { System.out.println("Error: a support RIM is required!"); @@ -74,6 +78,8 @@ public class Main { } gateway.generateSwidTag(commander.getOutFile()); break; + default: + System.out.println("No create type given, nothing to do"); } } } diff --git a/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidTagGateway.java b/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidTagGateway.java index 46a90026..8d76241e 100644 --- a/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidTagGateway.java +++ b/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidTagGateway.java @@ -80,7 +80,7 @@ public class SwidTagGateway { private Marshaller marshaller; private String attributesFile; private boolean defaultCredentials; - private String jksKeystoreFile; + private String jksTruststoreFile; private String pemPrivateKeyFile; private String pemCertificateFile; private String rimEventLog; @@ -120,9 +120,9 @@ public class SwidTagGateway { /** * Setter for JKS keystore file - * @param jksKeystoreFile + * @param jksTruststoreFile */ - public void setJksKeystoreFile(String jksKeystoreFile) { this.jksKeystoreFile = jksKeystoreFile; } + public void setJksTruststoreFile(String jksTruststoreFile) { this.jksTruststoreFile = jksTruststoreFile; } /** * Setter for private key file in PEM format @@ -440,7 +440,7 @@ public class SwidTagGateway { PublicKey publicKey; CredentialParser cp = new CredentialParser(); if (defaultCredentials) { - cp.parseJKSCredentials(jksKeystoreFile); + cp.parseJKSCredentials(jksTruststoreFile); privateKey = cp.getPrivateKey(); publicKey = cp.getPublicKey(); KeyName keyName = kiFactory.newKeyName(cp.getCertificateSubjectKeyIdentifier()); diff --git a/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidTagValidator.java b/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidTagValidator.java index a2eb1b83..c12f2a86 100644 --- a/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidTagValidator.java +++ b/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidTagValidator.java @@ -42,6 +42,7 @@ import java.security.Key; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PublicKey; +import java.security.Security; import java.security.SignatureException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; @@ -57,6 +58,14 @@ public class SwidTagValidator { private String certificateFile; private String trustStore; + /** + * Ensure that BouncyCastle is configured as a javax.security.Security provider, as this + * class expects it to be available. + */ + static { + Security.addProvider(new BouncyCastleProvider()); + } + /** * Setter for rimel file path. * @param rimEventLog the rimel file diff --git a/tools/tcg_rim_tool/src/main/java/hirs/swid/utils/Commander.java b/tools/tcg_rim_tool/src/main/java/hirs/swid/utils/Commander.java index d18282e5..70bbd3a9 100644 --- a/tools/tcg_rim_tool/src/main/java/hirs/swid/utils/Commander.java +++ b/tools/tcg_rim_tool/src/main/java/hirs/swid/utils/Commander.java @@ -1,11 +1,5 @@ package hirs.swid.utils; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.InvalidPathException; -import java.nio.file.Paths; - import com.beust.jcommander.Parameter; import hirs.swid.SwidTagConstants; @@ -29,9 +23,9 @@ public class Commander { @Parameter(names = {"-v", "--verify <path>"}, order = 3, description = "Specify a RIM file to verify.") private String verifyFile = ""; - @Parameter(names = {"--keystore <path>"}, order = 4, - description = "JKS keystore containing a private key to sign the base RIM created by the create function.") - private String keystoreFile = ""; + @Parameter(names = {"-t", "--truststore <path>"}, order = 4, + description = "PEM truststore to sign the base RIM created.") + private String truststoreFile = ""; @Parameter(names = {"-k", "--privateKeyFile <path>"}, order = 5, description = "File containing the private key used to sign the base RIM created by the create function.") private String privateKeyFile = ""; @@ -42,17 +36,7 @@ public class Commander { @Parameter(names = {"-l", "--rimel <path>"}, order = 7, description = "The TCG eventlog file to use as a support RIM. By default the last system eventlog will be used.") private String rimEventLog = ""; -/* - @Parameter(names = {"-t", "--rimpcr <path>"}, order = 7, - description = "The file containing TPM PCR values to use as a support RIM. By default the current platform TPM will be used.") - private String rimPcrs = ""; - //@Parameter(names = {}, order = 8, description = "") - private String toBeSigned = ""; - @Parameter(names = {"-s", "--addSignatureData <originalBaseRIM> <signatureFile> <outputFile>"}, order = 8, - description = "The signature data in <signatureFile> will be combined with the data in <originalBaseRIM>" + - "and written to <outputFile>, or will overwrite <originalBaseRIM> if <outputFile> is not given.") - private String signatureData = ""; -*/ + public boolean isHelp() { return help; } @@ -73,7 +57,7 @@ public class Commander { return verifyFile; } - public String getKeystoreFile() { return keystoreFile; } + public String getTruststoreFile() { return truststoreFile; } public String getPrivateKeyFile() { return privateKeyFile; @@ -84,19 +68,7 @@ public class Commander { } public String getRimEventLog() { return rimEventLog; } -/* - public String getRimPcrs() { - return rimPcrs; - } - public String getToBeSigned() { - return toBeSigned; - } - - public String getSignatureData() { - return signatureData; - } -*/ public String printHelpExamples() { StringBuilder sb = new StringBuilder(); sb.append("Create a base RIM using the values in attributes.json; " + @@ -108,8 +80,8 @@ public class Commander { sb.append("\t\t-c base -l support_rim.bin -k privateKey.pem -p cert.pem\n\n\n"); sb.append("Validate a base RIM using an external support RIM to override the payload file:\n\n"); sb.append("\t\t-v base_rim.swidtag -l support_rim.bin\n\n\n"); - sb.append("Validate a base RIM with an external cert:\n\n"); - sb.append("\t\t-v base_rim.swidtag -p signing_cert.pem\n\n\n"); + sb.append("Validate a base RIM (with an embedded cert) with a PEM truststore:\n\n"); + sb.append("\t\t-v base_rim.swidtag -t ca.crt\n\n\n"); return sb.toString(); @@ -120,8 +92,8 @@ public class Commander { sb.append("Using attributes file: " + this.getAttributesFile() + System.lineSeparator()); sb.append("Write to: " + this.getOutFile() + System.lineSeparator()); sb.append("Verify file: " + this.getVerifyFile() + System.lineSeparator()); - if (!this.getKeystoreFile().isEmpty()) { - sb.append("Keystore file: " + this.getKeystoreFile() + System.lineSeparator()); + if (!this.getTruststoreFile().isEmpty()) { + sb.append("Truststore file: " + this.getTruststoreFile() + System.lineSeparator()); } else if (!this.getPrivateKeyFile().isEmpty() && !this.getPublicCertificate().isEmpty()) { sb.append("Private key file: " + this.getPrivateKeyFile() + System.lineSeparator()); @@ -131,11 +103,6 @@ public class Commander { + System.lineSeparator()); } sb.append("Event log support RIM: " + this.getRimEventLog() + System.lineSeparator()); -/* - sb.append("TPM PCRs support RIM: " + getRimPcrs() + System.lineSeparator()); - sb.append("Base RIM to be signed: " + getToBeSigned() + System.lineSeparator()); - sb.append("External signature file: " + getSignatureData() + System.lineSeparator()); -*/ return sb.toString(); } } diff --git a/tools/tcg_rim_tool/src/test/java/hirs/swid/TestSwidTagGateway.java b/tools/tcg_rim_tool/src/test/java/hirs/swid/TestSwidTagGateway.java index 554f3ca5..9bf58ac8 100644 --- a/tools/tcg_rim_tool/src/test/java/hirs/swid/TestSwidTagGateway.java +++ b/tools/tcg_rim_tool/src/test/java/hirs/swid/TestSwidTagGateway.java @@ -1,13 +1,9 @@ package hirs.swid; -import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; -import java.nio.file.Paths; -import java.util.Scanner; -import java.net.URISyntaxException; import org.testng.Assert; import org.testng.annotations.BeforeClass; @@ -65,7 +61,7 @@ public class TestSwidTagGateway { @Test public void testCreateBaseWithoutCert() { gateway.setDefaultCredentials(true); - gateway.setJksKeystoreFile(JKS_KEYSTORE_FILE); + gateway.setJksTruststoreFile(JKS_KEYSTORE_FILE); gateway.generateSwidTag(DEFAULT_OUTPUT); expectedFile = TestSwidTagGateway.class.getClassLoader().getResourceAsStream(DEFAULT_NO_CERT); Assert.assertTrue(compareFileBytesToExpectedFile(DEFAULT_OUTPUT)); diff --git a/tools/tcg_rim_tool/src/test/resources/generated_no_cert.swidtag b/tools/tcg_rim_tool/src/test/resources/generated_no_cert.swidtag index 51a45d76..7fb01eee 100644 --- a/tools/tcg_rim_tool/src/test/resources/generated_no_cert.swidtag +++ b/tools/tcg_rim_tool/src/test/resources/generated_no_cert.swidtag @@ -20,19 +20,19 @@ <DigestValue>97uWB7zSsO5WaGbrcQrlKd1Bju0aDTjK1/ktUYBje8A=</DigestValue> </Reference> </SignedInfo> - <SignatureValue>N1YtTeo2Ryuj+CtlXIpICEay+ni7vt8+4J7tAsYpa3efnLwtea69PIqEylPWm9LdA8Eo8XDdpgxV -7h3hi2LTOU+Wxq3bLiLamo99T1EtIwl+ZPcOv8bsfEkmShHdMC0dlfcj6r7x4tc0XkNAhhJgfRNz -FsmPWKJb6FYcsHFbHO/Uw1hSokbAGcWWTshEOqvKHMa8UVkrFMUPnrnMtdyJqZlhDBrZHNi4rWth -8TjlUnQVSCF9s9I04FxJ1cUAdeVMHtXKM8Pvjv68PaJMJK73dW5Yd3SbcgoKLesf/HPWeeZL0rr4 + <SignatureValue>N1YtTeo2Ryuj+CtlXIpICEay+ni7vt8+4J7tAsYpa3efnLwtea69PIqEylPWm9LdA8Eo8XDdpgxV +7h3hi2LTOU+Wxq3bLiLamo99T1EtIwl+ZPcOv8bsfEkmShHdMC0dlfcj6r7x4tc0XkNAhhJgfRNz +FsmPWKJb6FYcsHFbHO/Uw1hSokbAGcWWTshEOqvKHMa8UVkrFMUPnrnMtdyJqZlhDBrZHNi4rWth +8TjlUnQVSCF9s9I04FxJ1cUAdeVMHtXKM8Pvjv68PaJMJK73dW5Yd3SbcgoKLesf/HPWeeZL0rr4 TNjlqJ/wq61Ons45MFG9bIscVbnd+XxFHx8Skw==</SignatureValue> <KeyInfo> <KeyName>2fdeb8e7d030a2209daa01861a964fedecf2bcc1</KeyName> <KeyValue> <RSAKeyValue> - <Modulus>p3WVYaRJG7EABjbAdqDYZXFSTV1nHY9Ol9A5+W8t5xwBXBryZCGWxERGr5AryKWPxd+qzjj+cFpx -xkM6N18jEhQIx/CEZePEJqpluBO5w2wTEOe7hqtMatqgDDMeDRxUuIpP8LGP00vh1wyDFFew90d9 -dvT3bcLvFh3a3ap9bTm6aBqPup5CXpzrwIU2wZfgkDytYVBm+8bHkMaUrgpNyM+5BAg2zl/Fqw0q -otjaGr7PzbH+urCvaGbKLMPoWkVLIgAE8Qw98HTfoYSFHC7VYQySrzIinaOBFSgViR72kHemH2lW + <Modulus>p3WVYaRJG7EABjbAdqDYZXFSTV1nHY9Ol9A5+W8t5xwBXBryZCGWxERGr5AryKWPxd+qzjj+cFpx +xkM6N18jEhQIx/CEZePEJqpluBO5w2wTEOe7hqtMatqgDDMeDRxUuIpP8LGP00vh1wyDFFew90d9 +dvT3bcLvFh3a3ap9bTm6aBqPup5CXpzrwIU2wZfgkDytYVBm+8bHkMaUrgpNyM+5BAg2zl/Fqw0q +otjaGr7PzbH+urCvaGbKLMPoWkVLIgAE8Qw98HTfoYSFHC7VYQySrzIinaOBFSgViR72kHemH2lW jDQeHiY0VIoPik/jVVIpjWe6zzeZ2S66Q/LmjQ==</Modulus> <Exponent>AQAB</Exponent> </RSAKeyValue> diff --git a/tools/tcg_rim_tool/src/test/resources/generated_with_cert.swidtag b/tools/tcg_rim_tool/src/test/resources/generated_with_cert.swidtag index 38a0cf0a..313cae3a 100644 --- a/tools/tcg_rim_tool/src/test/resources/generated_with_cert.swidtag +++ b/tools/tcg_rim_tool/src/test/resources/generated_with_cert.swidtag @@ -20,39 +20,39 @@ <DigestValue>97uWB7zSsO5WaGbrcQrlKd1Bju0aDTjK1/ktUYBje8A=</DigestValue> </Reference> </SignedInfo> - <SignatureValue>N1YtTeo2Ryuj+CtlXIpICEay+ni7vt8+4J7tAsYpa3efnLwtea69PIqEylPWm9LdA8Eo8XDdpgxV -7h3hi2LTOU+Wxq3bLiLamo99T1EtIwl+ZPcOv8bsfEkmShHdMC0dlfcj6r7x4tc0XkNAhhJgfRNz -FsmPWKJb6FYcsHFbHO/Uw1hSokbAGcWWTshEOqvKHMa8UVkrFMUPnrnMtdyJqZlhDBrZHNi4rWth -8TjlUnQVSCF9s9I04FxJ1cUAdeVMHtXKM8Pvjv68PaJMJK73dW5Yd3SbcgoKLesf/HPWeeZL0rr4 + <SignatureValue>N1YtTeo2Ryuj+CtlXIpICEay+ni7vt8+4J7tAsYpa3efnLwtea69PIqEylPWm9LdA8Eo8XDdpgxV +7h3hi2LTOU+Wxq3bLiLamo99T1EtIwl+ZPcOv8bsfEkmShHdMC0dlfcj6r7x4tc0XkNAhhJgfRNz +FsmPWKJb6FYcsHFbHO/Uw1hSokbAGcWWTshEOqvKHMa8UVkrFMUPnrnMtdyJqZlhDBrZHNi4rWth +8TjlUnQVSCF9s9I04FxJ1cUAdeVMHtXKM8Pvjv68PaJMJK73dW5Yd3SbcgoKLesf/HPWeeZL0rr4 TNjlqJ/wq61Ons45MFG9bIscVbnd+XxFHx8Skw==</SignatureValue> <KeyInfo> <X509Data> <X509SubjectName>CN=example.RIM.signer,OU=PCClient,O=Example,ST=VA,C=US</X509SubjectName> - <X509Certificate>MIID2jCCAsKgAwIBAgIJAP0uwoNdwZDFMA0GCSqGSIb3DQEBCwUAMFMxCzAJBgNVBAYTAlVTMQsw -CQYDVQQIDAJWQTEQMA4GA1UECgwHRXhhbXBsZTERMA8GA1UECwwIUENDbGllbnQxEjAQBgNVBAMM -CUV4YW1wbGVDQTAeFw0yMDA3MjEyMTQ1MDBaFw0zMDA1MzAyMTQ1MDBaMFwxCzAJBgNVBAYTAlVT -MQswCQYDVQQIDAJWQTEQMA4GA1UECgwHRXhhbXBsZTERMA8GA1UECwwIUENDbGllbnQxGzAZBgNV -BAMMEmV4YW1wbGUuUklNLnNpZ25lcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKd1 -lWGkSRuxAAY2wHag2GVxUk1dZx2PTpfQOflvLeccAVwa8mQhlsRERq+QK8ilj8Xfqs44/nBaccZD -OjdfIxIUCMfwhGXjxCaqZbgTucNsExDnu4arTGraoAwzHg0cVLiKT/Cxj9NL4dcMgxRXsPdHfXb0 -923C7xYd2t2qfW05umgaj7qeQl6c68CFNsGX4JA8rWFQZvvGx5DGlK4KTcjPuQQINs5fxasNKqLY -2hq+z82x/rqwr2hmyizD6FpFSyIABPEMPfB036GEhRwu1WEMkq8yIp2jgRUoFYke9pB3ph9pVow0 -Hh4mNFSKD4pP41VSKY1nus83mdkuukPy5o0CAwEAAaOBpzCBpDAdBgNVHQ4EFgQUL96459AwoiCd -qgGGGpZP7ezyvMEwHwYDVR0jBBgwFoAURqG47dumcV/Q0ud6ijxdbprDljgwCQYDVR0TBAIwADAL -BgNVHQ8EBAMCBsAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwNQYIKwYBBQUHAQEEKTAnMCUGCCsGAQUF -BzAChhlodHRwczovL2V4YW1wbGUuY29tL2NlcnRzMA0GCSqGSIb3DQEBCwUAA4IBAQDpKx5oQlkS -11cg7Qp58BmCvjCzFpof+qYePooJsD3i5SwKfRTa2CkDMww9qrwBK7G60y7jhe5InKTdqIlVqaji -5ZImR0QMKTtk7zt9AJ9EaEzKxfDiE/qX34KxNe4ZmbvLH8N+BSujQXMMi56zGjW469Y/rbDMG8uU -1dq3zqhO5b+dUr1ecdkYLgzxu6O+oWy5JpVibmcjvNezJsUtjc+km2FYm24vU3/fCNzZ2z0EHQES -cIEQ5OqfpdFrV3De238RhMH6J4xePSidnFpfBc6FrdyDI1A8eRFz36I4xfVL3ZnJP/+j+NE4q6yz + <X509Certificate>MIID2jCCAsKgAwIBAgIJAP0uwoNdwZDFMA0GCSqGSIb3DQEBCwUAMFMxCzAJBgNVBAYTAlVTMQsw +CQYDVQQIDAJWQTEQMA4GA1UECgwHRXhhbXBsZTERMA8GA1UECwwIUENDbGllbnQxEjAQBgNVBAMM +CUV4YW1wbGVDQTAeFw0yMDA3MjEyMTQ1MDBaFw0zMDA1MzAyMTQ1MDBaMFwxCzAJBgNVBAYTAlVT +MQswCQYDVQQIDAJWQTEQMA4GA1UECgwHRXhhbXBsZTERMA8GA1UECwwIUENDbGllbnQxGzAZBgNV +BAMMEmV4YW1wbGUuUklNLnNpZ25lcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKd1 +lWGkSRuxAAY2wHag2GVxUk1dZx2PTpfQOflvLeccAVwa8mQhlsRERq+QK8ilj8Xfqs44/nBaccZD +OjdfIxIUCMfwhGXjxCaqZbgTucNsExDnu4arTGraoAwzHg0cVLiKT/Cxj9NL4dcMgxRXsPdHfXb0 +923C7xYd2t2qfW05umgaj7qeQl6c68CFNsGX4JA8rWFQZvvGx5DGlK4KTcjPuQQINs5fxasNKqLY +2hq+z82x/rqwr2hmyizD6FpFSyIABPEMPfB036GEhRwu1WEMkq8yIp2jgRUoFYke9pB3ph9pVow0 +Hh4mNFSKD4pP41VSKY1nus83mdkuukPy5o0CAwEAAaOBpzCBpDAdBgNVHQ4EFgQUL96459AwoiCd +qgGGGpZP7ezyvMEwHwYDVR0jBBgwFoAURqG47dumcV/Q0ud6ijxdbprDljgwCQYDVR0TBAIwADAL +BgNVHQ8EBAMCBsAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwNQYIKwYBBQUHAQEEKTAnMCUGCCsGAQUF +BzAChhlodHRwczovL2V4YW1wbGUuY29tL2NlcnRzMA0GCSqGSIb3DQEBCwUAA4IBAQDpKx5oQlkS +11cg7Qp58BmCvjCzFpof+qYePooJsD3i5SwKfRTa2CkDMww9qrwBK7G60y7jhe5InKTdqIlVqaji +5ZImR0QMKTtk7zt9AJ9EaEzKxfDiE/qX34KxNe4ZmbvLH8N+BSujQXMMi56zGjW469Y/rbDMG8uU +1dq3zqhO5b+dUr1ecdkYLgzxu6O+oWy5JpVibmcjvNezJsUtjc+km2FYm24vU3/fCNzZ2z0EHQES +cIEQ5OqfpdFrV3De238RhMH6J4xePSidnFpfBc6FrdyDI1A8eRFz36I4xfVL3ZnJP/+j+NE4q6yz 5VGvm0npLO394ZihtsI1sRAR8ORJ</X509Certificate> </X509Data> <KeyValue> <RSAKeyValue> - <Modulus>p3WVYaRJG7EABjbAdqDYZXFSTV1nHY9Ol9A5+W8t5xwBXBryZCGWxERGr5AryKWPxd+qzjj+cFpx -xkM6N18jEhQIx/CEZePEJqpluBO5w2wTEOe7hqtMatqgDDMeDRxUuIpP8LGP00vh1wyDFFew90d9 -dvT3bcLvFh3a3ap9bTm6aBqPup5CXpzrwIU2wZfgkDytYVBm+8bHkMaUrgpNyM+5BAg2zl/Fqw0q -otjaGr7PzbH+urCvaGbKLMPoWkVLIgAE8Qw98HTfoYSFHC7VYQySrzIinaOBFSgViR72kHemH2lW + <Modulus>p3WVYaRJG7EABjbAdqDYZXFSTV1nHY9Ol9A5+W8t5xwBXBryZCGWxERGr5AryKWPxd+qzjj+cFpx +xkM6N18jEhQIx/CEZePEJqpluBO5w2wTEOe7hqtMatqgDDMeDRxUuIpP8LGP00vh1wyDFFew90d9 +dvT3bcLvFh3a3ap9bTm6aBqPup5CXpzrwIU2wZfgkDytYVBm+8bHkMaUrgpNyM+5BAg2zl/Fqw0q +otjaGr7PzbH+urCvaGbKLMPoWkVLIgAE8Qw98HTfoYSFHC7VYQySrzIinaOBFSgViR72kHemH2lW jDQeHiY0VIoPik/jVVIpjWe6zzeZ2S66Q/LmjQ==</Modulus> <Exponent>AQAB</Exponent> </RSAKeyValue> From ce090558a642f579d3c7d027318085f9ed1428c5 Mon Sep 17 00:00:00 2001 From: chubtub <43381989+chubtub@users.noreply.github.com> Date: Tue, 13 Jul 2021 10:25:35 -0400 Subject: [PATCH 3/5] Parse public key or signing cert from XML properly. Disallow a single cert for validation. Update javadocs --- .../main/java/hirs/swid/CredentialParser.java | 2 + .../src/main/java/hirs/swid/Main.java | 14 +- .../main/java/hirs/swid/SwidTagValidator.java | 154 ++++++++++++------ .../main/java/hirs/swid/utils/Commander.java | 35 ++-- .../java/hirs/swid/TestSwidTagGateway.java | 8 +- 5 files changed, 133 insertions(+), 80 deletions(-) diff --git a/tools/tcg_rim_tool/src/main/java/hirs/swid/CredentialParser.java b/tools/tcg_rim_tool/src/main/java/hirs/swid/CredentialParser.java index 41e25d56..a48472a9 100644 --- a/tools/tcg_rim_tool/src/main/java/hirs/swid/CredentialParser.java +++ b/tools/tcg_rim_tool/src/main/java/hirs/swid/CredentialParser.java @@ -86,7 +86,9 @@ public class CredentialParser { try { CertificateFactory factory = CertificateFactory.getInstance(X509); InputStream inputStream = new ByteArrayInputStream((CERTIFICATE_HEADER + + System.lineSeparator() + pemString + + System.lineSeparator() + CERTIFICATE_FOOTER).getBytes()); return (X509Certificate) factory.generateCertificate(inputStream); } catch (CertificateException e) { diff --git a/tools/tcg_rim_tool/src/main/java/hirs/swid/Main.java b/tools/tcg_rim_tool/src/main/java/hirs/swid/Main.java index 60e6cb82..fec6e298 100644 --- a/tools/tcg_rim_tool/src/main/java/hirs/swid/Main.java +++ b/tools/tcg_rim_tool/src/main/java/hirs/swid/Main.java @@ -29,18 +29,14 @@ public class Main { if (!rimel.isEmpty()) { validator.setRimEventLog(rimel); } - if (!certificateFile.isEmpty()) { - validator.setCertificateFile(certificateFile); - } if (!trustStore.isEmpty()) { - validator.setTrustStore(trustStore); + validator.setTrustStoreFile(trustStore); } - try { - validator.validateSwidTag(verifyFile); - } catch (IOException e) { - System.out.println("Error validating RIM file: " + e.getMessage()); - System.exit(1); + if (!certificateFile.isEmpty()) { + System.out.println("A single cert cannot be used for verification. " + + "The signing cert will be searched for in the trust store."); } + validator.validateSwidTag(verifyFile); } else { System.out.println("Need a RIM file to validate!"); System.exit(1); diff --git a/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidTagValidator.java b/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidTagValidator.java index c12f2a86..03d9490f 100644 --- a/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidTagValidator.java +++ b/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidTagValidator.java @@ -4,6 +4,7 @@ import hirs.swid.utils.HashSwid; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.w3c.dom.Document; import org.w3c.dom.Element; +import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; @@ -35,10 +36,13 @@ import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.math.BigInteger; import java.security.InvalidKeyException; import java.security.Key; +import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PublicKey; @@ -46,6 +50,10 @@ import java.security.Security; import java.security.SignatureException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.RSAPublicKeySpec; +import java.util.Arrays; +import java.util.Base64; import java.util.Iterator; import java.util.List; @@ -56,7 +64,7 @@ public class SwidTagValidator { private Unmarshaller unmarshaller; private String rimEventLog; private String certificateFile; - private String trustStore; + private String trustStoreFile; /** * Ensure that BouncyCastle is configured as a javax.security.Security provider, as this @@ -74,20 +82,12 @@ public class SwidTagValidator { this.rimEventLog = rimEventLog; } - /** - * Setter for signing cert file. - * @param certificateFile the signing cert - */ - public void setCertificateFile(String certificateFile) { - this.certificateFile = certificateFile; - } - /** * Setter for the truststore file path. - * @param trustStore the truststore + * @param trustStoreFile the truststore */ - public void setTrustStore(String trustStore) { - this.trustStore = trustStore; + public void setTrustStoreFile(String trustStoreFile) { + this.trustStoreFile = trustStoreFile; } public SwidTagValidator() { @@ -96,7 +96,7 @@ public class SwidTagValidator { unmarshaller = jaxbContext.createUnmarshaller(); rimEventLog = ""; certificateFile = ""; - trustStore = SwidTagConstants.DEFAULT_KEYSTORE_FILE; + trustStoreFile = SwidTagConstants.DEFAULT_KEYSTORE_FILE; } catch (JAXBException e) { System.out.println("Error initializing JAXBContext: " + e.getMessage()); } @@ -108,7 +108,7 @@ public class SwidTagValidator { * * @param path the location of the file to be validated */ - public boolean validateSwidTag(String path) throws IOException { + public boolean validateSwidTag(String path) { Document document = unmarshallSwidTag(path); Element softwareIdentity = (Element) document.getElementsByTagName("SoftwareIdentity").item(0); StringBuilder si = new StringBuilder("Base RIM detected:\n"); @@ -144,55 +144,94 @@ public class SwidTagValidator { } /** - * This method validates a Document with a signature element. - * - * @param doc + * This method validates a signed XML document. + * First, the signing certificate is either parsed from the embedded X509Certificate element or + * generated from the Modulus and Exponent elements. + * Next, the signature is inspected for two things: + * 1. valid signature + * 2. valid certificate chain + * @param doc XML document + * @return true if both the signature and cert chain are valid; false otherwise */ private boolean validateSignedXMLDocument(Document doc) { - DOMValidateContext context = null; - CredentialParser cp = new CredentialParser(); - X509Certificate signingCert = null; - boolean isValid = false; try { + DOMValidateContext context; + CredentialParser cp = new CredentialParser(); + X509Certificate signingCert = null; + List<X509Certificate> trustStore = cp.parseCertsFromPEM(trustStoreFile); NodeList nodes = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature"); if (nodes.getLength() == 0) { throw new Exception("Signature element not found!"); } - NodeList embeddedCert = doc.getElementsByTagName("X509Data"); + NodeList embeddedCert = doc.getElementsByTagName("X509Certificate"); if (embeddedCert.getLength() > 0) { context = new DOMValidateContext(new X509KeySelector(), nodes.item(0)); - signingCert = cp.parseCertFromPEMString(embeddedCert.item(1).getTextContent()); + signingCert = cp.parseCertFromPEMString(embeddedCert.item(0).getTextContent()); } else { - if (!certificateFile.isEmpty()) { - signingCert = cp.parseCertsFromPEM(certificateFile).get(0); - cp.setCertificate(signingCert); - System.out.println(cp.getCertificateAuthorityInfoAccess()); - context = new DOMValidateContext(signingCert.getPublicKey(), nodes.item(0)); - } else { + PublicKey pk = getPKFromKeyValue(doc); + for (X509Certificate trustedCert : trustStore) { + if (Arrays.equals(pk.getEncoded(), trustedCert.getPublicKey().getEncoded())) { + signingCert = trustedCert; + } + } + if (signingCert == null) { System.out.println("Signing certificate not found for validation!"); System.exit(1); } + context = new DOMValidateContext(signingCert.getPublicKey(), nodes.item(0)); } + cp.setCertificate(signingCert); + System.out.println(cp.getCertificateAuthorityInfoAccess()); XMLSignatureFactory sigFactory = XMLSignatureFactory.getInstance("DOM"); XMLSignature signature = sigFactory.unmarshalXMLSignature(context); - isValid = signature.validate(context) && validateCertChain(signingCert, - cp.parseCertsFromPEM(trustStore)); + boolean signatureIsValid = signature.validate(context); + boolean certChainIsValid = validateCertChain(signingCert, trustStore); + System.out.println("Signature validity: " + signatureIsValid); + System.out.println("Cert chain validity: " + certChainIsValid); + return signatureIsValid && certChainIsValid; + } catch (FileNotFoundException e) { + System.out.println("Error parsing truststore: " + e.getMessage()); + } catch (NoSuchAlgorithmException e) { + System.out.println("Error instantiating a KeyFactory to generate pk: " + + e.getMessage()); + } catch (InvalidKeySpecException e) { + System.out.println("Failed to generate a pk from swidtag: " + e.getMessage()); } catch (MarshalException | XMLSignatureException e) { System.out.println(e.getMessage()); } catch (Exception e) { System.out.println(e.getMessage()); } - return isValid; + return false; + } + + /** + * This method generates a public key from the modulus and exponent elements + * parsed from a signed swidtag. + * @param doc Document object containing the swidtag + * @return the generated PublicKey object + * @throws NoSuchAlgorithmException if the KeyFactory instance fails to instantiate + * @throws InvalidKeySpecException if the KeyFactory fails to generate the public key + */ + private PublicKey getPKFromKeyValue(Document doc) + throws NoSuchAlgorithmException, InvalidKeySpecException { + Node modulusElement = doc.getElementsByTagName("Modulus").item(0); + Node exponentElement = doc.getElementsByTagName("Exponent").item(0); + BigInteger modulus = new BigInteger( + Base64.getMimeDecoder().decode(modulusElement.getTextContent())); + BigInteger exponent = new BigInteger( + Base64.getMimeDecoder().decode(exponentElement.getTextContent())); + RSAPublicKeySpec keySpec = new RSAPublicKeySpec(modulus, exponent); + KeyFactory factory = KeyFactory.getInstance("RSA"); + return factory.generatePublic(keySpec); } /** * This method validates the cert chain for a given certificate. The truststore is iterated - * over until a root CA is found. If a root CA is not found an error is returned describing - * the problem with validation. + * over until a root CA is found, otherwise an error is returned. * @param cert the certificate at the start of the chain - * @param trustStore the collection from which to find the chain of intermediate and root CAs - * @return true if the chain is valid; the false case throws the exception below + * @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, @@ -229,7 +268,7 @@ public class SwidTagValidator { } } while (errorMessage.equals(INT_CA_ERROR)); - throw new Exception(errorMessage); + throw new Exception("Error while validating cert chain: " + errorMessage); } /** @@ -285,11 +324,26 @@ public class SwidTagValidator { return cert.getIssuerX500Principal().equals(cert.getSubjectX500Principal()); } + /** + * This internal class handles parsing the public key from a KeyInfo element. + */ public class X509KeySelector extends KeySelector { - public KeySelectorResult select(KeyInfo keyinfo, - KeySelector.Purpose purpose, - AlgorithmMethod algorithm, - XMLCryptoContext context) throws KeySelectorException { + /** + * This method extracts a public key from either an X509Certificate element + * or a KeyValue element. If the public key's algorithm matches the declared + * algorithm it is returned in a KeySelecctorResult. + * @param keyinfo the KeyInfo element + * @param purpose + * @param algorithm the encapsulating signature's declared signing algorithm + * @param context + * @return a KeySelectorResult if the public key's algorithm matches the declared algorithm + * @throws KeySelectorException if the algorithms do not match + */ + public KeySelectorResult select(final KeyInfo keyinfo, + final KeySelector.Purpose purpose, + final AlgorithmMethod algorithm, + final XMLCryptoContext context) + throws KeySelectorException { Iterator keyinfoItr = keyinfo.getContent().iterator(); while(keyinfoItr.hasNext()) { XMLStructure element = (XMLStructure) keyinfoItr.next(); @@ -307,16 +361,18 @@ public class SwidTagValidator { } } } - throw new KeySelectorException("No key found!"); } + /** + * This method checks that the signature and public key algorithms match. + * @param uri to match the signature algorithm + * @param name to match the public key algorithm + * @return true if both match, false otherwise + */ public boolean areAlgorithmsEqual(String uri, String name) { - if (uri.equals(SwidTagConstants.SIGNATURE_ALGORITHM_RSA_SHA256) && name.equalsIgnoreCase("RSA")) { - return true; - } else { - return false; - } + return uri.equals(SwidTagConstants.SIGNATURE_ALGORITHM_RSA_SHA256) + && name.equalsIgnoreCase("RSA"); } private class RIMKeySelectorResult implements KeySelectorResult { @@ -376,8 +432,8 @@ public class SwidTagValidator { /** * This method strips all whitespace from an xml file, including indents and spaces * added for human-readability. - * @param path - * @return + * @param path to the xml file + * @return Document object without whitespace */ private Document removeXMLWhitespace(String path) throws IOException { TransformerFactory tf = TransformerFactory.newInstance(); diff --git a/tools/tcg_rim_tool/src/main/java/hirs/swid/utils/Commander.java b/tools/tcg_rim_tool/src/main/java/hirs/swid/utils/Commander.java index 70bbd3a9..2454f299 100644 --- a/tools/tcg_rim_tool/src/main/java/hirs/swid/utils/Commander.java +++ b/tools/tcg_rim_tool/src/main/java/hirs/swid/utils/Commander.java @@ -13,28 +13,31 @@ public class Commander { private boolean help; @Parameter(names = {"-c", "--create \"base\""}, order = 0, description = "The type of RIM to create. A base RIM will be created by default.") - private String createType = "";//other possible values: "eventlog" and "pcr" + private String createType = ""; @Parameter(names = {"-a", "--attributes <path>"}, order = 1, - description = "The configuration file holding attributes to populate the base RIM with.") + description = "The configuration file holding attributes " + + "to populate the base RIM with.") private String attributesFile = ""; @Parameter(names = {"-o", "--out <path>"}, order = 2, - description = "The file to write the RIM out to. The RIM will be written to stdout by default.") + description = "The file to write the RIM out to. " + + "The RIM will be written to stdout by default.") private String outFile = ""; @Parameter(names = {"-v", "--verify <path>"}, order = 3, description = "Specify a RIM file to verify.") private String verifyFile = ""; @Parameter(names = {"-t", "--truststore <path>"}, order = 4, - description = "PEM truststore to sign the base RIM created.") - private String truststoreFile = ""; + description = "The truststore to sign the base RIM created " + + "or to validate the signed base RIM.") + private String truststoreFile = "/opt/hirs/rimtool/keystore.jks"; @Parameter(names = {"-k", "--privateKeyFile <path>"}, order = 5, - description = "File containing the private key used to sign the base RIM created by the create function.") + description = "The private key used to sign the base RIM created by this tool.") private String privateKeyFile = ""; @Parameter(names = {"-p", "--publicCertificate <path>"}, order = 6, - description = "The public key certificate used to verify a RIM file or to embed in a signed RIM. " + - "A signed RIM generated by this tool by default will not show the signing certificate without this parameter present.") + description = "The public key certificate to embed in the base RIM created by " + + "this tool.") private String publicCertificate = ""; @Parameter(names = {"-l", "--rimel <path>"}, order = 7, - description = "The TCG eventlog file to use as a support RIM. By default the last system eventlog will be used.") + description = "The TCG eventlog file to use as a support RIM.") private String rimEventLog = ""; public boolean isHelp() { @@ -75,15 +78,17 @@ public class Commander { "sign it with the default keystore, alias, and password;\n"); sb.append("and write the data to base_rim.swidtag:\n\n"); sb.append("\t\t-c base -a attributes.json -l support_rim.bin -o base_rim.swidtag\n\n\n"); - sb.append("Create a base RIM using the default attribute values; sign it using privateKey.pem;\n"); - sb.append("and write the data to console output, to embed cert.pem in the signature block:\n\n"); + sb.append("Create a base RIM using the default attribute values; "); + sb.append("sign it using privateKey.pem; embed cert.pem in the signature block; "); + sb.append("and write the data to console output:\n\n"); sb.append("\t\t-c base -l support_rim.bin -k privateKey.pem -p cert.pem\n\n\n"); - sb.append("Validate a base RIM using an external support RIM to override the payload file:\n\n"); + sb.append("Validate a base RIM using an external support RIM to override the "); + sb.append("payload file:\n\n"); sb.append("\t\t-v base_rim.swidtag -l support_rim.bin\n\n\n"); - sb.append("Validate a base RIM (with an embedded cert) with a PEM truststore:\n\n"); + sb.append("Validate a base RIM with its own payload file and a PEM truststore "); + sb.append("containing the signing cert:\n\n"); sb.append("\t\t-v base_rim.swidtag -t ca.crt\n\n\n"); - return sb.toString(); } public String toString() { @@ -99,7 +104,7 @@ public class Commander { sb.append("Private key file: " + this.getPrivateKeyFile() + System.lineSeparator()); sb.append("Public certificate: " + this.getPublicCertificate() + System.lineSeparator()); } else { - sb.append("Keystore file: default (" + SwidTagConstants.DEFAULT_KEYSTORE_FILE + ")" + sb.append("Truststore file: default (" + SwidTagConstants.DEFAULT_KEYSTORE_FILE + ")" + System.lineSeparator()); } sb.append("Event log support RIM: " + this.getRimEventLog() + System.lineSeparator()); diff --git a/tools/tcg_rim_tool/src/test/java/hirs/swid/TestSwidTagGateway.java b/tools/tcg_rim_tool/src/test/java/hirs/swid/TestSwidTagGateway.java index 9bf58ac8..02826f0a 100644 --- a/tools/tcg_rim_tool/src/test/java/hirs/swid/TestSwidTagGateway.java +++ b/tools/tcg_rim_tool/src/test/java/hirs/swid/TestSwidTagGateway.java @@ -75,13 +75,7 @@ public class TestSwidTagGateway { public void testValidateSwidTag() { String filepath = TestSwidTagGateway.class.getClassLoader().getResource(DEFAULT_WITH_CERT).getPath(); System.out.println("Validating file at " + filepath); - try { - Assert.assertTrue(validator.validateSwidTag(filepath)); - } catch (IOException e) { - Assert.fail("Invalid swidtag!"); - } catch (NullPointerException e) { - Assert.fail("Cannot find file: " + filepath); - } + Assert.assertTrue(validator.validateSwidTag(filepath)); } /** From 8958085b607d16af8755524a2c2ccb74b3c4b4ab Mon Sep 17 00:00:00 2001 From: chubtub <43381989+chubtub@users.noreply.github.com> Date: Fri, 16 Jul 2021 16:46:31 -0400 Subject: [PATCH 4/5] Search for signing cert by SKID instead of PK --- .../main/java/hirs/swid/CredentialParser.java | 19 +++++++++++-- .../main/java/hirs/swid/SwidTagValidator.java | 27 +++---------------- 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/tools/tcg_rim_tool/src/main/java/hirs/swid/CredentialParser.java b/tools/tcg_rim_tool/src/main/java/hirs/swid/CredentialParser.java index a48472a9..de208c87 100644 --- a/tools/tcg_rim_tool/src/main/java/hirs/swid/CredentialParser.java +++ b/tools/tcg_rim_tool/src/main/java/hirs/swid/CredentialParser.java @@ -273,8 +273,8 @@ public class CredentialParser { } /** - * This method returns the subjectKeyIdentifier from an X509Certificate. - * @return + * This method returns the subjectKeyIdentifier from the local X509Certificate. + * @return the String representation of the subjectKeyIdentifier * @throws IOException */ public String getCertificateSubjectKeyIdentifier() throws IOException { @@ -285,4 +285,19 @@ public class CredentialParser { } return decodedValue.substring(1);//Drop the # at the beginning of the string } + + /** + * This method returns the subjectKeyIdentifier from a given X509Certificate. + * @param certificate the cert to pull the subjectKeyIdentifier from + * @return the String representation of the subjectKeyIdentifier + * @throws IOException + */ + public String getCertificateSubjectKeyIdentifier(X509Certificate certificate) throws IOException { + String decodedValue = null; + byte[] extension = certificate.getExtensionValue(Extension.subjectKeyIdentifier.getId()); + if (extension != null && extension.length > 0) { + decodedValue = JcaX509ExtensionUtils.parseExtensionValue(extension).toString(); + } + return decodedValue.substring(1);//Drop the # at the beginning of the string + } } diff --git a/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidTagValidator.java b/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidTagValidator.java index 03d9490f..9af5f446 100644 --- a/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidTagValidator.java +++ b/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidTagValidator.java @@ -168,10 +168,12 @@ public class SwidTagValidator { context = new DOMValidateContext(new X509KeySelector(), nodes.item(0)); signingCert = cp.parseCertFromPEMString(embeddedCert.item(0).getTextContent()); } else { - PublicKey pk = getPKFromKeyValue(doc); + String skId = doc.getElementsByTagName("KeyName").item(0).getTextContent(); for (X509Certificate trustedCert : trustStore) { - if (Arrays.equals(pk.getEncoded(), trustedCert.getPublicKey().getEncoded())) { + String trustedSkId = cp.getCertificateSubjectKeyIdentifier(trustedCert); + if (skId.equals(trustedSkId)) { signingCert = trustedCert; + break; } } if (signingCert == null) { @@ -205,27 +207,6 @@ public class SwidTagValidator { return false; } - /** - * This method generates a public key from the modulus and exponent elements - * parsed from a signed swidtag. - * @param doc Document object containing the swidtag - * @return the generated PublicKey object - * @throws NoSuchAlgorithmException if the KeyFactory instance fails to instantiate - * @throws InvalidKeySpecException if the KeyFactory fails to generate the public key - */ - private PublicKey getPKFromKeyValue(Document doc) - throws NoSuchAlgorithmException, InvalidKeySpecException { - Node modulusElement = doc.getElementsByTagName("Modulus").item(0); - Node exponentElement = doc.getElementsByTagName("Exponent").item(0); - BigInteger modulus = new BigInteger( - Base64.getMimeDecoder().decode(modulusElement.getTextContent())); - BigInteger exponent = new BigInteger( - Base64.getMimeDecoder().decode(exponentElement.getTextContent())); - RSAPublicKeySpec keySpec = new RSAPublicKeySpec(modulus, exponent); - KeyFactory factory = KeyFactory.getInstance("RSA"); - return factory.generatePublic(keySpec); - } - /** * 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. From d2d2b9a494ea06b8ada19d6d1cf54c9e5711d7ad Mon Sep 17 00:00:00 2001 From: chubtub <43381989+chubtub@users.noreply.github.com> Date: Tue, 20 Jul 2021 11:04:38 -0400 Subject: [PATCH 5/5] Remove KeyValue element from signed swidtag. Update error message for missing signer cert. Update version number. --- tools/tcg_rim_tool/build.gradle | 2 +- tools/tcg_rim_tool/scripts/rimtool.sh | 2 +- .../src/main/java/hirs/swid/SwidTagGateway.java | 7 ------- .../src/main/java/hirs/swid/SwidTagValidator.java | 9 +++++++-- .../src/main/java/hirs/swid/utils/Commander.java | 2 +- .../src/test/resources/generated_no_cert.swidtag | 10 ---------- .../src/test/resources/generated_with_cert.swidtag | 10 ---------- tools/tcg_rim_tool/tcg_rim_tool.spec | 2 +- 8 files changed, 11 insertions(+), 33 deletions(-) diff --git a/tools/tcg_rim_tool/build.gradle b/tools/tcg_rim_tool/build.gradle index 423e0ced..80d26693 100644 --- a/tools/tcg_rim_tool/build.gradle +++ b/tools/tcg_rim_tool/build.gradle @@ -1,5 +1,5 @@ apply plugin: 'java' -version = '2.0.0' +version = '2.1.0' repositories { mavenCentral() diff --git a/tools/tcg_rim_tool/scripts/rimtool.sh b/tools/tcg_rim_tool/scripts/rimtool.sh index 8b0fc5b1..fedae5db 100644 --- a/tools/tcg_rim_tool/scripts/rimtool.sh +++ b/tools/tcg_rim_tool/scripts/rimtool.sh @@ -3,6 +3,6 @@ # Calls the the_tcg_rim_tool and passes in parameters scriptDir=$(dirname -- "$(readlink -f -- "${BASH_SOURCE[0]}")") baseDir=${scriptDir%/*} -jar="tcg_rim_tool-2.0.0.jar"; +jar="tcg_rim_tool-2.1.0.jar"; java -jar $baseDir/$jar "$@" diff --git a/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidTagGateway.java b/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidTagGateway.java index 8d76241e..3de70de2 100644 --- a/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidTagGateway.java +++ b/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidTagGateway.java @@ -437,27 +437,22 @@ public class SwidTagGateway { KeyInfoFactory kiFactory = sigFactory.getKeyInfoFactory(); PrivateKey privateKey; - PublicKey publicKey; CredentialParser cp = new CredentialParser(); if (defaultCredentials) { cp.parseJKSCredentials(jksTruststoreFile); privateKey = cp.getPrivateKey(); - publicKey = cp.getPublicKey(); KeyName keyName = kiFactory.newKeyName(cp.getCertificateSubjectKeyIdentifier()); keyInfoElements.add(keyName); } else { cp.parsePEMCredentials(pemCertificateFile, pemPrivateKeyFile); X509Certificate certificate = cp.getCertificate(); privateKey = cp.getPrivateKey(); - publicKey = cp.getPublicKey(); ArrayList<Object> x509Content = new ArrayList<Object>(); x509Content.add(certificate.getSubjectX500Principal().getName()); x509Content.add(certificate); X509Data data = kiFactory.newX509Data(x509Content); keyInfoElements.add(data); } - KeyValue keyValue = kiFactory.newKeyValue(publicKey); - keyInfoElements.add(keyValue); KeyInfo keyinfo = kiFactory.newKeyInfo(keyInfoElements); doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); @@ -472,8 +467,6 @@ public class SwidTagGateway { } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | ParserConfigurationException e) { System.out.println(e.getMessage()); - } catch (KeyException e) { - System.out.println("Error setting public key in KeyValue: " + e.getMessage()); } catch (CertificateException e) { System.out.println(e.getMessage()); } catch (JAXBException e) { diff --git a/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidTagValidator.java b/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidTagValidator.java index 9af5f446..e1313f0f 100644 --- a/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidTagValidator.java +++ b/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidTagValidator.java @@ -177,7 +177,8 @@ public class SwidTagValidator { } } if (signingCert == null) { - System.out.println("Signing certificate not found for validation!"); + System.out.println("Issuer certificate with subject key identifier = " + + skId + " not found"); System.exit(1); } context = new DOMValidateContext(signingCert.getPublicKey(), nodes.item(0)); @@ -290,7 +291,11 @@ public class SwidTagValidator { } catch (NoSuchProviderException e) { throw new Exception("Error with BouncyCastleProvider: " + e.getMessage()); } catch (SignatureException e) { - throw new Exception("Error with signature: " + e.getMessage()); + 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()); } diff --git a/tools/tcg_rim_tool/src/main/java/hirs/swid/utils/Commander.java b/tools/tcg_rim_tool/src/main/java/hirs/swid/utils/Commander.java index 2454f299..3893af68 100644 --- a/tools/tcg_rim_tool/src/main/java/hirs/swid/utils/Commander.java +++ b/tools/tcg_rim_tool/src/main/java/hirs/swid/utils/Commander.java @@ -28,7 +28,7 @@ public class Commander { @Parameter(names = {"-t", "--truststore <path>"}, order = 4, description = "The truststore to sign the base RIM created " + "or to validate the signed base RIM.") - private String truststoreFile = "/opt/hirs/rimtool/keystore.jks"; + private String truststoreFile = ""; @Parameter(names = {"-k", "--privateKeyFile <path>"}, order = 5, description = "The private key used to sign the base RIM created by this tool.") private String privateKeyFile = ""; diff --git a/tools/tcg_rim_tool/src/test/resources/generated_no_cert.swidtag b/tools/tcg_rim_tool/src/test/resources/generated_no_cert.swidtag index 7fb01eee..ea454392 100644 --- a/tools/tcg_rim_tool/src/test/resources/generated_no_cert.swidtag +++ b/tools/tcg_rim_tool/src/test/resources/generated_no_cert.swidtag @@ -27,16 +27,6 @@ FsmPWKJb6FYcsHFbHO/Uw1hSokbAGcWWTshEOqvKHMa8UVkrFMUPnrnMtdyJqZlhDBrZHNi4rWth
 TNjlqJ/wq61Ons45MFG9bIscVbnd+XxFHx8Skw==</SignatureValue> <KeyInfo> <KeyName>2fdeb8e7d030a2209daa01861a964fedecf2bcc1</KeyName> - <KeyValue> - <RSAKeyValue> - <Modulus>p3WVYaRJG7EABjbAdqDYZXFSTV1nHY9Ol9A5+W8t5xwBXBryZCGWxERGr5AryKWPxd+qzjj+cFpx -xkM6N18jEhQIx/CEZePEJqpluBO5w2wTEOe7hqtMatqgDDMeDRxUuIpP8LGP00vh1wyDFFew90d9 -dvT3bcLvFh3a3ap9bTm6aBqPup5CXpzrwIU2wZfgkDytYVBm+8bHkMaUrgpNyM+5BAg2zl/Fqw0q -otjaGr7PzbH+urCvaGbKLMPoWkVLIgAE8Qw98HTfoYSFHC7VYQySrzIinaOBFSgViR72kHemH2lW -jDQeHiY0VIoPik/jVVIpjWe6zzeZ2S66Q/LmjQ==</Modulus> - <Exponent>AQAB</Exponent> - </RSAKeyValue> - </KeyValue> </KeyInfo> </Signature> </SoftwareIdentity> diff --git a/tools/tcg_rim_tool/src/test/resources/generated_with_cert.swidtag b/tools/tcg_rim_tool/src/test/resources/generated_with_cert.swidtag index 313cae3a..dc711f3e 100644 --- a/tools/tcg_rim_tool/src/test/resources/generated_with_cert.swidtag +++ b/tools/tcg_rim_tool/src/test/resources/generated_with_cert.swidtag @@ -47,16 +47,6 @@ BzAChhlodHRwczovL2V4YW1wbGUuY29tL2NlcnRzMA0GCSqGSIb3DQEBCwUAA4IBAQDpKx5oQlkS
 cIEQ5OqfpdFrV3De238RhMH6J4xePSidnFpfBc6FrdyDI1A8eRFz36I4xfVL3ZnJP/+j+NE4q6yz 5VGvm0npLO394ZihtsI1sRAR8ORJ</X509Certificate> </X509Data> - <KeyValue> - <RSAKeyValue> - <Modulus>p3WVYaRJG7EABjbAdqDYZXFSTV1nHY9Ol9A5+W8t5xwBXBryZCGWxERGr5AryKWPxd+qzjj+cFpx -xkM6N18jEhQIx/CEZePEJqpluBO5w2wTEOe7hqtMatqgDDMeDRxUuIpP8LGP00vh1wyDFFew90d9 -dvT3bcLvFh3a3ap9bTm6aBqPup5CXpzrwIU2wZfgkDytYVBm+8bHkMaUrgpNyM+5BAg2zl/Fqw0q -otjaGr7PzbH+urCvaGbKLMPoWkVLIgAE8Qw98HTfoYSFHC7VYQySrzIinaOBFSgViR72kHemH2lW -jDQeHiY0VIoPik/jVVIpjWe6zzeZ2S66Q/LmjQ==</Modulus> - <Exponent>AQAB</Exponent> - </RSAKeyValue> - </KeyValue> </KeyInfo> </Signature> </SoftwareIdentity> diff --git a/tools/tcg_rim_tool/tcg_rim_tool.spec b/tools/tcg_rim_tool/tcg_rim_tool.spec index 502d3caf..c7ab4843 100644 --- a/tools/tcg_rim_tool/tcg_rim_tool.spec +++ b/tools/tcg_rim_tool/tcg_rim_tool.spec @@ -1,5 +1,5 @@ Name: tcg_rim_tool -Version: 2.0.0 +Version: 2.1.0 Release: 1%{?dist} Summary: A java command-line tool to create PC client root RIM