Merge pull request #715 from nsacyber/v3_issue-661_updated

[#661] Output handling should be more concise
This commit is contained in:
chubtub 2024-03-06 12:40:03 -05:00 committed by GitHub
commit 5445278723
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 215 additions and 78 deletions

View File

@ -253,20 +253,26 @@ public class ReferenceManifestValidator {
Element fileElement = (Element) rim.getElementsByTagName("File").item(0); Element fileElement = (Element) rim.getElementsByTagName("File").item(0);
if (trustStoreFile != null && !trustStoreFile.isEmpty()) { if (trustStoreFile != null && !trustStoreFile.isEmpty()) {
trustStore = parseCertificatesFromPem(trustStoreFile); trustStore = parseCertificatesFromPem(trustStoreFile);
} else {
return failWithError("File <" + trustStoreFile + "> is empty; " +
"a valid, non-empty truststore file is required for validation.");
} }
X509Certificate signingCert = null; X509Certificate signingCert = null;
try { try {
signingCert = getCertFromTruststore(); signingCert = getCertFromTruststore();
if (signingCert == null) {
return failWithError("Unable to locate the signing cert in the provided " +
"truststore " + trustStoreFile);
}
} catch (IOException e) { } catch (IOException e) {
log.warn("Error while parsing signing cert from truststore: " + e.getMessage()); return failWithError("Error while parsing signing cert from truststore: " +
return false; e.getMessage());
} }
String subjectKeyIdentifier = ""; String subjectKeyIdentifier = "";
try { try {
subjectKeyIdentifier = getCertificateSubjectKeyIdentifier(signingCert); subjectKeyIdentifier = getCertificateSubjectKeyIdentifier(signingCert);
} catch (IOException e) { } catch (IOException e) {
log.warn("Error while parsing certificate data: " + e.getMessage()); return failWithError("Error while parsing certificate data: " + e.getMessage());
return false;
} }
return validateXmlSignature(signingCert.getPublicKey(), return validateXmlSignature(signingCert.getPublicKey(),
subjectKeyIdentifier, subjectKeyIdentifier,
@ -307,8 +313,7 @@ public class ReferenceManifestValidator {
System.out.println("Support RIM hash verified!" + System.lineSeparator()); System.out.println("Support RIM hash verified!" + System.lineSeparator());
return true; return true;
} else { } else {
System.out.println("Support RIM hash does not match Base RIM!" + System.lineSeparator()); return failWithError("Support RIM hash does not match Base RIM!");
return false;
} }
} }
@ -771,4 +776,14 @@ public class ReferenceManifestValidator {
return doc; 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;
}
} }

View File

@ -1,9 +1,11 @@
package hirs.swid; package hirs.swid;
import hirs.swid.utils.Commander; import hirs.swid.utils.Commander;
import hirs.swid.utils.CredentialArgumentValidator;
import hirs.swid.utils.TimestampArgumentValidator; import hirs.swid.utils.TimestampArgumentValidator;
import hirs.utils.rim.ReferenceManifestValidator; import hirs.utils.rim.ReferenceManifestValidator;
import com.beust.jcommander.JCommander; import com.beust.jcommander.JCommander;
import lombok.extern.log4j.Log4j2;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -12,17 +14,29 @@ import java.nio.file.Paths;
import java.util.List; import java.util.List;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@Log4j2
public class Main { public class Main {
public static void main(String[] args) { public static void main(String[] args) {
Commander commander = new Commander(); Commander commander = new Commander();
JCommander jc = JCommander.newBuilder().addObject(commander).build(); JCommander jc = JCommander.newBuilder().addObject(commander).build();
jc.parse(args); try {
jc.parse(args);
} catch (Exception e) {
exitWithErrorCode(e.getMessage());
}
SwidTagGateway gateway; SwidTagGateway gateway;
ReferenceManifestValidator validator; ReferenceManifestValidator validator;
List<String> 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(); jc.usage();
System.out.println(commander.printHelpExamples()); System.out.println(commander.printHelpExamples());
} else if (commander.isVersion()) { } else if (commander.isVersion()) {
@ -36,34 +50,33 @@ public class Main {
} else { } else {
if (!commander.getVerifyFile().isEmpty()) { if (!commander.getVerifyFile().isEmpty()) {
validator = new ReferenceManifestValidator(); validator = new ReferenceManifestValidator();
System.out.println(commander.toString()); if (commander.isVerbose()) {
System.out.println(commander.toString());
}
String verifyFile = commander.getVerifyFile(); String verifyFile = commander.getVerifyFile();
String rimel = commander.getRimEventLog(); String rimel = commander.getRimEventLog();
String certificateFile = commander.getPublicCertificate();
String trustStore = commander.getTruststoreFile(); String trustStore = commander.getTruststoreFile();
if (!verifyFile.isEmpty()) { validator.setRim(verifyFile);
validator.setRim(verifyFile); validator.setRimEventLog(rimel);
if (!rimel.isEmpty()) { credValidator = new CredentialArgumentValidator(trustStore,
validator.setRimEventLog(rimel); "","", true);
} if (credValidator.isValid()) {
if (!trustStore.isEmpty()) { validator.setTrustStoreFile(trustStore);
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);
} else { } else {
System.out.println("Need a RIM file to validate!"); exitWithErrorCode(credValidator.getErrorMessage());
System.exit(1); }
if (validator.validateSwidtagFile(verifyFile)) {
System.out.println("Successfully verified " + verifyFile);
} else {
exitWithErrorCode("Failed to verify " + verifyFile);
} }
} else { } else {
gateway = new SwidTagGateway(); gateway = new SwidTagGateway();
System.out.println(commander.toString()); if (commander.isVerbose()) {
System.out.println(commander.toString());
}
String createType = commander.getCreateType().toUpperCase(); String createType = commander.getCreateType().toUpperCase();
String attributesFile = commander.getAttributesFile(); String attributesFile = commander.getAttributesFile();
String jksTruststoreFile = commander.getTruststoreFile();
String certificateFile = commander.getPublicCertificate(); String certificateFile = commander.getPublicCertificate();
String privateKeyFile = commander.getPrivateKeyFile(); String privateKeyFile = commander.getPrivateKeyFile();
boolean embeddedCert = commander.isEmbedded(); boolean embeddedCert = commander.isEmbedded();
@ -71,32 +84,22 @@ public class Main {
String rimEventLog = commander.getRimEventLog(); String rimEventLog = commander.getRimEventLog();
switch (createType) { switch (createType) {
case "BASE": case "BASE":
if (!attributesFile.isEmpty()) { gateway.setAttributesFile(attributesFile);
gateway.setAttributesFile(attributesFile); gateway.setRimEventLog(rimEventLog);
} credValidator = new CredentialArgumentValidator("" ,
if (!jksTruststoreFile.isEmpty()) { certificateFile, privateKeyFile, false);
if (defaultKey){
gateway.setDefaultCredentials(true); gateway.setDefaultCredentials(true);
gateway.setJksTruststoreFile(jksTruststoreFile); gateway.setJksTruststoreFile(SwidTagConstants.DEFAULT_KEYSTORE_FILE);
} else if (!certificateFile.isEmpty() && !privateKeyFile.isEmpty()) { } else if (credValidator.isValid()) {
gateway.setDefaultCredentials(false); gateway.setDefaultCredentials(false);
gateway.setPemCertificateFile(certificateFile); gateway.setPemCertificateFile(certificateFile);
gateway.setPemPrivateKeyFile(privateKeyFile); gateway.setPemPrivateKeyFile(privateKeyFile);
if (embeddedCert) { if (embeddedCert) {
gateway.setEmbeddedCert(true); gateway.setEmbeddedCert(true);
} }
} else if (defaultKey){
gateway.setDefaultCredentials(true);
gateway.setJksTruststoreFile(SwidTagConstants.DEFAULT_KEYSTORE_FILE);
} else { } else {
System.out.println("A private key (-k) and public certificate (-p) " + exitWithErrorCode(credValidator.getErrorMessage());
"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);
} }
List<String> timestampArguments = commander.getTimestampArguments(); List<String> timestampArguments = commander.getTimestampArguments();
if (timestampArguments.size() > 0) { if (timestampArguments.size() > 0) {
@ -106,18 +109,27 @@ public class Main {
gateway.setTimestampArgument(timestampArguments.get(1)); gateway.setTimestampArgument(timestampArguments.get(1));
} }
} else { } else {
System.exit(1); exitWithErrorCode("The provided timestamp argument(s) " +
"is/are not valid.");
} }
} }
gateway.generateSwidTag(commander.getOutFile()); gateway.generateSwidTag(commander.getOutFile());
break; break;
default: 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 * This method parses the version number from the jar filename in the absence of
* the VERSION file expected with an rpm installation. * the VERSION file expected with an rpm installation.

View File

@ -64,7 +64,6 @@ import java.security.InvalidAlgorithmParameterException;
import java.security.KeyException; import java.security.KeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
@ -100,7 +99,7 @@ public class SwidTagGateway {
try { try {
JAXBContext jaxbContext = JAXBContext.newInstance(SwidTagConstants.SCHEMA_PACKAGE); JAXBContext jaxbContext = JAXBContext.newInstance(SwidTagConstants.SCHEMA_PACKAGE);
marshaller = jaxbContext.createMarshaller(); marshaller = jaxbContext.createMarshaller();
attributesFile = SwidTagConstants.DEFAULT_ATTRIBUTES_FILE; attributesFile = "";
defaultCredentials = true; defaultCredentials = true;
pemCertificateFile = ""; pemCertificateFile = "";
embeddedCert = false; embeddedCert = false;

View File

@ -12,32 +12,38 @@ import java.util.List;
*/ */
public class Commander { public class Commander {
@Parameter(description = "This parameter catches all unrecognized arguments.")
private List<String> unknownOptions = new ArrayList<>();
@Parameter(names = {"-h", "--help"}, help = true, description = "Print this help text.") @Parameter(names = {"-h", "--help"}, help = true, description = "Print this help text.")
private boolean help; private boolean help;
@Parameter(names = {"-c", "--create \"base\""}, order = 0, @Parameter(names = {"-c", "--create \"base\""}, order = 0,
description = "The type of RIM to create. A base RIM will be created by default.") description = "The type of RIM to create. A base RIM will be created by default.")
private String createType = ""; private String createType = "";
@Parameter(names = {"-v", "--verify <path>"}, order = 3, @Parameter(names = {"-v", "--verify <path>"}, validateWith = FileArgumentValidator.class,
description = "Specify a RIM file to verify.") description = "Specify a RIM file to verify.")
private String verifyFile = ""; private String verifyFile = "";
@Parameter(names = {"-V", "--version"}, description = "Output the current version.") @Parameter(names = {"-V", "--version"}, description = "Output the current version.")
private boolean version = false; private boolean version = false;
@Parameter(names = {"-a", "--attributes <path>"}, order = 1, @Parameter(names = {"-a", "--attributes <path>"}, validateWith = FileArgumentValidator.class,
description = "The configuration file holding attributes " 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 = ""; private String attributesFile = "";
@Parameter(names = {"-o", "--out <path>"}, order = 2, @Parameter(names = {"-o", "--out <path>"}, order = 2,
description = "The file to write the RIM out to. " description = "The file to write the RIM out to. "
+ "The RIM will be written to stdout by default.") + "The RIM will be written to stdout by default.")
private String outFile = ""; private String outFile = "";
@Parameter(names = {"-t", "--truststore <path>"}, order = 4, @Parameter(names = {"--verbose"}, description = "Control output verbosity.")
private boolean verbose = false;
@Parameter(names = {"-t", "--truststore <path>"}, validateWith = FileArgumentValidator.class,
description = "The truststore to sign the base RIM created " description = "The truststore to sign the base RIM created "
+ "or to validate the signed base RIM.") + "or to validate the signed base RIM.")
private String truststoreFile = ""; private String truststoreFile = "";
@Parameter(names = {"-k", "--privateKeyFile <path>"}, order = 5, @Parameter(names = {"-k", "--privateKeyFile <path>"},
validateWith = FileArgumentValidator.class,
description = "The private key used to sign the base RIM created by this tool.") description = "The private key used to sign the base RIM created by this tool.")
private String privateKeyFile = ""; private String privateKeyFile = "";
@Parameter(names = {"-p", "--publicCertificate <path>"}, order = 6, @Parameter(names = {"-p", "--publicCertificate <path>"},
validateWith = FileArgumentValidator.class,
description = "The public key certificate to embed in the base RIM created by " description = "The public key certificate to embed in the base RIM created by "
+ "this tool.") + "this tool.")
private String publicCertificate = ""; private String publicCertificate = "";
@ -45,9 +51,9 @@ public class Commander {
description = "Embed the provided certificate in the signed swidtag.") description = "Embed the provided certificate in the signed swidtag.")
private boolean embedded = false; private boolean embedded = false;
@Parameter(names = {"-d", "--default-key"}, order = 8, @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; private boolean defaultKey = false;
@Parameter(names = {"-l", "--rimel <path>"}, order = 9, @Parameter(names = {"-l", "--rimel <path>"}, validateWith = FileArgumentValidator.class,
description = "The TCG eventlog file to use as a support RIM.") description = "The TCG eventlog file to use as a support RIM.")
private String rimEventLog = ""; private String rimEventLog = "";
@Parameter(names = {"--timestamp"}, order = 10, variableArity = true, @Parameter(names = {"--timestamp"}, order = 10, variableArity = true,
@ -56,6 +62,10 @@ public class Commander {
"\tRFC3339 [yyyy-MM-ddThh:mm:ssZ]\n\tRFC3852 <counterSignature.bin>") "\tRFC3339 [yyyy-MM-ddThh:mm:ssZ]\n\tRFC3852 <counterSignature.bin>")
private List<String> timestampArguments = new ArrayList<String>(2); private List<String> timestampArguments = new ArrayList<String>(2);
public List<String> getUnknownOptions() {
return unknownOptions;
}
public boolean isHelp() { public boolean isHelp() {
return help; return help;
} }
@ -71,6 +81,7 @@ public class Commander {
public boolean isVersion() { public boolean isVersion() {
return version; return version;
} }
public boolean isVerbose() { return verbose; }
public String getAttributesFile() { public String getAttributesFile() {
return attributesFile; return attributesFile;
} }
@ -101,26 +112,17 @@ public class Commander {
public String printHelpExamples() { public String printHelpExamples() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("Create a base RIM using the values in attributes.json; " + sb.append("Create a base RIM: use the values in attributes.json; ");
"sign it with the default keystore; "); sb.append("add support_rim.bin to the payload; ");
sb.append("and write the data to base_rim.swidtag:\n\n"); sb.append("sign it using privateKey.pem and cert.pem; embed cert.pem in the signature; ");
sb.append("\t\t-c base -a attributes.json -d -l support_rim.bin -o base_rim.swidtag" + sb.append("add a RFC3852 timestamp; and write the data to base_rim.swidtag:\n\n");
"\n\n\n"); sb.append("\t\t-c base -a attributes.json -l support_rim.bin "
sb.append("Create a base RIM using the default attribute values; "); + "-k privateKey.pem -p cert.pem -e --timestamp RFC3852 counterSignature.bin "
sb.append("sign it using privateKey.pem; embed cert.pem in the signature block; "); + "-o base_rim.swidtag\n\n\n");
sb.append("and write the data to console output:\n\n"); sb.append("Validate base_rim.swidtag: "
sb.append("\t\t-c base -l support_rim.bin -k privateKey.pem -p cert.pem -e\n\n\n"); + "the payload <File> is validated with support_rim.bin; "
sb.append("Create a base RIM using the values in attributes.json; " + + "and the signature is validated with ca.crt:\n\n");
"sign it with the default keystore; add a RFC3852 timestamp; "); sb.append("\t\t-v base_rim.swidtag -l support_rim.bin -t ca.crt\n\n\n");
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");
return sb.toString(); return sb.toString();
} }

View File

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

View File

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