mirror of
https://github.com/nsacyber/HIRS.git
synced 2025-04-07 19:34:27 +00:00
Merge pull request #407 from nsacyber/issue-381
[#381] Update RIM validation in ACA
This commit is contained in:
commit
bf8ef387c1
@ -126,6 +126,8 @@ public abstract class AbstractAttestationCertificateAuthority
|
||||
= "/webapps/HIRS_AttestationCA/upload/";
|
||||
private static final String PCR_UPLOAD_FOLDER
|
||||
= CATALINA_HOME + TOMCAT_UPLOAD_DIRECTORY;
|
||||
private static final String PCR_QUOTE_MASK = "0,1,2,3,4,5,6,7,8,9,10,11,12,13,"
|
||||
+ "14,15,16,17,18,19,20,21,22,23";
|
||||
|
||||
/**
|
||||
* Number of bytes to include in the TPM2.0 nonce.
|
||||
@ -441,6 +443,8 @@ public abstract class AbstractAttestationCertificateAuthority
|
||||
RSAPublicKey akPub = parsePublicKey(claim.getAkPublicArea().toByteArray());
|
||||
byte[] nonce = generateRandomBytes(NONCE_LENGTH);
|
||||
ByteString blobStr = tpm20MakeCredential(ekPub, akPub, nonce);
|
||||
SupplyChainPolicy scp = this.supplyChainValidationService.getPolicy();
|
||||
String pcrQuoteMask = PCR_QUOTE_MASK;
|
||||
|
||||
String strNonce = HexUtils.byteArrayToHexString(nonce);
|
||||
LOG.info("Sending nonce: " + strNonce);
|
||||
@ -448,10 +452,14 @@ public abstract class AbstractAttestationCertificateAuthority
|
||||
|
||||
tpm2ProvisionerStateDBManager.save(new TPM2ProvisionerState(nonce, identityClaim));
|
||||
|
||||
if (scp != null && scp.isIgnoreImaEnabled()) {
|
||||
pcrQuoteMask = PCR_QUOTE_MASK.replace("10,", "");
|
||||
}
|
||||
// Package response
|
||||
ProvisionerTpm2.IdentityClaimResponse response
|
||||
= ProvisionerTpm2.IdentityClaimResponse.newBuilder()
|
||||
.setCredentialBlob(blobStr).build();
|
||||
.setCredentialBlob(blobStr).setPcrMask(pcrQuoteMask)
|
||||
.build();
|
||||
|
||||
return response.toByteArray();
|
||||
} else {
|
||||
@ -622,9 +630,11 @@ public abstract class AbstractAttestationCertificateAuthority
|
||||
tpm2ProvisionerStateDBManager.delete(tpm2ProvisionerState);
|
||||
|
||||
// Package the signed certificate into a response
|
||||
ByteString certificateBytes = ByteString.copyFrom(derEncodedAttestationCertificate);
|
||||
ByteString certificateBytes = ByteString
|
||||
.copyFrom(derEncodedAttestationCertificate);
|
||||
ProvisionerTpm2.CertificateResponse response = ProvisionerTpm2.CertificateResponse
|
||||
.newBuilder().setCertificate(certificateBytes).build();
|
||||
.newBuilder().setCertificate(certificateBytes)
|
||||
.build();
|
||||
|
||||
saveAttestationCertificate(derEncodedAttestationCertificate, endorsementCredential,
|
||||
platformCredentials, device);
|
||||
@ -1017,23 +1027,25 @@ public abstract class AbstractAttestationCertificateAuthority
|
||||
}
|
||||
}
|
||||
} else if (dbSupport.isSwidSupplemental() && !dbSupport.isProcessed()) {
|
||||
try {
|
||||
TCGEventLog logProcessor = new TCGEventLog(dbSupport.getRimBytes());
|
||||
ReferenceDigestValue rdv;
|
||||
for (TpmPcrEvent tpe : logProcessor.getEventList()) {
|
||||
rdv = new ReferenceDigestValue(rdr.getId(), tpe.getPcrIndex(),
|
||||
tpe.getEventDigestStr(), tpe.getEventTypeStr(),
|
||||
false, false);
|
||||
this.referenceEventManager.saveValue(rdv);
|
||||
if (rdr != null) {
|
||||
try {
|
||||
TCGEventLog logProcessor = new TCGEventLog(dbSupport.getRimBytes());
|
||||
ReferenceDigestValue rdv;
|
||||
for (TpmPcrEvent tpe : logProcessor.getEventList()) {
|
||||
rdv = new ReferenceDigestValue(rdr.getId(), tpe.getPcrIndex(),
|
||||
tpe.getEventDigestStr(), tpe.getEventTypeStr(),
|
||||
false, false);
|
||||
this.referenceEventManager.saveValue(rdv);
|
||||
}
|
||||
dbSupport.setProcessed(true);
|
||||
this.referenceManifestManager.update(dbSupport);
|
||||
} catch (CertificateException cEx) {
|
||||
LOG.error(cEx);
|
||||
} catch (NoSuchAlgorithmException noSaEx) {
|
||||
LOG.error(noSaEx);
|
||||
} catch (IOException ioEx) {
|
||||
LOG.error(ioEx);
|
||||
}
|
||||
dbSupport.setProcessed(true);
|
||||
this.referenceManifestManager.update(dbSupport);
|
||||
} catch (CertificateException cEx) {
|
||||
LOG.error(cEx);
|
||||
} catch (NoSuchAlgorithmException noSaEx) {
|
||||
LOG.error(noSaEx);
|
||||
} catch (IOException ioEx) {
|
||||
LOG.error(ioEx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -46,15 +46,12 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
@ -95,7 +92,8 @@ public class SupplyChainValidationServiceImpl implements SupplyChainValidationSe
|
||||
/**
|
||||
* Constructor to set just the CertificateManager, so that cert chain validating
|
||||
* methods can be called from outside classes.
|
||||
* @param certificateManager the cert manager
|
||||
*
|
||||
* @param certificateManager the cert manager
|
||||
*/
|
||||
public SupplyChainValidationServiceImpl(final CertificateManager certificateManager) {
|
||||
this.certificateManager = certificateManager;
|
||||
@ -135,6 +133,7 @@ public class SupplyChainValidationServiceImpl implements SupplyChainValidationSe
|
||||
|
||||
/**
|
||||
* Allows other service access to the policy information.
|
||||
*
|
||||
* @return supply chain policy
|
||||
*/
|
||||
public SupplyChainPolicy getPolicy() {
|
||||
@ -243,7 +242,7 @@ public class SupplyChainValidationServiceImpl implements SupplyChainValidationSe
|
||||
} else {
|
||||
validations.add(new SupplyChainValidation(platformType,
|
||||
AppraisalStatus.Status.FAIL, new ArrayList<>(pcs), pcErrorMessage));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -425,33 +424,20 @@ public class SupplyChainValidationServiceImpl implements SupplyChainValidationSe
|
||||
|
||||
// verify signatures
|
||||
ReferenceManifestValidator referenceManifestValidator =
|
||||
new ReferenceManifestValidator(
|
||||
new ByteArrayInputStream(baseReferenceManifest.getRimBytes()));
|
||||
new ReferenceManifestValidator();
|
||||
referenceManifestValidator.setRim(baseReferenceManifest);
|
||||
|
||||
|
||||
for (SwidResource swidRes : resources) {
|
||||
supportReferenceManifest = SupportReferenceManifest.select(referenceManifestManager)
|
||||
.byHexDecHash(swidRes.getHashValue()).getRIM();
|
||||
if (supportReferenceManifest != null
|
||||
&& swidRes.getName().equals(supportReferenceManifest.getFileName())) {
|
||||
referenceManifestValidator.validateSupportRimHash(
|
||||
supportReferenceManifest.getRimBytes(), swidRes.getHashValue());
|
||||
} else {
|
||||
supportReferenceManifest = null;
|
||||
}
|
||||
}
|
||||
//Validate signing cert
|
||||
Set<CertificateAuthorityCredential> allCerts =
|
||||
CertificateAuthorityCredential.select(certificateManager).getCertificates();
|
||||
CertificateAuthorityCredential signingCert = null;
|
||||
for (CertificateAuthorityCredential cert : allCerts) {
|
||||
if (Arrays.equals(cert.getEncodedPublicKey(),
|
||||
referenceManifestValidator.getPublicKey().getEncoded())) {
|
||||
signingCert = cert;
|
||||
KeyStore keyStore = getCaChain(signingCert);
|
||||
signingCert = cert;
|
||||
KeyStore keyStore = getCaChain(signingCert);
|
||||
if (referenceManifestValidator.validateXmlSignature(signingCert)) {
|
||||
try {
|
||||
X509Certificate x509Cert = signingCert.getX509Certificate();
|
||||
if (!SupplyChainCredentialValidator.verifyCertificate(x509Cert, keyStore)) {
|
||||
if (!SupplyChainCredentialValidator.verifyCertificate(
|
||||
signingCert.getX509Certificate(), keyStore)) {
|
||||
passed = false;
|
||||
fwStatus = new AppraisalStatus(FAIL,
|
||||
"Firmware validation failed: invalid certificate path.");
|
||||
@ -467,6 +453,18 @@ public class SupplyChainValidationServiceImpl implements SupplyChainValidationSe
|
||||
}
|
||||
}
|
||||
|
||||
for (SwidResource swidRes : resources) {
|
||||
supportReferenceManifest = SupportReferenceManifest.select(referenceManifestManager)
|
||||
.byHexDecHash(swidRes.getHashValue()).getRIM();
|
||||
if (supportReferenceManifest != null
|
||||
&& swidRes.getName().equals(supportReferenceManifest.getFileName())) {
|
||||
referenceManifestValidator.validateSupportRimHash(
|
||||
supportReferenceManifest.getRimBytes(), swidRes.getHashValue());
|
||||
} else {
|
||||
supportReferenceManifest = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (signingCert == null) {
|
||||
passed = false;
|
||||
fwStatus = new AppraisalStatus(FAIL,
|
||||
@ -522,9 +520,9 @@ public class SupplyChainValidationServiceImpl implements SupplyChainValidationSe
|
||||
+ "provide pcr values.", device.getName()));
|
||||
} else {
|
||||
// we have a full set of PCR values
|
||||
int algorithmLength = baseline[0].length();
|
||||
String[] storedPcrs = buildStoredPcrs(pcrContent, algorithmLength);
|
||||
pcrPolicy.validatePcrs(storedPcrs);
|
||||
//int algorithmLength = baseline[0].length();
|
||||
//String[] storedPcrs = buildStoredPcrs(pcrContent, algorithmLength);
|
||||
//pcrPolicy.validatePcrs(storedPcrs);
|
||||
|
||||
// part 2 of firmware validation check: bios measurements
|
||||
// vs baseline tcg event log
|
||||
@ -583,6 +581,9 @@ public class SupplyChainValidationServiceImpl implements SupplyChainValidationSe
|
||||
} else {
|
||||
fwStatus = new AppraisalStatus(FAIL, String.format("Firmware Validation failed: "
|
||||
+ "%s for %s can not be found", failedString, manufacturer));
|
||||
EventLogMeasurements eventLog = (EventLogMeasurements) measurement;
|
||||
eventLog.setOverallValidationResult(fwStatus.getAppStatus());
|
||||
this.referenceManifestManager.update(eventLog);
|
||||
}
|
||||
|
||||
return buildValidationRecord(SupplyChainValidation.ValidationType.FIRMWARE,
|
||||
@ -605,7 +606,7 @@ public class SupplyChainValidationServiceImpl implements SupplyChainValidationSe
|
||||
SupplyChainValidationSummary summary = null;
|
||||
Level level = Level.ERROR;
|
||||
AppraisalStatus fwStatus = new AppraisalStatus(FAIL,
|
||||
SupplyChainCredentialValidator.FIRMWARE_VALID);
|
||||
"Unknown exception caught during quote validation.");
|
||||
SupportReferenceManifest sRim = null;
|
||||
EventLogMeasurements eventLog = null;
|
||||
|
||||
|
@ -31,12 +31,10 @@ import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
@ -68,9 +66,9 @@ public class ReferenceManifestDetailsPageController
|
||||
* Constructor providing the Page's display and routing specification.
|
||||
*
|
||||
* @param referenceManifestManager the reference manifest manager.
|
||||
* @param referenceDigestManager the reference digest manager.
|
||||
* @param referenceEventManager the reference event manager.
|
||||
* @param certificateManager the certificate manager.
|
||||
* @param referenceDigestManager the reference digest manager.
|
||||
* @param referenceEventManager the reference event manager.
|
||||
* @param certificateManager the certificate manager.
|
||||
*/
|
||||
@Autowired
|
||||
public ReferenceManifestDetailsPageController(
|
||||
@ -144,19 +142,20 @@ public class ReferenceManifestDetailsPageController
|
||||
*
|
||||
* @param uuid database reference for the requested RIM.
|
||||
* @param referenceManifestManager the reference manifest manager.
|
||||
* @param referenceDigestManager the reference digest manager.
|
||||
* @param referenceEventManager the reference event manager.
|
||||
* @param certificateManager the certificate manager.
|
||||
* @param referenceDigestManager the reference digest manager.
|
||||
* @param referenceEventManager the reference event manager.
|
||||
* @param certificateManager the certificate manager.
|
||||
* @return mapping of the RIM information from the database.
|
||||
* @throws java.io.IOException error for reading file bytes.
|
||||
* @throws NoSuchAlgorithmException If an unknown Algorithm is encountered.
|
||||
* @throws CertificateException if a certificate doesn't parse.
|
||||
*/
|
||||
public static HashMap<String, Object> getRimDetailInfo(final UUID uuid,
|
||||
final ReferenceManifestManager referenceManifestManager,
|
||||
final ReferenceDigestManager referenceDigestManager,
|
||||
final ReferenceEventManager referenceEventManager,
|
||||
final CertificateManager certificateManager) throws IOException,
|
||||
final ReferenceManifestManager referenceManifestManager,
|
||||
final ReferenceDigestManager referenceDigestManager,
|
||||
final ReferenceEventManager referenceEventManager,
|
||||
final CertificateManager certificateManager)
|
||||
throws IOException,
|
||||
CertificateException, NoSuchAlgorithmException {
|
||||
HashMap<String, Object> data = new HashMap<>();
|
||||
|
||||
@ -189,9 +188,9 @@ public class ReferenceManifestDetailsPageController
|
||||
* This method takes the place of an entire class for a string builder.
|
||||
* Gathers all information and returns it for displays.
|
||||
*
|
||||
* @param baseRim established ReferenceManifest Type.
|
||||
* @param baseRim established ReferenceManifest Type.
|
||||
* @param referenceManifestManager the reference manifest manager.
|
||||
* @param certificateManager the certificate manager.
|
||||
* @param certificateManager the certificate manager.
|
||||
* @return mapping of the RIM information from the database.
|
||||
* @throws java.io.IOException error for reading file bytes.
|
||||
* @throws NoSuchAlgorithmException If an unknown Algorithm is encountered.
|
||||
@ -282,6 +281,7 @@ public class ReferenceManifestDetailsPageController
|
||||
}
|
||||
// going to have to pull the filename and grab that from the DB
|
||||
// to get the id to make the link
|
||||
RIM_VALIDATOR.setRim(baseRim);
|
||||
for (SwidResource swidRes : resources) {
|
||||
if (support != null && swidRes.getHashValue()
|
||||
.equalsIgnoreCase(support.getHexDecHash())) {
|
||||
@ -303,29 +303,24 @@ public class ReferenceManifestDetailsPageController
|
||||
data.put("pcrList", support.getExpectedPCRList());
|
||||
}
|
||||
|
||||
RIM_VALIDATOR.validateXmlSignature(new ByteArrayInputStream(baseRim.getRimBytes()));
|
||||
Set<CertificateAuthorityCredential> certificates =
|
||||
CertificateAuthorityCredential.select(certificateManager)
|
||||
.getCertificates();
|
||||
//Report invalid signature unless RIM_VALIDATOR validates it and cert path is valid
|
||||
data.put("signatureValid", false);
|
||||
if (RIM_VALIDATOR.isSignatureValid()) {
|
||||
for (CertificateAuthorityCredential cert : certificates) {
|
||||
if (Arrays.equals(cert.getEncodedPublicKey(),
|
||||
RIM_VALIDATOR.getPublicKey().getEncoded())) {
|
||||
SupplyChainValidationServiceImpl scvsImpl =
|
||||
new SupplyChainValidationServiceImpl(certificateManager);
|
||||
KeyStore keystore = scvsImpl.getCaChain(cert);
|
||||
X509Certificate signingCert = cert.getX509Certificate();
|
||||
try {
|
||||
if (SupplyChainCredentialValidator.verifyCertificate(signingCert,
|
||||
keystore)) {
|
||||
data.replace("signatureValid", true);
|
||||
}
|
||||
} catch (SupplyChainValidatorException e) {
|
||||
LOGGER.error("Error verifying cert chain: " + e.getMessage());
|
||||
for (CertificateAuthorityCredential cert : certificates) {
|
||||
SupplyChainValidationServiceImpl scvsImpl =
|
||||
new SupplyChainValidationServiceImpl(certificateManager);
|
||||
KeyStore keystore = scvsImpl.getCaChain(cert);
|
||||
if (RIM_VALIDATOR.validateXmlSignature(cert)) {
|
||||
try {
|
||||
if (SupplyChainCredentialValidator.verifyCertificate(
|
||||
cert.getX509Certificate(), keystore)) {
|
||||
data.replace("signatureValid", true);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
} catch (SupplyChainValidatorException e) {
|
||||
LOGGER.error("Error verifying cert chain: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -347,7 +342,7 @@ public class ReferenceManifestDetailsPageController
|
||||
* This method takes the place of an entire class for a string builder.
|
||||
* Gathers all information and returns it for displays.
|
||||
*
|
||||
* @param support established ReferenceManifest Type.
|
||||
* @param support established ReferenceManifest Type.
|
||||
* @param referenceManifestManager the reference manifest manager.
|
||||
* @return mapping of the RIM information from the database.
|
||||
* @throws java.io.IOException error for reading file bytes.
|
||||
@ -412,8 +407,8 @@ public class ReferenceManifestDetailsPageController
|
||||
digestMap.put(tpe.getEventDigestStr(), tpe);
|
||||
if (!support.isSwidSupplemental()
|
||||
&& !tpe.eventCompare(
|
||||
measurementsProcess.getEventByNumber(
|
||||
tpe.getEventNumber()))) {
|
||||
measurementsProcess.getEventByNumber(
|
||||
tpe.getEventNumber()))) {
|
||||
tpe.setError(true);
|
||||
}
|
||||
tpmPcrEvents.add(tpe);
|
||||
@ -507,10 +502,10 @@ public class ReferenceManifestDetailsPageController
|
||||
* This method takes the place of an entire class for a string builder.
|
||||
* Gathers all information and returns it for displays.
|
||||
*
|
||||
* @param measurements established ReferenceManifest Type.
|
||||
* @param measurements established ReferenceManifest Type.
|
||||
* @param referenceManifestManager the reference manifest manager.
|
||||
* @param referenceDigestManager the reference digest manager.
|
||||
* @param referenceEventManager the reference event manager.
|
||||
* @param referenceDigestManager the reference digest manager.
|
||||
* @param referenceEventManager the reference event manager.
|
||||
* @return mapping of the RIM information from the database.
|
||||
* @throws java.io.IOException error for reading file bytes.
|
||||
* @throws NoSuchAlgorithmException If an unknown Algorithm is encountered.
|
||||
|
@ -30,6 +30,7 @@ public final class PciIds {
|
||||
{
|
||||
add("/usr/share/hwdata/pci.ids");
|
||||
add("/usr/share/misc/pci.ids");
|
||||
add("/tmp/pci.ids");
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -151,6 +151,8 @@ namespace string_utils {
|
||||
std::string trimWhitespaceFromRight(std::string str);
|
||||
|
||||
std::string trimWhitespaceFromBothEnds(std::string str);
|
||||
|
||||
std::vector<std::string> split(const std::string& str, char delim);
|
||||
} // namespace string_utils
|
||||
|
||||
} // namespace hirs
|
||||
|
@ -79,6 +79,7 @@ message TpmQuote {
|
||||
|
||||
message IdentityClaimResponse {
|
||||
required bytes credential_blob = 1;
|
||||
optional string pcr_mask = 2;
|
||||
}
|
||||
|
||||
message CertificateRequest {
|
||||
|
@ -81,18 +81,25 @@ string RestfulClientProvisioner::sendIdentityClaim(
|
||||
}
|
||||
|
||||
IdentityClaimResponse response;
|
||||
response.ParseFromString(r.text);
|
||||
|
||||
{
|
||||
// Convert the nonce blob to hex for logging
|
||||
string blobHex = binaryToHex(response.credential_blob());
|
||||
stringstream logStream;
|
||||
logStream << "Received nonce blob: " << blobHex;
|
||||
LOGGER.info(logStream.str());
|
||||
try {
|
||||
response.ParseFromString(r.text);
|
||||
} catch (const google::protobuf::FatalException& e) {
|
||||
LOGGER.error(e.what());
|
||||
stringstream errormsg;
|
||||
errormsg << "Provisioning failed. IdentityClaimResponse "
|
||||
<< "did not contain credential_blob.";
|
||||
throw HirsRuntimeException(errormsg.str(),
|
||||
"RestfulClientProvisioner::sendIdentityClaim");
|
||||
}
|
||||
|
||||
// Return the wrapped nonce blob
|
||||
return response.credential_blob();
|
||||
// Convert the nonce blob to hex for logging
|
||||
string blobHex = binaryToHex(response.credential_blob());
|
||||
stringstream logStream;
|
||||
logStream << "Received nonce blob: " << blobHex;
|
||||
LOGGER.info(logStream.str());
|
||||
|
||||
// Return the response
|
||||
return response.SerializeAsString();
|
||||
|
||||
} else {
|
||||
stringstream errormsg;
|
||||
|
@ -39,6 +39,7 @@ using std::cerr;
|
||||
using std::endl;
|
||||
using std::string;
|
||||
using std::stringstream;
|
||||
using std::vector;
|
||||
|
||||
int provision() {
|
||||
Logger logger = Logger::getDefaultLogger();
|
||||
@ -67,7 +68,10 @@ int provision() {
|
||||
// if platformCredential is empty, not in TPM
|
||||
// pull from properties file
|
||||
if (platformCredential.empty()) {
|
||||
const std::string& cert_dir = props.get("tcg.cert.dir", "");
|
||||
const std::string& cert_dir =
|
||||
props.get(
|
||||
"tcg.cert.dir",
|
||||
"/boot/tcg/cert/platform/");
|
||||
try {
|
||||
platformCredentials =
|
||||
hirs::file_utils::search_directory(cert_dir);
|
||||
@ -85,9 +89,18 @@ int provision() {
|
||||
// collect TCG Boot files
|
||||
std::vector<string> rim_files;
|
||||
std::vector<string> swidtag_files;
|
||||
const std::string& rim_dir = props.get("tcg.rim.dir", "");
|
||||
const std::string& swid_dir = props.get("tcg.swidtag.dir", "");
|
||||
const std::string& live_log_file = props.get("tcg.event.file", "");
|
||||
const std::string& rim_dir =
|
||||
props.get(
|
||||
"tcg.rim.dir",
|
||||
"/boot/tcg/manifest/rim/");
|
||||
const std::string& swid_dir =
|
||||
props.get(
|
||||
"tcg.swidtag.dir",
|
||||
"/boot/tcg/manifest/swidtag/");
|
||||
const std::string& live_log_file =
|
||||
props.get(
|
||||
"tcg.event.file",
|
||||
"/sys/kernel/security/tpm0/binary_bios_measurements");
|
||||
|
||||
try {
|
||||
rim_files = hirs::file_utils::search_directory(rim_dir);
|
||||
@ -128,13 +141,16 @@ int provision() {
|
||||
"TPM2_Provisioner.cpp", __LINE__);
|
||||
identityClaim.set_paccoroutput(paccorOutputString);
|
||||
RestfulClientProvisioner provisioner;
|
||||
string nonceBlob = provisioner.sendIdentityClaim(identityClaim);
|
||||
if (nonceBlob == "") {
|
||||
string response = provisioner.sendIdentityClaim(identityClaim);
|
||||
hirs::pb::IdentityClaimResponse icr;
|
||||
if (!icr.ParseFromString(response) || !icr.has_credential_blob()) {
|
||||
cout << "----> Provisioning failed." << endl;
|
||||
cout << "Please refer to the Attestation CA for details." << endl;
|
||||
cout << "The ACA did not send make credential information." << endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
string nonceBlob = icr.credential_blob();
|
||||
|
||||
// activateIdentity requires we read makeCredential output from a file
|
||||
cout << "----> Received response. Attempting to decrypt nonce" << endl;
|
||||
try {
|
||||
@ -152,9 +168,10 @@ int provision() {
|
||||
hirs::pb::CertificateRequest certificateRequest;
|
||||
certificateRequest.set_nonce(decryptedNonce);
|
||||
certificateRequest.set_quote(tpm2.getQuote(
|
||||
"0,1,2,3,4,5,6,7,8,9,10,11,12,13,"
|
||||
"14,15,16,17,18,19,20,21,22,23",
|
||||
decryptedNonce));
|
||||
icr.has_pcr_mask()
|
||||
? icr.pcr_mask()
|
||||
: "0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23",
|
||||
decryptedNonce));
|
||||
|
||||
const string& akCertificateByteString
|
||||
= provisioner.sendAttestationCertificateRequest(certificateRequest);
|
||||
|
@ -293,6 +293,18 @@ namespace string_utils {
|
||||
return trimWhitespaceFromRight(trimWhitespaceFromLeft(str));
|
||||
}
|
||||
|
||||
vector<string> split(const string &str, char delim) {
|
||||
vector<string> result;
|
||||
stringstream ss(str);
|
||||
string item;
|
||||
|
||||
while (getline(ss, item, delim)) {
|
||||
result.push_back(item);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace string_utils
|
||||
|
||||
} // namespace hirs
|
||||
|
@ -39,6 +39,7 @@ dependencies {
|
||||
compile libs.joda_time
|
||||
compile libs.log4j2
|
||||
compile libs.mariadb
|
||||
compile libs.pci_ids
|
||||
compile libs.reflections
|
||||
compile libs.guava
|
||||
compile libs.spring_core
|
||||
@ -48,6 +49,7 @@ dependencies {
|
||||
exclude group: 'junit'
|
||||
}
|
||||
compile 'org.jboss.logging:jboss-logging:3.2.0.Final'
|
||||
compile 'org.apache.commons:commons-text:1.9'
|
||||
|
||||
// add spring plugin, but do not pull transitive dependencies (causes conflicts)
|
||||
compile(libs.spring_plugin) {
|
||||
|
@ -13,7 +13,7 @@ import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -40,6 +40,7 @@ public final class PCRPolicy extends Policy {
|
||||
private static final int TBOOT_PCR_END = 19;
|
||||
// PCR 5
|
||||
private static final int GPT_PCR = 5;
|
||||
private static final int IMA_MASK = 0xfffbff;
|
||||
|
||||
// Event Log Event Types
|
||||
private static final String EVT_EFI_BOOT = "EV_EFI_BOOT_SERVICES_APPLICATION";
|
||||
@ -169,20 +170,29 @@ public final class PCRPolicy extends Policy {
|
||||
boolean validated = false;
|
||||
short localityAtRelease = 0;
|
||||
String quoteString = new String(tpmQuote, StandardCharsets.UTF_8);
|
||||
int pcrMaskSelection = PcrSelection.ALL_PCRS_ON;
|
||||
|
||||
if (enableIgnoreIma) {
|
||||
pcrMaskSelection = IMA_MASK;
|
||||
}
|
||||
|
||||
ArrayList<TPMMeasurementRecord> measurements = new ArrayList<>();
|
||||
|
||||
TPMMeasurementRecord[] measurements = new TPMMeasurementRecord[baselinePcrs.length];
|
||||
try {
|
||||
for (int i = 0; i <= TPMMeasurementRecord.MAX_PCR_ID; i++) {
|
||||
measurements[i] = new TPMMeasurementRecord(i, storedPcrs[i]);
|
||||
for (int i = 0; i < storedPcrs.length; i++) {
|
||||
if (i == IMA_PCR && enableIgnoreIma) {
|
||||
LOGGER.info("Ignore IMA PCR policy is enabled.");
|
||||
} else {
|
||||
measurements.add(new TPMMeasurementRecord(i, storedPcrs[i]));
|
||||
}
|
||||
}
|
||||
} catch (DecoderException deEx) {
|
||||
LOGGER.error(deEx);
|
||||
}
|
||||
|
||||
PcrSelection pcrSelection = new PcrSelection(PcrSelection.ALL_PCRS_ON);
|
||||
PcrSelection pcrSelection = new PcrSelection(pcrMaskSelection);
|
||||
PcrComposite pcrComposite = new PcrComposite(
|
||||
pcrSelection,
|
||||
Arrays.asList(measurements));
|
||||
pcrSelection, measurements);
|
||||
PcrInfoShort pcrInfoShort = new PcrInfoShort(pcrSelection,
|
||||
localityAtRelease,
|
||||
tpmQuote, pcrComposite);
|
||||
@ -197,6 +207,9 @@ public final class PCRPolicy extends Policy {
|
||||
String calculatedString = Hex.encodeHexString(
|
||||
pcrInfoShort.getCalculatedDigest());
|
||||
validated = quoteString.contains(calculatedString);
|
||||
if (!validated) {
|
||||
LOGGER.warn(calculatedString + " not found in " + quoteString);
|
||||
}
|
||||
} catch (NoSuchAlgorithmException naEx) {
|
||||
LOGGER.error(naEx);
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ import hirs.data.persist.TPMReport;
|
||||
import hirs.data.persist.baseline.TpmBlackListBaseline;
|
||||
import hirs.data.persist.baseline.TpmWhiteListBaseline;
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
import org.apache.commons.lang3.StringEscapeUtils;
|
||||
import org.apache.commons.text.StringEscapeUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
194
HIRS_Utils/src/main/java/hirs/utils/PciIds.java
Normal file
194
HIRS_Utils/src/main/java/hirs/utils/PciIds.java
Normal file
@ -0,0 +1,194 @@
|
||||
package hirs.utils;
|
||||
|
||||
import com.github.marandus.pciid.model.Device;
|
||||
import com.github.marandus.pciid.model.Vendor;
|
||||
import com.github.marandus.pciid.service.PciIdsDatabase;
|
||||
import com.google.common.base.Strings;
|
||||
import hirs.data.persist.certificate.attributes.ComponentIdentifier;
|
||||
import hirs.data.persist.certificate.attributes.V2.ComponentIdentifierV2;
|
||||
import org.bouncycastle.asn1.DERUTF8String;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* Provide Java access to PCI IDs.
|
||||
*/
|
||||
public final class PciIds {
|
||||
/**
|
||||
* This pci ids file can be in different places on different distributions.
|
||||
*/
|
||||
public static final List<String> PCI_IDS_PATH =
|
||||
Collections.unmodifiableList(new Vector<String>() {
|
||||
private static final long serialVersionUID = 1L;
|
||||
{
|
||||
add("/usr/share/hwdata/pci.ids");
|
||||
add("/usr/share/misc/pci.ids");
|
||||
add("/tmp/pci.ids");
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* The PCI IDs Database object.
|
||||
*
|
||||
* This only needs to be loaded one time.
|
||||
*
|
||||
* The pci ids library protects the data inside the object by making it immutable.
|
||||
*/
|
||||
public static final PciIdsDatabase DB = new PciIdsDatabase();
|
||||
|
||||
static {
|
||||
if (!DB.isReady()) {
|
||||
String dbFile = null;
|
||||
for (final String path : PCI_IDS_PATH) {
|
||||
if ((new File(path)).exists()) {
|
||||
dbFile = path;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (dbFile != null) {
|
||||
InputStream is = null;
|
||||
try {
|
||||
is = new FileInputStream(new File(dbFile));
|
||||
DB.loadStream(is);
|
||||
} catch (IOException e) {
|
||||
// DB will not be ready, hardware IDs will not be translated
|
||||
dbFile = null;
|
||||
} finally {
|
||||
if (is != null) {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
dbFile = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility class.
|
||||
*/
|
||||
private PciIds() {
|
||||
}
|
||||
|
||||
/**
|
||||
* The Component Class TCG Registry OID.
|
||||
*/
|
||||
public static final String COMPCLASS_TCG_OID = "2.23.133.18.3.1";
|
||||
/**
|
||||
* The Component Class Value mask for NICs.
|
||||
*/
|
||||
public static final String COMPCLASS_TCG_CAT_NIC = "00090000";
|
||||
/**
|
||||
* The Component Class Value mask for GFX cards.
|
||||
*/
|
||||
public static final String COMPCLASS_TCG_CAT_GFX = "00050000";
|
||||
|
||||
/**
|
||||
* Iterate through all components and translate PCI hardware IDs as necessary. It will only
|
||||
* translate ComponentIdentifierV2+ objects as it relies on Component Class information.
|
||||
* @param components List of ComponentIdentifiers.
|
||||
* @return the translated list of ComponentIdentifiers.
|
||||
*/
|
||||
public static List<ComponentIdentifier> translate(
|
||||
final List<ComponentIdentifier> components) {
|
||||
Vector<ComponentIdentifier> newList = new Vector<ComponentIdentifier>();
|
||||
if (components != null && !components.isEmpty()) {
|
||||
for (final ComponentIdentifier component : components) {
|
||||
// V2 components should not be found alongside V1 components
|
||||
// they pass through just in case
|
||||
if (component.isVersion2()) {
|
||||
newList.add(translate((ComponentIdentifierV2) component));
|
||||
} else {
|
||||
newList.add(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
return newList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate Vendor and Device IDs, if found, in ComponentIdentifierV2 objects.
|
||||
* It will only translate ID values, any other value will pass through.
|
||||
* @param component ComponentIdentifierV2 object.
|
||||
* @return the translated ComponentIdentifierV2 object.
|
||||
*/
|
||||
public static ComponentIdentifierV2 translate(final ComponentIdentifierV2 component) {
|
||||
ComponentIdentifierV2 newComponent = null;
|
||||
if (component != null) {
|
||||
newComponent = component;
|
||||
// This can be updated as we get more accurate component class registries and values
|
||||
// Component Class Registry not accessible: TCG assumed
|
||||
final String compClassValue = component.getComponentClass().getCategoryValue();
|
||||
if (compClassValue.equals(COMPCLASS_TCG_CAT_NIC)
|
||||
|| compClassValue.equals(COMPCLASS_TCG_CAT_GFX)) {
|
||||
DERUTF8String manufacturer = translateVendor(component.getComponentManufacturer());
|
||||
DERUTF8String model = translateDevice(component.getComponentManufacturer(),
|
||||
component.getComponentModel());
|
||||
|
||||
newComponent = new ComponentIdentifierV2(component.getComponentClass(),
|
||||
manufacturer,
|
||||
model,
|
||||
component.getComponentSerial(),
|
||||
component.getComponentRevision(),
|
||||
component.getComponentManufacturerId(),
|
||||
component.getFieldReplaceable(),
|
||||
component.getComponentAddress(),
|
||||
component.getCertificateIdentifier(),
|
||||
component.getComponentPlatformUri(),
|
||||
component.getAttributeStatus());
|
||||
}
|
||||
|
||||
}
|
||||
return newComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up the vendor name from the PCI IDs list, if the input string contains an ID.
|
||||
* If any part of this fails, return the original manufacturer value.
|
||||
* @param refManufacturer DERUTF8String, likely from a ComponentIdentifier
|
||||
* @return DERUTF8String with the discovered vendor name, or the original manufacturer value.
|
||||
*/
|
||||
public static DERUTF8String translateVendor(final DERUTF8String refManufacturer) {
|
||||
DERUTF8String manufacturer = refManufacturer;
|
||||
if (manufacturer != null && manufacturer.getString().trim().matches("^[0-9A-Fa-f]{4}$")) {
|
||||
Vendor ven = DB.findVendor(manufacturer.getString().toLowerCase());
|
||||
if (ven != null && !Strings.isNullOrEmpty(ven.getName())) {
|
||||
manufacturer = new DERUTF8String(ven.getName());
|
||||
}
|
||||
}
|
||||
return manufacturer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up the device name from the PCI IDs list, if the input strings contain IDs.
|
||||
* The Device lookup requires the Vendor ID AND the Device ID to be valid values.
|
||||
* If any part of this fails, return the original model value.
|
||||
* @param refManufacturer DERUTF8String, likely from a ComponentIdentifier
|
||||
* @param refModel DERUTF8String, likely from a ComponentIdentifier
|
||||
* @return DERUTF8String with the discovered device name, or the original model value.
|
||||
*/
|
||||
public static DERUTF8String translateDevice(final DERUTF8String refManufacturer,
|
||||
final DERUTF8String refModel) {
|
||||
DERUTF8String manufacturer = refManufacturer;
|
||||
DERUTF8String model = refModel;
|
||||
if (manufacturer != null
|
||||
&& model != null
|
||||
&& manufacturer.getString().trim().matches("^[0-9A-Fa-f]{4}$")
|
||||
&& model.getString().trim().matches("^[0-9A-Fa-f]{4}$")) {
|
||||
Device dev = DB.findDevice(manufacturer.getString().toLowerCase(),
|
||||
model.getString().toLowerCase());
|
||||
if (dev != null && !Strings.isNullOrEmpty(dev.getName())) {
|
||||
model = new DERUTF8String(dev.getName());
|
||||
}
|
||||
}
|
||||
return model;
|
||||
}
|
||||
}
|
@ -1,7 +1,11 @@
|
||||
package hirs.utils;
|
||||
|
||||
import hirs.data.persist.ReferenceManifest;
|
||||
import hirs.data.persist.certificate.CertificateAuthorityCredential;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.bouncycastle.asn1.x509.Extension;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.SAXException;
|
||||
@ -23,7 +27,6 @@ import javax.xml.crypto.dsig.XMLSignatureException;
|
||||
import javax.xml.crypto.dsig.XMLSignatureFactory;
|
||||
import javax.xml.crypto.dsig.dom.DOMValidateContext;
|
||||
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
|
||||
import javax.xml.crypto.dsig.keyinfo.KeyValue;
|
||||
import javax.xml.crypto.dsig.keyinfo.X509Data;
|
||||
import javax.xml.transform.Source;
|
||||
import javax.xml.transform.Transformer;
|
||||
@ -34,14 +37,18 @@ import javax.xml.transform.dom.DOMResult;
|
||||
import javax.xml.transform.stream.StreamSource;
|
||||
import javax.xml.validation.Schema;
|
||||
import javax.xml.validation.SchemaFactory;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.Key;
|
||||
import java.security.KeyException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
|
||||
|
||||
@ -52,7 +59,7 @@ import java.util.Iterator;
|
||||
*/
|
||||
public class ReferenceManifestValidator {
|
||||
private static final String SIGNATURE_ALGORITHM_RSA_SHA256 =
|
||||
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
|
||||
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
|
||||
private static final String SCHEMA_PACKAGE = "hirs.utils.xjc";
|
||||
private static final String SCHEMA_URL = "swid_schema.xsd";
|
||||
private static final String SCHEMA_LANGUAGE = XMLConstants.W3C_XML_SCHEMA_NS_URI;
|
||||
@ -63,12 +70,29 @@ public class ReferenceManifestValidator {
|
||||
private static final int RADIX = 16;
|
||||
private static final Logger LOGGER = LogManager.getLogger(ReferenceManifestValidator.class);
|
||||
|
||||
private Document rim;
|
||||
private Unmarshaller unmarshaller;
|
||||
private PublicKey publicKey;
|
||||
private Schema schema;
|
||||
private String subjectKeyIdentifier;
|
||||
private boolean signatureValid, supportRimValid;
|
||||
|
||||
/**
|
||||
* Setter for the RIM to be validated. The ReferenceManifest object is converted into a
|
||||
* Document for processing.
|
||||
*
|
||||
* @param rim ReferenceManifest object
|
||||
*/
|
||||
public void setRim(final ReferenceManifest rim) {
|
||||
try {
|
||||
Document doc = validateSwidtagSchema(removeXMLWhitespace(new StreamSource(
|
||||
new ByteArrayInputStream(rim.getRimBytes()))));
|
||||
this.rim = doc;
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Error while unmarshalling rim bytes: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for signatureValid.
|
||||
*
|
||||
@ -98,6 +122,7 @@ public class ReferenceManifestValidator {
|
||||
|
||||
/**
|
||||
* Getter for subjectKeyIdentifier.
|
||||
*
|
||||
* @return subjectKeyIdentifier
|
||||
*/
|
||||
public String getSubjectKeyIdentifier() {
|
||||
@ -114,62 +139,87 @@ public class ReferenceManifestValidator {
|
||||
.getClassLoader().getResourceAsStream(SCHEMA_URL);
|
||||
SchemaFactory schemaFactory = SchemaFactory.newInstance(SCHEMA_LANGUAGE);
|
||||
schema = schemaFactory.newSchema(new StreamSource(is));
|
||||
rim = null;
|
||||
signatureValid = false;
|
||||
supportRimValid = false;
|
||||
publicKey = null;
|
||||
subjectKeyIdentifier = "";
|
||||
subjectKeyIdentifier = "(not found)";
|
||||
} catch (SAXException e) {
|
||||
LOGGER.warn("Error setting schema for validation!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This constructor is used for a quick signature check which bypasses the loading of
|
||||
* the schema url into memory. As a result the full stream is not validated against the schema.
|
||||
* This method attempts to validate the signature element of the instance's RIM
|
||||
* using a given cert. The cert is compared to either the RIM's embedded certificate
|
||||
* or the RIM's subject key identifier. If the cert is matched then validation proceeds,
|
||||
* otherwise validation ends.
|
||||
*
|
||||
* @param input xml data byte array.
|
||||
* @param cert the cert to be checked against the RIM
|
||||
* @return true if the signature element is validated, false otherwise
|
||||
*/
|
||||
public ReferenceManifestValidator(final InputStream input) {
|
||||
@SuppressWarnings("magicnumber")
|
||||
public boolean validateXmlSignature(final CertificateAuthorityCredential cert) {
|
||||
DOMValidateContext context = null;
|
||||
try {
|
||||
signatureValid = validateSignedXMLDocument(
|
||||
removeXMLWhitespace(new StreamSource(input)));
|
||||
NodeList nodes = rim.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
|
||||
if (nodes.getLength() == 0) {
|
||||
LOGGER.error("Cannot validate RIM, signature element not found!");
|
||||
return false;
|
||||
}
|
||||
NodeList certElement = rim.getElementsByTagName("X509Certificate");
|
||||
if (certElement.getLength() > 0) {
|
||||
X509Certificate embeddedCert = parseCertFromPEMString(
|
||||
certElement.item(0).getTextContent());
|
||||
if (embeddedCert != null) {
|
||||
subjectKeyIdentifier = getCertificateSubjectKeyIdentifier(embeddedCert);
|
||||
if (Arrays.equals(embeddedCert.getPublicKey().getEncoded(),
|
||||
cert.getEncodedPublicKey())) {
|
||||
context = new DOMValidateContext(new X509KeySelector(), nodes.item(0));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
subjectKeyIdentifier = getKeyName(rim);
|
||||
if (subjectKeyIdentifier.equals(cert.getSubjectKeyIdString().substring(8))) {
|
||||
context = new DOMValidateContext(cert.getX509Certificate().getPublicKey(),
|
||||
nodes.item(0));
|
||||
}
|
||||
}
|
||||
if (context != null) {
|
||||
publicKey = cert.getX509Certificate().getPublicKey();
|
||||
signatureValid = validateSignedXMLDocument(context);
|
||||
return signatureValid;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Error during unmarshal: " + e.getMessage());
|
||||
LOGGER.warn("Error while parsing certificate data: " + e.getMessage());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method calculates the SHA256 hash of the input byte array and compares it against
|
||||
* the value passed in.
|
||||
*
|
||||
* @param input byte array to hash.
|
||||
* @param input byte array to hash.
|
||||
* @param expected value to compare against.
|
||||
*/
|
||||
public void validateSupportRimHash(final byte[] input, final String expected) {
|
||||
String calculatedHash = getHashValue(input, SHA256);
|
||||
LOGGER.info("Calculated hash: " + calculatedHash + ", actual: " + expected);
|
||||
supportRimValid = calculatedHash.equals(expected);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method validates the xml signature in the stream and stores the
|
||||
* result for public access.
|
||||
*
|
||||
* @param input the xml data stream.
|
||||
*/
|
||||
public void validateXmlSignature(final InputStream input) {
|
||||
try {
|
||||
Document doc = validateSwidtagSchema(removeXMLWhitespace(new StreamSource(input)));
|
||||
signatureValid = validateSignedXMLDocument(doc);
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Error during unmarshal: " + e.getMessage());
|
||||
if (!supportRimValid) {
|
||||
LOGGER.info("Unmatched support RIM hash! Expected: " + expected
|
||||
+ ", actual: " + calculatedHash);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method calculates the digest of a byte array based on the hashing algorithm passed in.
|
||||
*
|
||||
* @param input byte array.
|
||||
* @param sha hash algorithm.
|
||||
* @param sha hash algorithm.
|
||||
* @return String digest.
|
||||
*/
|
||||
private String getHashValue(final byte[] input, final String sha) {
|
||||
@ -181,7 +231,7 @@ public class ReferenceManifestValidator {
|
||||
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
sb.append(Integer.toString((bytes[i] & EIGHT_BIT_MASK)
|
||||
+ LEFT_SHIFT, RADIX).substring(1));
|
||||
+ LEFT_SHIFT, RADIX).substring(1));
|
||||
}
|
||||
resultString = sb.toString();
|
||||
} catch (NoSuchAlgorithmException grex) {
|
||||
@ -191,37 +241,18 @@ public class ReferenceManifestValidator {
|
||||
return resultString;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method validates a Document with a signature element.
|
||||
*
|
||||
* @param doc
|
||||
*/
|
||||
private boolean validateSignedXMLDocument(final Document doc) {
|
||||
DOMValidateContext context;
|
||||
boolean isValid = false;
|
||||
private boolean validateSignedXMLDocument(final DOMValidateContext context) {
|
||||
try {
|
||||
NodeList nodes = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
|
||||
if (nodes.getLength() == 0) {
|
||||
throw new Exception("Signature element not found!");
|
||||
}
|
||||
X509KeySelector keySelector = new ReferenceManifestValidator.X509KeySelector();
|
||||
context = new DOMValidateContext(keySelector, nodes.item(0));
|
||||
XMLSignatureFactory sigFactory = XMLSignatureFactory.getInstance("DOM");
|
||||
XMLSignature signature = sigFactory.unmarshalXMLSignature(context);
|
||||
isValid = signature.validate(context);
|
||||
publicKey = keySelector.getPublicKey();
|
||||
subjectKeyIdentifier = getKeyName(doc);
|
||||
return signature.validate(context);
|
||||
} catch (MarshalException e) {
|
||||
LOGGER.warn("Error while unmarshalling XML signature: " + e.getMessage());
|
||||
} catch (XMLSignatureException e) {
|
||||
LOGGER.warn("Error while validating XML signature: " + e.getMessage());
|
||||
} catch (KeySelectorException e) {
|
||||
LOGGER.warn("Public key not found in XML signature: " + e.getMessage());
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn(e.getMessage());
|
||||
}
|
||||
|
||||
return isValid;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -230,8 +261,6 @@ public class ReferenceManifestValidator {
|
||||
* an XML signature.
|
||||
*/
|
||||
public static class X509KeySelector extends KeySelector {
|
||||
private PublicKey publicKey;
|
||||
|
||||
/**
|
||||
* This method selects a public key for validation.
|
||||
* PKs are parsed preferentially from the following elements:
|
||||
@ -240,10 +269,10 @@ public class ReferenceManifestValidator {
|
||||
* The parsed PK is then verified based on the provided algorithm before
|
||||
* being returned in a KeySelectorResult.
|
||||
*
|
||||
* @param keyinfo object containing the cert.
|
||||
* @param purpose purpose.
|
||||
* @param keyinfo object containing the cert.
|
||||
* @param purpose purpose.
|
||||
* @param algorithm algorithm.
|
||||
* @param context XMLCryptoContext.
|
||||
* @param context XMLCryptoContext.
|
||||
* @return KeySelectorResult holding the PublicKey.
|
||||
* @throws KeySelectorException exception.
|
||||
*/
|
||||
@ -251,7 +280,7 @@ public class ReferenceManifestValidator {
|
||||
final KeySelector.Purpose purpose,
|
||||
final AlgorithmMethod algorithm,
|
||||
final XMLCryptoContext context)
|
||||
throws KeySelectorException {
|
||||
throws KeySelectorException {
|
||||
Iterator keyinfoItr = keyinfo.getContent().iterator();
|
||||
while (keyinfoItr.hasNext()) {
|
||||
XMLStructure element = (XMLStructure) keyinfoItr.next();
|
||||
@ -261,32 +290,23 @@ public class ReferenceManifestValidator {
|
||||
while (dataItr.hasNext()) {
|
||||
Object object = dataItr.next();
|
||||
if (object instanceof X509Certificate) {
|
||||
publicKey = ((X509Certificate) object).getPublicKey();
|
||||
break;
|
||||
final PublicKey publicKey = ((X509Certificate) object).getPublicKey();
|
||||
if (areAlgorithmsEqual(algorithm.getAlgorithm(),
|
||||
publicKey.getAlgorithm())) {
|
||||
return new ReferenceManifestValidator.X509KeySelector
|
||||
.RIMKeySelectorResult(publicKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (element instanceof KeyValue) {
|
||||
try {
|
||||
publicKey = ((KeyValue) element).getPublicKey();
|
||||
} catch (KeyException e) {
|
||||
LOGGER.warn("KeyException thrown while getting PK from KeyValue: "
|
||||
+ e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (areAlgorithmsEqual(algorithm.getAlgorithm(),
|
||||
publicKey.getAlgorithm())) {
|
||||
return new ReferenceManifestValidator.X509KeySelector
|
||||
.RIMKeySelectorResult(publicKey);
|
||||
}
|
||||
|
||||
throw new KeySelectorException("No key found!");
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks if two strings refer to the same algorithm.
|
||||
*
|
||||
* @param uri string 1
|
||||
* @param uri string 1
|
||||
* @param name string 2
|
||||
* @return true if equal, false if not
|
||||
*/
|
||||
@ -294,15 +314,6 @@ public class ReferenceManifestValidator {
|
||||
return uri.equals(SIGNATURE_ALGORITHM_RSA_SHA256) && name.equalsIgnoreCase("RSA");
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the public key that is parsed in the select() method above.
|
||||
*
|
||||
* @return PublicKey encoded in the X509 cert.
|
||||
*/
|
||||
public PublicKey getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* This internal class creates a KeySelectorResult from the public key.
|
||||
*/
|
||||
@ -319,6 +330,54 @@ public class ReferenceManifestValidator {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, or null
|
||||
* @throws Exception if certificate cannot be successfully parsed
|
||||
*/
|
||||
private X509Certificate parseCertFromPEMString(final String pemString) throws Exception {
|
||||
String certificateHeader = "-----BEGIN CERTIFICATE-----";
|
||||
String certificateFooter = "-----END CERTIFICATE-----";
|
||||
try {
|
||||
CertificateFactory factory = CertificateFactory.getInstance("X.509");
|
||||
InputStream inputStream = new ByteArrayInputStream((certificateHeader
|
||||
+ System.lineSeparator()
|
||||
+ pemString
|
||||
+ System.lineSeparator()
|
||||
+ certificateFooter).getBytes("UTF-8"));
|
||||
return (X509Certificate) factory.generateCertificate(inputStream);
|
||||
} catch (CertificateException e) {
|
||||
LOGGER.warn("Error creating CertificateFactory instance: " + e.getMessage());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
LOGGER.warn("Error while parsing cert from PEM string: " + e.getMessage());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
private String getCertificateSubjectKeyIdentifier(final X509Certificate certificate)
|
||||
throws IOException {
|
||||
String decodedValue;
|
||||
byte[] extension = certificate.getExtensionValue(Extension.subjectKeyIdentifier.getId());
|
||||
if (extension != null && extension.length > 0) {
|
||||
decodedValue = JcaX509ExtensionUtils.parseExtensionValue(extension).toString();
|
||||
} else {
|
||||
decodedValue = " "; //Unlikely that a proper X509Certificate does not have a skid
|
||||
}
|
||||
return decodedValue.substring(1); //Drop the # at the beginning of the string
|
||||
}
|
||||
|
||||
/**
|
||||
* This method parses the subject key identifier from the KeyName element of a signature.
|
||||
*
|
||||
@ -368,7 +427,7 @@ public class ReferenceManifestValidator {
|
||||
TransformerFactory tf = TransformerFactory.newInstance();
|
||||
Source identitySource = new StreamSource(
|
||||
ReferenceManifestValidator.class.getClassLoader()
|
||||
.getResourceAsStream(IDENTITY_TRANSFORM));
|
||||
.getResourceAsStream(IDENTITY_TRANSFORM));
|
||||
Document doc = null;
|
||||
try {
|
||||
Transformer transformer = tf.newTransformer(identitySource);
|
||||
|
@ -13,6 +13,7 @@ import hirs.data.persist.certificate.attributes.ComponentIdentifier;
|
||||
import hirs.data.persist.certificate.attributes.V2.ComponentIdentifierV2;
|
||||
import hirs.data.persist.info.ComponentInfo;
|
||||
import hirs.data.persist.info.HardwareInfo;
|
||||
import hirs.utils.PciIds;
|
||||
import org.apache.commons.codec.Charsets;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@ -91,7 +92,7 @@ public final class SupplyChainCredentialValidator implements CredentialValidator
|
||||
"Platform credential attributes validated";
|
||||
|
||||
/**
|
||||
* AppraisalStatus message for a valid platform credential appraisal.
|
||||
* AppraisalStatus message for a valid firmware appraisal.
|
||||
*/
|
||||
public static final String FIRMWARE_VALID = "Firmware validated";
|
||||
|
||||
@ -564,9 +565,15 @@ public final class SupplyChainCredentialValidator implements CredentialValidator
|
||||
resultMessage.append(unmatchedComponents);
|
||||
|
||||
// pass information of which ones failed in additionInfo
|
||||
int counter = 0;
|
||||
for (ComponentIdentifier ci : validPcComponents) {
|
||||
counter++;
|
||||
additionalInfo.append(String.format("%d;", ci.hashCode()));
|
||||
}
|
||||
if (counter > 0) {
|
||||
additionalInfo.insert(0, "COMPID=");
|
||||
additionalInfo.append(counter);
|
||||
}
|
||||
}
|
||||
|
||||
passesValidation &= fieldValidation;
|
||||
@ -697,6 +704,7 @@ public final class SupplyChainCredentialValidator implements CredentialValidator
|
||||
}
|
||||
|
||||
if (!fieldValidation || !deltaSb.toString().isEmpty()) {
|
||||
deltaSb.insert(0, "COMPID=");
|
||||
return new AppraisalStatus(FAIL, resultMessage.toString(), deltaSb.toString());
|
||||
}
|
||||
|
||||
@ -716,21 +724,29 @@ public final class SupplyChainCredentialValidator implements CredentialValidator
|
||||
LOGGER.error("PACCOR output string:\n" + paccorOutputString);
|
||||
return new AppraisalStatus(ERROR, baseErrorMessage + ioEx.getMessage());
|
||||
}
|
||||
StringBuilder additionalInfo = new StringBuilder();
|
||||
if (!fieldValidation) {
|
||||
// instead of listing all unmatched, just print the #. The failure
|
||||
// will link to the platform certificate that'll display them.
|
||||
String failureResults = unmatchedComponents.substring(0,
|
||||
unmatchedComponents.length() - 1);
|
||||
String size = unmatchedComponents.substring(unmatchedComponents.length() - 1);
|
||||
resultMessage = new StringBuilder();
|
||||
|
||||
resultMessage.append(String.format("There are %s unmatched components "
|
||||
+ "on the Platform Certificate:%n", size));
|
||||
resultMessage.append("There are unmatched components:\n");
|
||||
resultMessage.append(unmatchedComponents);
|
||||
|
||||
return new AppraisalStatus(FAIL, resultMessage.toString(), failureResults);
|
||||
// pass information of which ones failed in additionInfo
|
||||
int counter = 0;
|
||||
for (ComponentIdentifier ci : baseCompList) {
|
||||
counter++;
|
||||
additionalInfo.append(String.format("%d;", ci.hashCode()));
|
||||
}
|
||||
if (counter > 0) {
|
||||
additionalInfo.insert(0, "COMPID=");
|
||||
additionalInfo.append(counter);
|
||||
}
|
||||
}
|
||||
|
||||
if (fieldValidation) {
|
||||
return new AppraisalStatus(PASS, PLATFORM_ATTRIBUTES_VALID);
|
||||
} else {
|
||||
return new AppraisalStatus(FAIL, resultMessage.toString(), additionalInfo.toString());
|
||||
}
|
||||
return new AppraisalStatus(PASS, PLATFORM_ATTRIBUTES_VALID);
|
||||
}
|
||||
|
||||
private static String validateV2PlatformCredentialAttributes(
|
||||
@ -762,14 +778,23 @@ public final class SupplyChainCredentialValidator implements CredentialValidator
|
||||
// now we return everything that was unmatched
|
||||
// what is in the component info/device reported components
|
||||
// is to be displayed as the failure
|
||||
fullDeltaChainComponents.clear();
|
||||
for (ComponentIdentifier ci : subCompIdList) {
|
||||
ciV2 = (ComponentIdentifierV2) ci;
|
||||
invalidPcIds.append(String.format("%d;",
|
||||
ciV2.hashCode()));
|
||||
if (ci.isVersion2() && PciIds.DB.isReady()) {
|
||||
ci = PciIds.translate((ComponentIdentifierV2) ci);
|
||||
}
|
||||
LOGGER.error("Unmatched component: " + ci);
|
||||
fullDeltaChainComponents.add(ci);
|
||||
invalidPcIds.append(String.format(
|
||||
"Manufacturer=%s, Model=%s, Serial=%s, Revision=%s;%n",
|
||||
ci.getComponentManufacturer(),
|
||||
ci.getComponentModel(),
|
||||
ci.getComponentSerial(),
|
||||
ci.getComponentRevision()));
|
||||
}
|
||||
}
|
||||
|
||||
return String.format("COMPID=%s%d", invalidPcIds.toString(), subCompIdList.size());
|
||||
return invalidPcIds.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -794,24 +819,23 @@ public final class SupplyChainCredentialValidator implements CredentialValidator
|
||||
// on the leftovers in the lists and the policy in place.
|
||||
final List<ComponentIdentifier> pcComponents = new ArrayList<>();
|
||||
for (ComponentIdentifier component : untrimmedPcComponents) {
|
||||
DERUTF8String componentSerial = new DERUTF8String("");
|
||||
DERUTF8String componentRevision = new DERUTF8String("");
|
||||
if (component.getComponentManufacturer() != null) {
|
||||
component.setComponentManufacturer(new DERUTF8String(
|
||||
component.getComponentManufacturer().getString().trim()));
|
||||
}
|
||||
if (component.getComponentModel() != null) {
|
||||
component.setComponentModel(new DERUTF8String(
|
||||
component.getComponentModel().getString().trim()));
|
||||
}
|
||||
if (component.getComponentSerial() != null) {
|
||||
componentSerial = new DERUTF8String(
|
||||
component.getComponentSerial().getString().trim());
|
||||
component.setComponentSerial(new DERUTF8String(
|
||||
component.getComponentSerial().getString().trim()));
|
||||
}
|
||||
if (component.getComponentRevision() != null) {
|
||||
componentRevision = new DERUTF8String(
|
||||
component.getComponentRevision().getString().trim());
|
||||
component.setComponentRevision(new DERUTF8String(
|
||||
component.getComponentRevision().getString().trim()));
|
||||
}
|
||||
pcComponents.add(
|
||||
new ComponentIdentifier(
|
||||
new DERUTF8String(component.getComponentManufacturer().getString().trim()),
|
||||
new DERUTF8String(component.getComponentModel().getString().trim()),
|
||||
componentSerial, componentRevision,
|
||||
component.getComponentManufacturerId(),
|
||||
component.getFieldReplaceable(),
|
||||
component.getComponentAddress()));
|
||||
pcComponents.add(component);
|
||||
}
|
||||
|
||||
LOGGER.info("Validating the following Platform Cert components...");
|
||||
@ -835,7 +859,6 @@ public final class SupplyChainCredentialValidator implements CredentialValidator
|
||||
= allDeviceInfoComponents.stream().filter(componentInfo
|
||||
-> componentInfo.getComponentManufacturer().equals(pcManufacturer))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// For each component listed in the platform credential from this manufacturer
|
||||
// find the ones that specify a serial number so we can match the most specific ones
|
||||
// first.
|
||||
@ -844,7 +867,6 @@ public final class SupplyChainCredentialValidator implements CredentialValidator
|
||||
-> compIdentifier.getComponentSerial() != null
|
||||
&& StringUtils.isNotEmpty(compIdentifier.getComponentSerial().getString()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Now match up the components from the device info that are from the same
|
||||
// manufacturer and have a serial number. As matches are found, remove them from
|
||||
// both lists.
|
||||
@ -865,7 +887,6 @@ public final class SupplyChainCredentialValidator implements CredentialValidator
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For each component listed in the platform credential from this manufacturer
|
||||
// find the ones that specify value for the revision field so we can match the most
|
||||
// specific ones first.
|
||||
@ -874,7 +895,6 @@ public final class SupplyChainCredentialValidator implements CredentialValidator
|
||||
-> compIdentifier.getComponentRevision() != null
|
||||
&& StringUtils.isNotEmpty(compIdentifier.getComponentRevision().getString()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Now match up the components from the device info that are from the same
|
||||
// manufacturer and specify a value for the revision field. As matches are found,
|
||||
// remove them from both lists.
|
||||
@ -895,7 +915,6 @@ public final class SupplyChainCredentialValidator implements CredentialValidator
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The remaining components from the manufacturer have only the 2 required fields so
|
||||
// just match them.
|
||||
List<ComponentIdentifier> templist = new ArrayList<>(pcComponentsFromManufacturer);
|
||||
@ -921,6 +940,10 @@ public final class SupplyChainCredentialValidator implements CredentialValidator
|
||||
|
||||
int unmatchedComponentCounter = 1;
|
||||
for (ComponentIdentifier unmatchedComponent : pcUnmatchedComponents) {
|
||||
if (unmatchedComponent.isVersion2() && PciIds.DB.isReady()) {
|
||||
unmatchedComponent =
|
||||
PciIds.translate((ComponentIdentifierV2) unmatchedComponent);
|
||||
}
|
||||
LOGGER.error("Unmatched component " + unmatchedComponentCounter++ + ": "
|
||||
+ unmatchedComponent);
|
||||
sb.append(String.format("Manufacturer=%s, Model=%s, Serial=%s, Revision=%s;%n",
|
||||
@ -1432,7 +1455,7 @@ public final class SupplyChainCredentialValidator implements CredentialValidator
|
||||
}
|
||||
} while (foundRootOfCertChain.equals(intCAError));
|
||||
|
||||
LOGGER.error(foundRootOfCertChain);
|
||||
LOGGER.warn(foundRootOfCertChain);
|
||||
return foundRootOfCertChain;
|
||||
}
|
||||
|
||||
@ -1660,11 +1683,19 @@ public final class SupplyChainCredentialValidator implements CredentialValidator
|
||||
try {
|
||||
cert.verify(signingCert.getPublicKey(), BouncyCastleProvider.PROVIDER_NAME);
|
||||
return true;
|
||||
} catch (InvalidKeyException | CertificateException | NoSuchAlgorithmException
|
||||
| NoSuchProviderException | SignatureException e) {
|
||||
LOGGER.error("Exception thrown while verifying certificate", e);
|
||||
return false;
|
||||
} catch (InvalidKeyException e) {
|
||||
LOGGER.info("Incorrect key given to validate this cert's signature");
|
||||
} catch (CertificateException e) {
|
||||
LOGGER.info("Encoding error while validating this cert's signature");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
LOGGER.info("Unsupported signature algorithm found during validation");
|
||||
} catch (NoSuchProviderException e) {
|
||||
LOGGER.info("Incorrect provider for cert signature validation");
|
||||
} catch (SignatureException e) {
|
||||
LOGGER.info(String.format("%s.verify(%s)", cert.getSubjectDN(),
|
||||
signingCert.getSubjectDN()));
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
@ -1708,7 +1739,9 @@ public final class SupplyChainCredentialValidator implements CredentialValidator
|
||||
new JcaContentVerifierProviderBuilder().setProvider("BC").build(signingKey);
|
||||
return cert.isSignatureValid(contentVerifierProvider);
|
||||
} catch (OperatorCreationException | CertException e) {
|
||||
LOGGER.error("Exception thrown while verifying certificate", e);
|
||||
LOGGER.info("Exception thrown while verifying certificate", e);
|
||||
LOGGER.info(String.format("%s.isSignatureValid(%s)", cert.getSerialNumber(),
|
||||
signingKey.getFormat()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -2256,9 +2256,7 @@ public class SupplyChainCredentialValidatorTest {
|
||||
.validateDeltaPlatformCredentialAttributes(delta1,
|
||||
deviceInfoReport, base, chainCredentials);
|
||||
Assert.assertEquals(result.getAppStatus(), AppraisalStatus.Status.FAIL);
|
||||
Assert.assertEquals(result.getMessage(),
|
||||
"There are 1 unmatched components on the Platform Certificate:\n"
|
||||
+ "COMPID=370101885;1");
|
||||
Assert.assertEquals(result.getAdditionalInfo(), "COMPID=370101885;1");
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user