diff --git a/HIRS_Utils/src/main/java/hirs/utils/rim/ReferenceManifestValidator.java b/HIRS_Utils/src/main/java/hirs/utils/rim/ReferenceManifestValidator.java index 112c7168..9b72fb8c 100644 --- a/HIRS_Utils/src/main/java/hirs/utils/rim/ReferenceManifestValidator.java +++ b/HIRS_Utils/src/main/java/hirs/utils/rim/ReferenceManifestValidator.java @@ -253,20 +253,26 @@ public class ReferenceManifestValidator { Element fileElement = (Element) rim.getElementsByTagName("File").item(0); if (trustStoreFile != null && !trustStoreFile.isEmpty()) { trustStore = parseCertificatesFromPem(trustStoreFile); + } else { + return failWithError("File <" + trustStoreFile + "> is empty; " + + "a valid, non-empty truststore file is required for validation."); } X509Certificate signingCert = null; try { signingCert = getCertFromTruststore(); + if (signingCert == null) { + return failWithError("Unable to locate the signing cert in the provided " + + "truststore " + trustStoreFile); + } } catch (IOException e) { - log.warn("Error while parsing signing cert from truststore: " + e.getMessage()); - return false; + return failWithError("Error while parsing signing cert from truststore: " + + e.getMessage()); } String subjectKeyIdentifier = ""; try { subjectKeyIdentifier = getCertificateSubjectKeyIdentifier(signingCert); } catch (IOException e) { - log.warn("Error while parsing certificate data: " + e.getMessage()); - return false; + return failWithError("Error while parsing certificate data: " + e.getMessage()); } return validateXmlSignature(signingCert.getPublicKey(), subjectKeyIdentifier, @@ -307,8 +313,7 @@ public class ReferenceManifestValidator { System.out.println("Support RIM hash verified!" + System.lineSeparator()); return true; } else { - System.out.println("Support RIM hash does not match Base RIM!" + System.lineSeparator()); - return false; + return failWithError("Support RIM hash does not match Base RIM!"); } } @@ -771,4 +776,14 @@ public class ReferenceManifestValidator { return doc; } + + /** + * This method logs an error message and returns a false to signal failed validation. + * @param errorMessage String description of what went wrong + * @return false to represent failed validation + */ + private boolean failWithError(String errorMessage) { + log.error(errorMessage); + return false; + } } 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 688be2fb..baa8412f 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 @@ -1,9 +1,11 @@ package hirs.swid; import hirs.swid.utils.Commander; +import hirs.swid.utils.CredentialArgumentValidator; import hirs.swid.utils.TimestampArgumentValidator; import hirs.utils.rim.ReferenceManifestValidator; import com.beust.jcommander.JCommander; +import lombok.extern.log4j.Log4j2; import java.io.File; import java.io.IOException; @@ -12,17 +14,29 @@ import java.nio.file.Paths; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; - +@Log4j2 public class Main { public static void main(String[] args) { Commander commander = new Commander(); JCommander jc = JCommander.newBuilder().addObject(commander).build(); - jc.parse(args); + try { + jc.parse(args); + } catch (Exception e) { + exitWithErrorCode(e.getMessage()); + } SwidTagGateway gateway; ReferenceManifestValidator validator; + List unknownOpts = commander.getUnknownOptions(); + CredentialArgumentValidator credValidator; - if (commander.isHelp()) { + if (!unknownOpts.isEmpty()) { + StringBuilder sb = new StringBuilder("Unknown options encountered: "); + for (String opt : unknownOpts) { + sb.append(opt + ", "); + } + exitWithErrorCode(sb.substring(0,sb.lastIndexOf(","))); + } else if (commander.isHelp()) { jc.usage(); System.out.println(commander.printHelpExamples()); } else if (commander.isVersion()) { @@ -36,34 +50,33 @@ public class Main { } else { if (!commander.getVerifyFile().isEmpty()) { validator = new ReferenceManifestValidator(); - System.out.println(commander.toString()); + if (commander.isVerbose()) { + System.out.println(commander.toString()); + } String verifyFile = commander.getVerifyFile(); String rimel = commander.getRimEventLog(); - String certificateFile = commander.getPublicCertificate(); String trustStore = commander.getTruststoreFile(); - if (!verifyFile.isEmpty()) { - validator.setRim(verifyFile); - if (!rimel.isEmpty()) { - validator.setRimEventLog(rimel); - } - if (!trustStore.isEmpty()) { - validator.setTrustStoreFile(trustStore); - } - 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.validateSwidtagFile(verifyFile); + validator.setRim(verifyFile); + validator.setRimEventLog(rimel); + credValidator = new CredentialArgumentValidator(trustStore, + "","", true); + if (credValidator.isValid()) { + validator.setTrustStoreFile(trustStore); } else { - System.out.println("Need a RIM file to validate!"); - System.exit(1); + exitWithErrorCode(credValidator.getErrorMessage()); + } + if (validator.validateSwidtagFile(verifyFile)) { + System.out.println("Successfully verified " + verifyFile); + } else { + exitWithErrorCode("Failed to verify " + verifyFile); } } else { gateway = new SwidTagGateway(); - System.out.println(commander.toString()); + if (commander.isVerbose()) { + System.out.println(commander.toString()); + } String createType = commander.getCreateType().toUpperCase(); String attributesFile = commander.getAttributesFile(); - String jksTruststoreFile = commander.getTruststoreFile(); String certificateFile = commander.getPublicCertificate(); String privateKeyFile = commander.getPrivateKeyFile(); boolean embeddedCert = commander.isEmbedded(); @@ -71,32 +84,22 @@ public class Main { String rimEventLog = commander.getRimEventLog(); switch (createType) { case "BASE": - if (!attributesFile.isEmpty()) { - gateway.setAttributesFile(attributesFile); - } - if (!jksTruststoreFile.isEmpty()) { + gateway.setAttributesFile(attributesFile); + gateway.setRimEventLog(rimEventLog); + credValidator = new CredentialArgumentValidator("" , + certificateFile, privateKeyFile, false); + if (defaultKey){ gateway.setDefaultCredentials(true); - gateway.setJksTruststoreFile(jksTruststoreFile); - } else if (!certificateFile.isEmpty() && !privateKeyFile.isEmpty()) { + gateway.setJksTruststoreFile(SwidTagConstants.DEFAULT_KEYSTORE_FILE); + } else if (credValidator.isValid()) { gateway.setDefaultCredentials(false); gateway.setPemCertificateFile(certificateFile); gateway.setPemPrivateKeyFile(privateKeyFile); if (embeddedCert) { gateway.setEmbeddedCert(true); } - } else if (defaultKey){ - gateway.setDefaultCredentials(true); - gateway.setJksTruststoreFile(SwidTagConstants.DEFAULT_KEYSTORE_FILE); } else { - System.out.println("A private key (-k) and public certificate (-p) " + - "are required, or the default key (-d) must be indicated."); - System.exit(1); - } - if (rimEventLog.isEmpty()) { - System.out.println("Error: a support RIM is required!"); - System.exit(1); - } else { - gateway.setRimEventLog(rimEventLog); + exitWithErrorCode(credValidator.getErrorMessage()); } List timestampArguments = commander.getTimestampArguments(); if (timestampArguments.size() > 0) { @@ -106,18 +109,27 @@ public class Main { gateway.setTimestampArgument(timestampArguments.get(1)); } } else { - System.exit(1); + exitWithErrorCode("The provided timestamp argument(s) " + + "is/are not valid."); } } gateway.generateSwidTag(commander.getOutFile()); break; default: - System.out.println("No create type given, nothing to do"); + exitWithErrorCode("Create type not recognized."); } } } } + /** + * Use cases that exit with an error code are redirected here. + */ + private static void exitWithErrorCode(String errorMessage) { + log.error(errorMessage); + System.exit(1); + } + /** * This method parses the version number from the jar filename in the absence of * the VERSION file expected with an rpm installation. 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 0cd4ffc1..aa9b23d2 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 @@ -64,7 +64,6 @@ import java.security.InvalidAlgorithmParameterException; import java.security.KeyException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; -import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.time.LocalDateTime; import java.util.ArrayList; @@ -100,7 +99,7 @@ public class SwidTagGateway { try { JAXBContext jaxbContext = JAXBContext.newInstance(SwidTagConstants.SCHEMA_PACKAGE); marshaller = jaxbContext.createMarshaller(); - attributesFile = SwidTagConstants.DEFAULT_ATTRIBUTES_FILE; + attributesFile = ""; defaultCredentials = true; pemCertificateFile = ""; embeddedCert = false; 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 855d9625..b9630e17 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 @@ -12,32 +12,38 @@ import java.util.List; */ public class Commander { + @Parameter(description = "This parameter catches all unrecognized arguments.") + private List unknownOptions = new ArrayList<>(); @Parameter(names = {"-h", "--help"}, help = true, description = "Print this help text.") 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 = ""; - @Parameter(names = {"-v", "--verify "}, order = 3, + @Parameter(names = {"-v", "--verify "}, validateWith = FileArgumentValidator.class, description = "Specify a RIM file to verify.") private String verifyFile = ""; @Parameter(names = {"-V", "--version"}, description = "Output the current version.") private boolean version = false; - @Parameter(names = {"-a", "--attributes "}, order = 1, + @Parameter(names = {"-a", "--attributes "}, validateWith = FileArgumentValidator.class, description = "The configuration file holding attributes " - + "to populate the base RIM with.") + + "to populate the base RIM with. An example file can be found in /opt/rimtool/data.") private String attributesFile = ""; @Parameter(names = {"-o", "--out "}, order = 2, description = "The file to write the RIM out to. " + "The RIM will be written to stdout by default.") private String outFile = ""; - @Parameter(names = {"-t", "--truststore "}, order = 4, + @Parameter(names = {"--verbose"}, description = "Control output verbosity.") + private boolean verbose = false; + @Parameter(names = {"-t", "--truststore "}, validateWith = FileArgumentValidator.class, description = "The truststore to sign the base RIM created " + "or to validate the signed base RIM.") private String truststoreFile = ""; - @Parameter(names = {"-k", "--privateKeyFile "}, order = 5, + @Parameter(names = {"-k", "--privateKeyFile "}, + validateWith = FileArgumentValidator.class, description = "The private key used to sign the base RIM created by this tool.") private String privateKeyFile = ""; - @Parameter(names = {"-p", "--publicCertificate "}, order = 6, + @Parameter(names = {"-p", "--publicCertificate "}, + validateWith = FileArgumentValidator.class, description = "The public key certificate to embed in the base RIM created by " + "this tool.") private String publicCertificate = ""; @@ -45,9 +51,9 @@ public class Commander { description = "Embed the provided certificate in the signed swidtag.") private boolean embedded = false; @Parameter(names = {"-d", "--default-key"}, order = 8, - description = "Use default signing credentials.") + description = "Use the JKS keystore installed in /opt/rimtool/data.") private boolean defaultKey = false; - @Parameter(names = {"-l", "--rimel "}, order = 9, + @Parameter(names = {"-l", "--rimel "}, validateWith = FileArgumentValidator.class, description = "The TCG eventlog file to use as a support RIM.") private String rimEventLog = ""; @Parameter(names = {"--timestamp"}, order = 10, variableArity = true, @@ -56,6 +62,10 @@ public class Commander { "\tRFC3339 [yyyy-MM-ddThh:mm:ssZ]\n\tRFC3852 ") private List timestampArguments = new ArrayList(2); + public List getUnknownOptions() { + return unknownOptions; + } + public boolean isHelp() { return help; } @@ -71,6 +81,7 @@ public class Commander { public boolean isVersion() { return version; } + public boolean isVerbose() { return verbose; } public String getAttributesFile() { return attributesFile; } @@ -101,26 +112,17 @@ public class Commander { public String printHelpExamples() { StringBuilder sb = new StringBuilder(); - sb.append("Create a base RIM using the values in attributes.json; " + - "sign it with the default keystore; "); - sb.append("and write the data to base_rim.swidtag:\n\n"); - sb.append("\t\t-c base -a attributes.json -d -l support_rim.bin -o base_rim.swidtag" + - "\n\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 -e\n\n\n"); - sb.append("Create a base RIM using the values in attributes.json; " + - "sign it with the default keystore; add a RFC3852 timestamp; "); - sb.append("and write the data to base_rim.swidtag:\n\n"); - sb.append("\t\t-c base -a attributes.json -d -l support_rim.bin " + - "--timestamp RFC3852 counterSignature.bin -o base_rim.swidtag\n\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 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"); + sb.append("Create a base RIM: use the values in attributes.json; "); + sb.append("add support_rim.bin to the payload; "); + sb.append("sign it using privateKey.pem and cert.pem; embed cert.pem in the signature; "); + sb.append("add a RFC3852 timestamp; and write the data to base_rim.swidtag:\n\n"); + sb.append("\t\t-c base -a attributes.json -l support_rim.bin " + + "-k privateKey.pem -p cert.pem -e --timestamp RFC3852 counterSignature.bin " + + "-o base_rim.swidtag\n\n\n"); + sb.append("Validate base_rim.swidtag: " + + "the payload is validated with support_rim.bin; " + + "and the signature is validated with ca.crt:\n\n"); + sb.append("\t\t-v base_rim.swidtag -l support_rim.bin -t ca.crt\n\n\n"); return sb.toString(); } diff --git a/tools/tcg_rim_tool/src/main/java/hirs/swid/utils/CredentialArgumentValidator.java b/tools/tcg_rim_tool/src/main/java/hirs/swid/utils/CredentialArgumentValidator.java new file mode 100644 index 00000000..1b162349 --- /dev/null +++ b/tools/tcg_rim_tool/src/main/java/hirs/swid/utils/CredentialArgumentValidator.java @@ -0,0 +1,76 @@ +package hirs.swid.utils; + +public class CredentialArgumentValidator { + private String truststoreFile; + private String certificateFile; + private String privateKeyFile; + private String format; + private boolean isValidating; + private String errorMessage; + private static final String PEM = "PEM"; + + public CredentialArgumentValidator(String truststoreFile, + String certificateFile, + String privateKeyFile, + boolean isValidating) { + this.truststoreFile = truststoreFile; + this.certificateFile = certificateFile; + this.privateKeyFile = privateKeyFile; + this.isValidating = isValidating; + errorMessage = ""; + } + + /** + * Getter for format property + * + * @return string + */ + public String getFormat() { + return format; + } + + /** + * Getter for error message + * + * @return string + */ + public String getErrorMessage() { + return errorMessage; + } + + /** + * This method checks for the following valid configurations of input arguments: + * 1. + * 2. truststore only for validating (PEM format) + * 3. certificate + private key for signing (PEM format) + * 4. + * + * @return true if the above are found, false otherwise + */ + public boolean isValid() { + if (isValidating) { + if (!truststoreFile.isEmpty()) { + format = PEM; + return true; + } else { + errorMessage = "Validation requires a valid truststore file."; + return false; + } + } else { + if (!certificateFile.isEmpty() && !privateKeyFile.isEmpty()) { + format = PEM; + return true; + } else { + if (certificateFile.isEmpty()) { + errorMessage = "A public certificate must be specified by \'-p\' " + + "for signing operations."; + } + if (privateKeyFile.isEmpty()) { + errorMessage = "A private key must be specified by \'-k\' " + + "for signing operations."; + } + return false; + } + } + } +} diff --git a/tools/tcg_rim_tool/src/main/java/hirs/swid/utils/FileArgumentValidator.java b/tools/tcg_rim_tool/src/main/java/hirs/swid/utils/FileArgumentValidator.java new file mode 100644 index 00000000..a06c019b --- /dev/null +++ b/tools/tcg_rim_tool/src/main/java/hirs/swid/utils/FileArgumentValidator.java @@ -0,0 +1,33 @@ +package hirs.swid.utils; + +import com.beust.jcommander.IParameterValidator; +import com.beust.jcommander.ParameterException; + +import java.io.File; +import lombok.extern.log4j.Log4j2; + +/** + * This class validates arguments that take a String path to a file. + * The file path is checked for null, and if the file is found it is checked + * for validity, emptiness, and read permissions. + */ +@Log4j2 +public class FileArgumentValidator implements IParameterValidator { + public void validate(String name, String value) throws ParameterException { + try { + File file = new File(value); + if (!file.isFile()) { + throw new ParameterException("Invalid file path: " + value + + ". Please verify file path."); + } + if (file.length() == 0) { + throw new ParameterException("File " + value + " is empty."); + } + } catch (NullPointerException e) { + throw new ParameterException("File path cannot be null: " + e.getMessage()); + } catch (SecurityException e) { + throw new ParameterException("Read access denied for " + value + + ", please verify permissions."); + } + } +} \ No newline at end of file