From 6a2c5d246b78694549c969b722ee316e0f9150a4 Mon Sep 17 00:00:00 2001 From: Cyrus <24922493+cyrus-dev@users.noreply.github.com> Date: Tue, 29 Aug 2023 15:15:34 -0400 Subject: [PATCH 01/16] This is a series of code changes to begin setting up for provisioning. The code has not been tested to provision. The url linkage needs to be worked on again. --- HIRS_AttestationCA/build.gradle | 10 + .../config}/genJavaProtoBuf.sh | 0 .../AttestationCertificateAuthority.java | 138 +++ ...estfulAttestationCertificateAuthority.java | 146 ++++ .../persist/RestfulInterface.java | 14 + .../persist/StorageProperties.java | 21 - .../manager/CACredentialRepository.java | 3 + .../entity/manager/CertificateRepository.java | 9 +- .../ReferenceDigestValueRepository.java | 1 + .../manager/ReferenceManifestRepository.java | 9 +- .../TPM2ProvisionerStateRepository.java | 11 + .../entity/tpm/TPM2ProvisionerState.java | 112 +++ .../entity/userdefined/ExaminableRecord.java | 67 ++ .../CertificateAuthorityCredential.java | 39 - .../IssuedAttestationCertificate.java | 46 +- .../certificate/PlatformCredential.java | 99 --- .../record/TPMMeasurementRecord.java | 130 +++ .../userdefined/report/DeviceInfoReport.java | 2 + .../rim/SupportReferenceManifest.java | 85 -- .../CertificateProcessingException.java | 27 + .../IdentityProcessingException.java | 27 + .../exceptions/UnexpectedServerException.java | 27 + .../provision/AbstractRequestHandler.java | 485 +++++++++++ .../provision/CertificateRequestHandler.java | 218 +++++ .../provision/IdentityClaimHandler.java | 815 ++++++++++++++++++ .../provision/IdentityRequestHandler.java | 574 ++++++++++++ .../helper/CredentialManagementHelper.java | 148 ++++ .../IssuedCertificateAttributeHelper.java | 201 +++++ .../service/CertificateServiceImpl.java | 91 -- .../persist/service/DefaultDbService.java | 191 ---- .../persist/service/DeviceServiceImpl.java | 32 - .../persist/service/FilesStorageService.java | 20 - .../service/FilesStorageServiceImpl.java | 88 -- .../persist/service/PolicyServiceImpl.java | 25 - .../ReferenceDigestValueServiceImpl.java | 21 - .../service/ReferenceManifestServiceImpl.java | 116 --- .../service/SupplyChainValidationService.java | 42 + .../SupplyChainValidationServiceImpl.java | 270 +++++- .../service/selector/CertificateSelector.java | 99 +-- .../selector/ReferenceManifestSelector.java | 56 +- .../persist/tpm/PcrComposite.java | 119 +++ .../persist/tpm/PcrInfoShort.java | 217 +++++ .../persist/tpm/PcrSelection.java | 143 +++ .../persist/validation/PcrValidator.java | 229 +++++ .../SupplyChainCredentialValidator.java | 72 ++ .../portal/PersistenceJPAConfig.java | 47 +- .../CertificateDetailsPageController.java | 6 +- .../ReferenceManifestPageController.java | 5 +- .../utils/CertificateStringMapBuilder.java | 29 +- .../src/main/resources/application.properties | 2 +- HIRS_Structs/build.gradle | 66 +- settings.gradle | 4 +- 52 files changed, 4356 insertions(+), 1098 deletions(-) rename {config => HIRS_AttestationCA/config}/genJavaProtoBuf.sh (100%) create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/AttestationCertificateAuthority.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/RestfulAttestationCertificateAuthority.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/RestfulInterface.java delete mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/StorageProperties.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/manager/TPM2ProvisionerStateRepository.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/tpm/TPM2ProvisionerState.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/ExaminableRecord.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/record/TPMMeasurementRecord.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/exceptions/CertificateProcessingException.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/exceptions/IdentityProcessingException.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/exceptions/UnexpectedServerException.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/AbstractRequestHandler.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/CertificateRequestHandler.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/IdentityClaimHandler.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/IdentityRequestHandler.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/helper/CredentialManagementHelper.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/helper/IssuedCertificateAttributeHelper.java delete mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/CertificateServiceImpl.java delete mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/DefaultDbService.java delete mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/DeviceServiceImpl.java delete mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/FilesStorageService.java delete mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/FilesStorageServiceImpl.java delete mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/PolicyServiceImpl.java delete mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/ReferenceDigestValueServiceImpl.java delete mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/ReferenceManifestServiceImpl.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/SupplyChainValidationService.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/tpm/PcrComposite.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/tpm/PcrInfoShort.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/tpm/PcrSelection.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/validation/PcrValidator.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/validation/SupplyChainCredentialValidator.java diff --git a/HIRS_AttestationCA/build.gradle b/HIRS_AttestationCA/build.gradle index c6f20171..c2473917 100644 --- a/HIRS_AttestationCA/build.gradle +++ b/HIRS_AttestationCA/build.gradle @@ -23,6 +23,7 @@ configurations { dependencies { implementation project(':HIRS_Utils') + implementation project(':HIRS_Structs') implementation 'org.springframework.boot:spring-boot-starter-data-jpa:3.0.1' implementation 'com.github.darrachequesne:spring-data-jpa-datatables:6.0.1' @@ -39,6 +40,7 @@ dependencies { implementation libs.jackson.core implementation libs.jackson.databind implementation libs.minimal.json + implementation libs.protobuf.java implementation 'org.apache.logging.log4j:log4j-core:2.19.0' implementation 'org.apache.logging.log4j:log4j-api:2.19.0' @@ -48,6 +50,14 @@ dependencies { annotationProcessor libs.lombok } +task generateProtoBuf(type:Exec) { + workingDir 'config' + + commandLine './genJavaProtoBuf.sh' +} + +compileJava.dependsOn generateProtoBuf + test { useJUnitPlatform() } diff --git a/config/genJavaProtoBuf.sh b/HIRS_AttestationCA/config/genJavaProtoBuf.sh similarity index 100% rename from config/genJavaProtoBuf.sh rename to HIRS_AttestationCA/config/genJavaProtoBuf.sh diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/AttestationCertificateAuthority.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/AttestationCertificateAuthority.java new file mode 100644 index 00000000..c480a7b9 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/AttestationCertificateAuthority.java @@ -0,0 +1,138 @@ +package hirs.attestationca.persist; + +import hirs.attestationca.persist.entity.manager.CertificateRepository; +import hirs.attestationca.persist.entity.manager.ComponentResultRepository; +import hirs.attestationca.persist.entity.manager.DeviceRepository; +import hirs.attestationca.persist.entity.manager.IssuedCertificateRepository; +import hirs.attestationca.persist.entity.manager.PolicyRepository; +import hirs.attestationca.persist.entity.manager.ReferenceDigestValueRepository; +import hirs.attestationca.persist.entity.manager.ReferenceManifestRepository; +import hirs.attestationca.persist.entity.manager.TPM2ProvisionerStateRepository; +import hirs.attestationca.persist.provision.CertificateRequestHandler; +import hirs.attestationca.persist.provision.IdentityClaimHandler; +import hirs.attestationca.persist.provision.IdentityRequestHandler; +import hirs.attestationca.persist.service.SupplyChainValidationService; +import hirs.structs.converters.StructConverter; +import lombok.extern.log4j.Log4j2; + +import java.security.PrivateKey; +import java.security.cert.X509Certificate; + +/** + * Provides base implementation of common tasks of an ACA that are required for attestation of an + * Identity Request. + */ +@Log4j2 +public abstract class AttestationCertificateAuthority { + + /** + * Container wired ACA private key. + */ + private final PrivateKey privateKey; + + /** + * Container wired ACA certificate. + */ + private final X509Certificate acaCertificate; + + /** + * Container wired {@link StructConverter} to be used in + * serialization / deserialization of TPM data structures. + */ + private final StructConverter structConverter; + + /** + * A handle to the service used to validate the supply chain. + */ + private final SupplyChainValidationService supplyChainValidationService; + + /** + * Container wired application configuration property identifying the number of days that + * certificates issued by this ACA are valid for. + */ + private Integer validDays = 1; + + private final ComponentResultRepository componentResultRepository; + private final CertificateRepository certificateRepository; + private final IssuedCertificateRepository issuedCertificateRepository; + private final ReferenceManifestRepository referenceManifestRepository; + private final DeviceRepository deviceRepository; +// private final DBManager tpm2ProvisionerStateDBManager; + private final ReferenceDigestValueRepository referenceDigestValueRepository; + private final PolicyRepository policyRepository; + private final TPM2ProvisionerStateRepository tpm2ProvisionerStateRepository; + + private CertificateRequestHandler certificateRequestHandler; + private IdentityClaimHandler identityClaimHandler; + private IdentityRequestHandler identityRequestHandler; + + /** + * Constructor. + * @param supplyChainValidationService the supply chain service + * @param privateKey the ACA private key + * @param acaCertificate the ACA certificate + * @param structConverter the struct converter + * @param componentResultRepository the component result manager + * @param certificateRepository the certificate manager + * @param referenceManifestRepository the Reference Manifest manager + * @param validDays the number of days issued certs are valid + * @param deviceRepository the device manager + * @param referenceDigestValueRepository the reference event manager + * @param policyRepository + * @param tpm2ProvisionerStateRepository + */ + @SuppressWarnings("checkstyle:parameternumber") + public AttestationCertificateAuthority( + final SupplyChainValidationService supplyChainValidationService, + final PrivateKey privateKey, final X509Certificate acaCertificate, + final StructConverter structConverter, + final ComponentResultRepository componentResultRepository, + final CertificateRepository certificateRepository, + final IssuedCertificateRepository issuedCertificateRepository, + final ReferenceManifestRepository referenceManifestRepository, + final int validDays, + final DeviceRepository deviceRepository, + final ReferenceDigestValueRepository referenceDigestValueRepository, + final PolicyRepository policyRepository, + final TPM2ProvisionerStateRepository tpm2ProvisionerStateRepository) { + this.supplyChainValidationService = supplyChainValidationService; + this.privateKey = privateKey; + this.acaCertificate = acaCertificate; + this.structConverter = structConverter; + this.componentResultRepository = componentResultRepository; + this.certificateRepository = certificateRepository; + this.issuedCertificateRepository = issuedCertificateRepository; + this.referenceManifestRepository = referenceManifestRepository; + this.validDays = validDays; + this.deviceRepository = deviceRepository; + this.referenceDigestValueRepository = referenceDigestValueRepository; + this.policyRepository = policyRepository; + this.tpm2ProvisionerStateRepository = tpm2ProvisionerStateRepository; + + this.certificateRequestHandler = new CertificateRequestHandler(supplyChainValidationService, + certificateRepository, deviceRepository, + privateKey, acaCertificate, validDays, tpm2ProvisionerStateRepository); + this.identityClaimHandler = new IdentityClaimHandler(supplyChainValidationService, + certificateRepository, referenceManifestRepository, + referenceDigestValueRepository, + deviceRepository, tpm2ProvisionerStateRepository, policyRepository); + this.identityRequestHandler = new IdentityRequestHandler(structConverter, certificateRepository, + deviceRepository, supplyChainValidationService, privateKey, validDays, acaCertificate); + } + + byte[] processIdentityRequest(final byte[] identityRequest) { + return this.identityRequestHandler.processIdentityRequest(identityRequest); + } + + byte[] processIdentityClaimTpm2(final byte[] identityClaim) { + return this.identityClaimHandler.processIdentityClaimTpm2(identityClaim); + } + + byte[] processCertificateRequest(final byte[] certificateRequest) { + return this.certificateRequestHandler.processCertificateRequest(certificateRequest); + } + + public byte[] getPublicKey() { + return acaCertificate.getPublicKey().getEncoded(); + } +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/RestfulAttestationCertificateAuthority.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/RestfulAttestationCertificateAuthority.java new file mode 100644 index 00000000..2b011879 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/RestfulAttestationCertificateAuthority.java @@ -0,0 +1,146 @@ +package hirs.attestationca.persist; + +import hirs.attestationca.persist.entity.manager.CertificateRepository; +import hirs.attestationca.persist.entity.manager.ComponentResultRepository; +import hirs.attestationca.persist.entity.manager.DeviceRepository; +import hirs.attestationca.persist.entity.manager.IssuedCertificateRepository; +import hirs.attestationca.persist.entity.manager.PolicyRepository; +import hirs.attestationca.persist.entity.manager.ReferenceDigestValueRepository; +import hirs.attestationca.persist.entity.manager.ReferenceManifestRepository; +import hirs.attestationca.persist.entity.manager.TPM2ProvisionerStateRepository; +import hirs.attestationca.persist.entity.userdefined.certificate.IssuedAttestationCertificate; +import hirs.attestationca.persist.service.SupplyChainValidationService; +import hirs.structs.converters.StructConverter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.PropertySource; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +import java.security.PrivateKey; +import java.security.cert.X509Certificate; + +/** + * Restful implementation of the {@link AttestationCertificateAuthority}. + * Exposes the ACA methods as REST endpoints. + */ +@PropertySource(value = "file:/etc/hirs/aca/application.properties", + ignoreResourceNotFound = true) +@RestController +@RequestMapping("/") +public class RestfulAttestationCertificateAuthority extends AttestationCertificateAuthority implements RestfulInterface { + + /** + * Constructor. + * + * @param supplyChainValidationService scp service + * @param privateKey the ACA private key + * @param acaCertificate the ACA certificate + * @param componentResultRepository the component result repository + * @param certificateRepository the certificate manager + * @param referenceManifestRepository the referenceManifestManager + * @param validDays the number of days issued certs are valid + * @param deviceRepository the device manager + * @param referenceDigestValueRepository the reference event repository + * @param policyRepository the provisioning policy entity + * @param tpm2ProvisionerStateRepository the provisioner state + */ + @SuppressWarnings({"checkstyle:parameternumber"}) + @Autowired + public RestfulAttestationCertificateAuthority( + final SupplyChainValidationService supplyChainValidationService, + final PrivateKey privateKey, final X509Certificate acaCertificate, + final StructConverter structConverter, + final ComponentResultRepository componentResultRepository, + final CertificateRepository certificateRepository, + final IssuedCertificateRepository issuedCertificateRepository, + final ReferenceManifestRepository referenceManifestRepository, + final DeviceRepository deviceRepository, + final ReferenceDigestValueRepository referenceDigestValueRepository, + @Value("${aca.certificates.validity}") final int validDays, + final PolicyRepository policyRepository, + final TPM2ProvisionerStateRepository tpm2ProvisionerStateRepository) { + super(supplyChainValidationService, privateKey, acaCertificate, structConverter, + componentResultRepository, certificateRepository, issuedCertificateRepository, + referenceManifestRepository, + validDays, deviceRepository, + referenceDigestValueRepository, policyRepository, tpm2ProvisionerStateRepository); + } + + /** + * Processes a given IdentityRequestEnvelope and + * generates a IdentityResponseEnvelope. In most cases, + * a client will generate the request using the TPM "Collate Identity" process. + * + * Wrap the {@link AttestationCertificateAuthority#processIdentityRequest(byte[])} + * with a Spring {@link org.springframework.web.bind.annotation.RequestMapping}. Effectively, this method then will allow spring to + * serialize and deserialize the request and responses on method invocation and + * return, respectively. + * + * @param identityRequest generated during the collate identity process with a Tpm + * @return response for the request + */ + @Override + @ResponseBody + @RequestMapping(value = "/identity-request/process", + method = RequestMethod.POST, + consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE) + public byte[] processIdentityRequest(@RequestBody final byte[] identityRequest) { + return super.processIdentityRequest(identityRequest); + } + + /** + * Listener for identity requests from TPM 2.0 provisioning. + * + * Processes a given IdentityClaim and generates a response + * containing an encrypted nonce to be returned by the client in + * a future handshake request. + * + * @param identityClaim The request object from the provisioner. + * @return The response to the provisioner. + */ + @Override + @ResponseBody + @RequestMapping(value = "/identity-claim-tpm2/process", + method = RequestMethod.POST, + consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE) + public byte[] processIdentityClaimTpm2(@RequestBody final byte[] identityClaim) { + return super.processIdentityClaimTpm2(identityClaim); + } + + /** + * Processes a given CertificateRequest + * and generates a response containing the signed, public certificate for + * the client's desired attestation key, if the correct nonce is supplied. + * + * @param certificateRequest request containing nonce from earlier identity + * * claim handshake + * @return The response to the client provisioner. + */ + @Override + @ResponseBody + @RequestMapping(value = "/request-certificate-tpm2", + method = RequestMethod.POST, + consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE) + public byte[] processCertificateRequest(@RequestBody final byte[] certificateRequest) { + return super.processCertificateRequest(certificateRequest); + } + + /** + * (non-javadoc) + *

+ * Wrap the {@link AttestationCertificateAuthority#getPublicKey()} with a Spring + * {@link org.springframework.web.bind.annotation.RequestMapping} such that Spring can serialize the certificate to be returned to an + * HTTP Request. + */ + @Override + @ResponseBody + @RequestMapping(value = "/public-key", method = RequestMethod.GET) + public byte[] getPublicKey() { + return super.getPublicKey(); + } +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/RestfulInterface.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/RestfulInterface.java new file mode 100644 index 00000000..10e63b9f --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/RestfulInterface.java @@ -0,0 +1,14 @@ +package hirs.attestationca.persist; + +/** + * Defines the responsibilities of the Attestation Certificate Authority. + */ +public interface RestfulInterface { + + byte[] processIdentityRequest(byte[] identityRequest); + + byte[] processIdentityClaimTpm2(byte[] identityClaim); + + byte[] processCertificateRequest(byte[] certificateRequest); + +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/StorageProperties.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/StorageProperties.java deleted file mode 100644 index 6dad847c..00000000 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/StorageProperties.java +++ /dev/null @@ -1,21 +0,0 @@ -package hirs.attestationca.persist; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties("storage") -public class StorageProperties { - - /** - * Folder location for storing files - */ - private String location = "/tmp/hirs"; - - public String getLocation() { - return location; - } - - public void setLocation(String location) { - this.location = location; - } - -} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/manager/CACredentialRepository.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/manager/CACredentialRepository.java index ab697571..cd30f254 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/manager/CACredentialRepository.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/manager/CACredentialRepository.java @@ -14,4 +14,7 @@ public interface CACredentialRepository extends JpaRepository findAll(); + List findBySubject(String subject); + List findBySubjectSorted(String subject); + CertificateAuthorityCredential findBySubjectKeyIdentifier(byte[] subjectKeyIdentifier); } diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/manager/CertificateRepository.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/manager/CertificateRepository.java index d4301660..5a97022d 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/manager/CertificateRepository.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/manager/CertificateRepository.java @@ -1,6 +1,8 @@ package hirs.attestationca.persist.entity.manager; import hirs.attestationca.persist.entity.userdefined.Certificate; +import hirs.attestationca.persist.entity.userdefined.certificate.EndorsementCredential; +import hirs.attestationca.persist.entity.userdefined.certificate.IssuedAttestationCertificate; import hirs.attestationca.persist.entity.userdefined.certificate.PlatformCredential; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -27,8 +29,11 @@ public interface CertificateRepository extends JpaReposit List byBoardSerialNumber(String boardSerialNumber); @Query(value = "SELECT * FROM Certificate where holderSerialNumber = ?1 AND DTYPE = 'PlatformCredential'", nativeQuery = true) PlatformCredential getPcByHolderSerialNumber(BigInteger holderSerialNumber); + @Query(value = "SELECT * FROM Certificate where holderSerialNumber = ?1 AND DTYPE = 'PlatformCredential'", nativeQuery = true) + List getByHolderSerialNumber(BigInteger holderSerialNumber); @Query(value = "SELECT * FROM Certificate where certificateHash = ?1 AND DTYPE = ?2", nativeQuery = true) T findByCertificateHash(int certificateHash, String dType); - @Query(value = "SELECT * FROM Certificate where subjectKeyIdentifier = ?1", nativeQuery = true) - Certificate findBySubjectKeyIdentifier(byte[] skiCA); + EndorsementCredential findByPublicKeyModulusHexValue(String publicKeyModulusHexValue); + IssuedAttestationCertificate findByDeviceId(UUID deviceId); + Certificate findByCertificateHash(int certificateHash); } diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/manager/ReferenceDigestValueRepository.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/manager/ReferenceDigestValueRepository.java index d1fae962..24be3dd5 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/manager/ReferenceDigestValueRepository.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/manager/ReferenceDigestValueRepository.java @@ -19,4 +19,5 @@ public interface ReferenceDigestValueRepository extends JpaRepository getValuesByRimId(UUID associatedRimId); List findBySupportRimId(UUID supportRimId); List findBySupportRimHash(String supportRimHash); + List findByManufacturerAndModel(String manufacturer, String model); } diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/manager/ReferenceManifestRepository.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/manager/ReferenceManifestRepository.java index 80782394..b74516bf 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/manager/ReferenceManifestRepository.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/manager/ReferenceManifestRepository.java @@ -15,11 +15,12 @@ import java.util.UUID; public interface ReferenceManifestRepository extends JpaRepository { ReferenceManifest findByHexDecHash(String hexDecHash); + ReferenceManifest findByBase64Hash(String base64Hash); ReferenceManifest findByHexDecHashAndRimType(String hexDecHash, String rimType); @Query(value = "SELECT * FROM ReferenceManifest WHERE platformManufacturer = ?1 AND platformModel = ?2 AND rimType = 'Base'", nativeQuery = true) - List getBaseByManufacturerModel(String manufacturer, String model); + BaseReferenceManifest getBaseByManufacturerModel(String manufacturer, String model); @Query(value = "SELECT * FROM ReferenceManifest WHERE platformManufacturer = ?1 AND DTYPE = ?2", nativeQuery = true) - ReferenceManifest getByManufacturer(String manufacturer, String dType); + List getByManufacturer(String manufacturer, String dType); @Query(value = "SELECT * FROM ReferenceManifest WHERE platformModel = ?1 AND DTYPE = ?2", nativeQuery = true) ReferenceManifest getByModel(String model, String dType); @Query(value = "SELECT * FROM ReferenceManifest WHERE DTYPE = 'BaseReferenceManifest'", nativeQuery = true) @@ -34,4 +35,8 @@ public interface ReferenceManifestRepository extends JpaRepository byDeviceName(String deviceName); + @Query(value = "SELECT * FROM ReferenceManifest WHERE deviceName = ?1 AND DTYPE = 'EventLogMeasurements'", nativeQuery = true) + EventLogMeasurements byMeasurementDeviceName(String deviceName); + @Query(value = "SELECT * FROM ReferenceManifest WHERE platformManufacturer = ?1 AND platformModel = ?2 AND rimType = 'Support'", nativeQuery = true) + List getSupportByManufacturerModel(String manufacturer, String model); } diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/manager/TPM2ProvisionerStateRepository.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/manager/TPM2ProvisionerStateRepository.java new file mode 100644 index 00000000..3e17881d --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/manager/TPM2ProvisionerStateRepository.java @@ -0,0 +1,11 @@ +package hirs.attestationca.persist.entity.manager; + +import hirs.attestationca.persist.entity.tpm.TPM2ProvisionerState; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface TPM2ProvisionerStateRepository extends JpaRepository { + + TPM2ProvisionerState findByFirstPartOfNonce(Long findByFirstPartOfNonce); +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/tpm/TPM2ProvisionerState.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/tpm/TPM2ProvisionerState.java new file mode 100644 index 00000000..18b50f08 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/tpm/TPM2ProvisionerState.java @@ -0,0 +1,112 @@ +package hirs.attestationca.persist.entity.tpm; + +import hirs.attestationca.persist.entity.manager.TPM2ProvisionerStateRepository; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Lob; +import lombok.NoArgsConstructor; +import org.bouncycastle.util.Arrays; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.util.Date; + +/** + * This class is for saving the Identity Claim and the Nonce between the two passes of the + * TPM 2.0 Provisioner. + */ +@NoArgsConstructor +@Entity +public class TPM2ProvisionerState { + private static final int MAX_BLOB_SIZE = 65535; + + @Id + private Long firstPartOfNonce; + + @Column(nullable = false) + private byte[] nonce; + + @Lob + @Column(nullable = false, length = MAX_BLOB_SIZE) + private byte[] identityClaim; + + @Column(nullable = false) + private Date timestamp = new Date(); + + /** + * Constructor. + * + * @param nonce the nonce + * @param identityClaim the identity claim + */ + public TPM2ProvisionerState(final byte[] nonce, final byte[] identityClaim) { + if (nonce == null) { + throw new IllegalArgumentException("Nonce should not be null"); + } + + if (identityClaim == null) { + throw new IllegalArgumentException("Identity Claim should not be null"); + } + + if (nonce.length < Long.BYTES) { + throw new IllegalArgumentException( + String.format("Nonce must be larger than 8 bytes. (Received %d.)", + nonce.length)); + } + + this.nonce = Arrays.clone(nonce); + this.identityClaim = Arrays.clone(identityClaim); + + try (DataInputStream dis = new DataInputStream(new ByteArrayInputStream(nonce))) { + firstPartOfNonce = dis.readLong(); + } catch (IOException e) { + // This would only happen if there were not enough bytes; that is handled above. + throw new RuntimeException(e); + } + } + + /** + * Get the nonce. + * + * @return the nonce + */ + public byte[] getNonce() { + return Arrays.clone(nonce); + } + + /** + * Get the identity claim. + * + * @return the identity claim + */ + public byte[] getIdentityClaim() { + return Arrays.clone(identityClaim); + } + + /** + * Convenience method for finding the {@link TPM2ProvisionerState} associated with the nonce. + * + * @param TPM2ProvisionerStateRepository the {@link TPM2ProvisionerStateRepository} to use when looking for the + * {@link TPM2ProvisionerState} + * @param nonce the nonce to use as the key for the {@link TPM2ProvisionerState} + * @return the {@link TPM2ProvisionerState} associated with the nonce; + * null if a match is not found + */ + public static TPM2ProvisionerState getTPM2ProvisionerState( + final TPM2ProvisionerStateRepository tpm2ProvisionerStateRepository, + final byte[] nonce) { + try (DataInputStream dis + = new DataInputStream(new ByteArrayInputStream(nonce))) { + long firstPartOfNonce = dis.readLong(); + TPM2ProvisionerState stateFound = tpm2ProvisionerStateRepository.findByFirstPartOfNonce(firstPartOfNonce); + if (Arrays.areEqual(stateFound.getNonce(), nonce)) { + return stateFound; + } + } catch (IOException | NullPointerException e) { + return null; + } + return null; + } +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/ExaminableRecord.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/ExaminableRecord.java new file mode 100644 index 00000000..df4dfb48 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/ExaminableRecord.java @@ -0,0 +1,67 @@ +package hirs.attestationca.persist.entity.userdefined; + +import jakarta.persistence.Access; +import jakarta.persistence.AccessType; +import jakarta.persistence.Column; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.MappedSuperclass; +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.extern.log4j.Log4j2; + +/** + * Specifies properties for an object that can be examined. + */ +@Log4j2 +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@MappedSuperclass +@Access(AccessType.FIELD) +@XmlAccessorType(XmlAccessType.FIELD) +public abstract class ExaminableRecord { + + /** + * State capturing if a record was examined during appraisal or not. + */ + public enum ExamineState { + /** + * If the record was never examined. + */ + UNEXAMINED, + + /** + * If the record was compared against a baseline during the appraisal process. + */ + EXAMINED, + + /** + * If a record was visited but ignored. + */ + IGNORED + } + + @Getter + @Column(nullable = false) + // Decided on ORDINAL instead of STRING due to concerns surrounding overall size and retrieval + // time of field from database. Consistent with other implementations of ExaminableRecord. + @Enumerated(EnumType.ORDINAL) + private ExamineState examineState = ExamineState.UNEXAMINED; + + /** + * Sets the examine state for this record. + * @param examineState the examine state + */ + public void setExamineState(final ExamineState examineState) { + if (examineState == ExamineState.UNEXAMINED) { + log.error("Can't set ExamineState on ExaminableRecord to Unexamined"); + throw new IllegalArgumentException( + "Can't set ExamineState on ExaminableRecord to Unexamined" + ); + } + + this.examineState = examineState; + } +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/CertificateAuthorityCredential.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/CertificateAuthorityCredential.java index a0fbe115..72047d42 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/CertificateAuthorityCredential.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/CertificateAuthorityCredential.java @@ -1,8 +1,6 @@ package hirs.attestationca.persist.entity.userdefined.certificate; import hirs.attestationca.persist.entity.userdefined.Certificate; -import hirs.attestationca.persist.service.CertificateServiceImpl; -import hirs.attestationca.persist.service.selector.CertificateSelector; import jakarta.persistence.Column; import jakarta.persistence.Entity; import lombok.Getter; @@ -46,43 +44,6 @@ public class CertificateAuthorityCredential extends Certificate { @Column private final String credentialType = "TCPA Trusted Platform Module Endorsement"; - /** - * This class enables the retrieval of CertificateAuthorityCredentials by their attributes. - */ - public static class Selector extends CertificateSelector { - /** - * Construct a new CertificateSelector that will use the given {@link CertificateServiceImpl} to - * retrieve one or many CertificateAuthorityCredentials. - * - * @param certificateService the certificate manager to be used to retrieve certificates - */ - public Selector(final CertificateServiceImpl certificateService) { - super(certificateService, CertificateAuthorityCredential.class); - } - - /** - * Specify a subject key identifier that certificates must have to be considered - * as matching. - * - * @param subjectKeyIdentifier a subject key identifier buffer to query, not empty or null - * @return this instance (for chaining further calls) - */ - public Selector bySubjectKeyIdentifier(final byte[] subjectKeyIdentifier) { - setFieldValue(SUBJECT_KEY_IDENTIFIER_FIELD, subjectKeyIdentifier); - return this; - } - } - - /** - * Get a Selector for use in retrieving CertificateAuthorityCredentials. - * - * @param certMan the CertificateService to be used to retrieve persisted certificates - * @return a CertificateAuthorityCredential.Selector instance to use for retrieving certificates - */ - public static Selector select(final CertificateServiceImpl certMan) { - return new Selector(certMan); - } - /** * Construct a new CertificateAuthorityCredential given its binary contents. The given * certificate should represent either an X509 certificate or X509 attribute certificate. diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/IssuedAttestationCertificate.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/IssuedAttestationCertificate.java index d07bbf07..24d1bb78 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/IssuedAttestationCertificate.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/IssuedAttestationCertificate.java @@ -11,7 +11,7 @@ import lombok.NoArgsConstructor; import java.io.IOException; import java.nio.file.Path; -import java.util.Set; +import java.util.List; /** * Represents an issued attestation certificate to a HIRS Client. @@ -32,45 +32,7 @@ public class IssuedAttestationCertificate extends DeviceAssociatedCertificate { @ManyToMany(fetch = FetchType.EAGER) @JoinColumn(name = "pc_id") - private Set platformCredentials; - - /** - * This class enables the retrieval of IssuedAttestationCertificate by their attributes. - */ -// public static class Selector extends CertificateSelector { -// /** -// * Construct a new CertificateSelector that will use the given {@link CertificateManager} to -// * retrieve one or many IssuedAttestationCertificate. -// * -// * @param certificateManager the certificate manager to be used to retrieve certificates -// */ -// public Selector(final CertificateManager certificateManager) { -// super(certificateManager, IssuedAttestationCertificate.class); -// } -// -// /** -// * Specify a device id that certificates must have to be considered -// * as matching. -// * -// * @param device the device id to query -// * @return this instance (for chaining further calls) -// */ -// public Selector byDeviceId(final UUID device) { -// setFieldValue(DEVICE_ID_FIELD, device); -// return this; -// } -// } -// -// /** -// * Get a Selector for use in retrieving IssuedAttestationCertificate. -// * -// * @param certMan the CertificateManager to be used to retrieve persisted certificates -// * @return a IssuedAttestationCertificate.Selector instance to use for retrieving certificates -// */ -// public static IssuedAttestationCertificate.Selector select(final CertificateManager certMan) { -// return new IssuedAttestationCertificate.Selector(certMan); -// } - + private List platformCredentials; /** * Constructor. @@ -81,7 +43,7 @@ public class IssuedAttestationCertificate extends DeviceAssociatedCertificate { */ public IssuedAttestationCertificate(final byte[] certificateBytes, final EndorsementCredential endorsementCredential, - final Set platformCredentials) + final List platformCredentials) throws IOException { super(certificateBytes); this.endorsementCredential = endorsementCredential; @@ -97,7 +59,7 @@ public class IssuedAttestationCertificate extends DeviceAssociatedCertificate { */ public IssuedAttestationCertificate(final Path certificatePath, final EndorsementCredential endorsementCredential, - final Set platformCredentials) + final List platformCredentials) throws IOException { this(readBytes(certificatePath), endorsementCredential, platformCredentials); } diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/PlatformCredential.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/PlatformCredential.java index 3cc3edbc..52b6769e 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/PlatformCredential.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/PlatformCredential.java @@ -7,7 +7,6 @@ import hirs.attestationca.persist.entity.userdefined.certificate.attributes.Plat import hirs.attestationca.persist.entity.userdefined.certificate.attributes.TBBSecurityAssertion; import hirs.attestationca.persist.entity.userdefined.certificate.attributes.URIReference; import hirs.attestationca.persist.entity.userdefined.certificate.attributes.V2.PlatformConfigurationV2; -import hirs.attestationca.persist.service.CertificateServiceImpl; import hirs.attestationca.persist.service.selector.CertificateSelector; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -128,93 +127,6 @@ public class PlatformCredential extends DeviceAssociatedCertificate { */ public static final String CERTIFICATE_TYPE_2_0 = "TCG Trusted Platform Endorsement"; - /** - * This class enables the retrieval of PlatformCredentials by their attributes. - */ - public static class Selector extends CertificateSelector { - /** - * Construct a new CertificateSelector that will use the given {@link CertificateServiceImpl} to - * retrieve one or many PlatformCredentials. - * - * @param certificateService the certificate manager to be used to retrieve certificates - */ - public Selector(final CertificateServiceImpl certificateService) { - super(certificateService, PlatformCredential.class); - } - - /** - * Specify a manufacturer that certificates must have to be considered as matching. - * @param manufacturer the manufacturer to query, not empty or null - * @return this instance (for chaining further calls) - */ - public Selector byManufacturer(final String manufacturer) { - setFieldValue(MANUFACTURER_FIELD, manufacturer); - return this; - } - - /** - * Specify a model that certificates must have to be considered as matching. - * @param model the model to query, not empty or null - * @return this instance (for chaining further calls) - */ - public Selector byModel(final String model) { - setFieldValue(MODEL_FIELD, model); - return this; - } - - /** - * Specify a version that certificates must have to be considered as matching. - * @param version the version to query, not empty or null - * @return this instance (for chaining further calls) - */ - public Selector byVersion(final String version) { - setFieldValue(VERSION_FIELD, version); - return this; - } - - /** - * Specify a serial number that certificates must have to be considered as matching. - * @param serialNumber the serial number to query, not empty or null - * @return this instance (for chaining further calls) - */ - public Selector bySerialNumber(final String serialNumber) { - setFieldValue(SERIAL_NUMBER_FIELD, serialNumber); - return this; - } - - /** - * Specify a board serial number that certificates must have to be considered as matching. - * @param boardSerialNumber the board serial number to query, not empty or null - * @return this instance (for chaining further calls) - */ - public Selector byBoardSerialNumber(final String boardSerialNumber) { - setFieldValue(PLATFORM_SERIAL_FIELD, boardSerialNumber); - return this; - } - - /** - * Specify a chassis serial number that certificates must have to be considered as matching. - * @param chassisSerialNumber the board serial number to query, not empty or null - * @return this instance (for chaining further calls) - */ - public Selector byChassisSerialNumber(final String chassisSerialNumber) { - setFieldValue(CHASSIS_SERIAL_NUMBER_FIELD, chassisSerialNumber); - return this; - } - - /** - * Specify a device id that certificates must have to be considered - * as matching. - * - * @param device the device id to query - * @return this instance (for chaining further calls) - */ - public Selector byDeviceId(final UUID device) { - setFieldValue(DEVICE_ID_FIELD, device); - return this; - } - } - @Column private String credentialType = null; @@ -271,17 +183,6 @@ public class PlatformCredential extends DeviceAssociatedCertificate { private String platformChainType = Strings.EMPTY; private boolean isDeltaChain = false; - - /** - * Get a Selector for use in retrieving PlatformCredentials. - * - * @param certificateService the CertificateManager to be used to retrieve persisted certificates - * @return a PlatformCredential.Selector instance to use for retrieving certificates - */ - public static Selector select(final CertificateServiceImpl certificateService) { - return new Selector(certificateService); - } - /** * Construct a new PlatformCredential given its binary contents. ParseFields is * optionally run. The given certificate should represent either an X509 certificate diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/record/TPMMeasurementRecord.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/record/TPMMeasurementRecord.java new file mode 100644 index 00000000..7507b8c6 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/record/TPMMeasurementRecord.java @@ -0,0 +1,130 @@ +package hirs.attestationca.persist.entity.userdefined.record; + +import hirs.attestationca.persist.entity.userdefined.ExaminableRecord; +import hirs.utils.digest.Digest; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlAttribute; +import jakarta.xml.bind.annotation.XmlElement; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; +import lombok.extern.log4j.Log4j2; +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Hex; + +/** + * Class represents a Trusted Platform Module (TPM) Platform Configuration + * Register (PCR). Each PCRMeasurementRecord contains a pcrID which is a direct + * reference to the TPM PCR location. For example, a pcrID of 0 is a reference + * to TPM PCR 0. Each PCRMeasurementRecord also contains a cryptographic hash + * which represents the value stored at the associated TPM PCR location. + */ +@Log4j2 +@Getter +@ToString +@EqualsAndHashCode +@Embeddable +@XmlAccessorType(XmlAccessType.FIELD) +public final class TPMMeasurementRecord extends ExaminableRecord { + + /** + * Minimum possible value for a PCR ID. This is 0. + */ + public static final int MIN_PCR_ID = 0; + + /** + * Maximum possible value for a PCR ID. This is 23. + */ + public static final int MAX_PCR_ID = 23; + + /** + * String length of a SHA 1 PCR value. + */ + public static final int SHA_BYTE_LENGTH = 40; + + /** + * String length of a 256 SHA PCR value. + */ + public static final int SHA_256_BYTE_LENGTH = 64; + + + @Column(name = "pcr", nullable = false) + @XmlAttribute(name = "PcrNumber", required = true) + private final int pcrId; + @Embedded + @XmlElement + private final Digest hash; + + /** + * Constructor initializes values associated with TPMMeasurementRecord. + * + * @param pcrId is the TPM PCR index. pcrId must be between 0 and 23. + * @param hash + * represents the measurement digest found at the particular PCR + * index. + * @throws IllegalArgumentException if pcrId is not valid + */ + public TPMMeasurementRecord(final int pcrId, final Digest hash) + throws IllegalArgumentException { + super(); + checkForValidPcrId(pcrId); + if (hash == null) { + log.error("null hash value"); + throw new NullPointerException("hash"); + } + + this.pcrId = pcrId; + this.hash = hash; + } + + /** + * Constructor initializes values associated with TPMMeasurementRecord. + * + * @param pcrId is the TPM PCR index. pcrId must be between 0 and 23. + * @param hash represents the measurement digest found at the particular PCR + * index. + * @throws DecoderException if there is a decode issue with string hex. + */ + public TPMMeasurementRecord(final int pcrId, final String hash) + throws DecoderException { + this(pcrId, new Digest(Hex.decodeHex(hash.toCharArray()))); + } + + /** + * Constructor initializes values associated with TPMMeasurementRecord. + * + * @param pcrId is the TPM PCR index. pcrId must be between 0 and 23. + * @param hash represents the measurement digest found at the particular PCR + * index. + */ + public TPMMeasurementRecord(final int pcrId, final byte[] hash) { + this(pcrId, new Digest(hash)); + } + + /** + * Helper method to determine if a PCR ID number is valid. + * + * @param pcrId + * int to check + */ + public static void checkForValidPcrId(final int pcrId) { + if (pcrId < MIN_PCR_ID || pcrId > MAX_PCR_ID) { + final String msg = String.format("invalid PCR ID: %d", pcrId); + log.error(msg); + throw new IllegalArgumentException(msg); + } + } + + /** + * Default constructor necessary for Hibernate. + */ + protected TPMMeasurementRecord() { + super(); + this.pcrId = -1; + this.hash = null; + } +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/report/DeviceInfoReport.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/report/DeviceInfoReport.java index 39282275..1d4c4a1f 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/report/DeviceInfoReport.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/report/DeviceInfoReport.java @@ -15,6 +15,7 @@ import jakarta.persistence.Transient; import jakarta.xml.bind.annotation.XmlElement; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; import lombok.extern.log4j.Log4j2; import java.io.Serializable; @@ -54,6 +55,7 @@ public class DeviceInfoReport extends AbstractEntity implements Serializable { @Column(nullable = false) private String clientApplicationVersion; + @Setter @XmlElement @Transient private String paccorOutputString; diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/rim/SupportReferenceManifest.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/rim/SupportReferenceManifest.java index 07204929..98769b57 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/rim/SupportReferenceManifest.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/rim/SupportReferenceManifest.java @@ -2,8 +2,6 @@ package hirs.attestationca.persist.entity.userdefined.rim; import com.fasterxml.jackson.annotation.JsonIgnore; import hirs.attestationca.persist.entity.userdefined.ReferenceManifest; -import hirs.attestationca.persist.service.ReferenceManifestServiceImpl; -import hirs.attestationca.persist.service.selector.ReferenceManifestSelector; import hirs.utils.tpm.eventlog.TCGEventLog; import hirs.utils.tpm.eventlog.TpmPcrEvent; import jakarta.persistence.Column; @@ -36,78 +34,6 @@ public class SupportReferenceManifest extends ReferenceManifest { @Column private boolean processed = false; - /** - * This class enables the retrieval of SupportReferenceManifest by their attributes. - */ - public static class Selector extends ReferenceManifestSelector { - /** - * Construct a new ReferenceManifestSelector that will - * use the given (@link ReferenceManifestService} - * to retrieve one or may SupportReferenceManifest. - * - * @param referenceManifestManager the reference manifest manager to be used to retrieve - * reference manifests. - */ - public Selector(final ReferenceManifestServiceImpl referenceManifestManager) { - super(referenceManifestManager, SupportReferenceManifest.class); - } - - /** - * Specify the platform manufacturer that rims must have to be considered - * as matching. - * @param manufacturer string for the manufacturer - * @return this instance - */ - public Selector byManufacturer(final String manufacturer) { - setFieldValue(PLATFORM_MANUFACTURER, manufacturer); - return this; - } - - /** - * Specify the platform model that rims must have to be considered - * as matching. - * @param manufacturer string for the manufacturer - * @param model string for the model - * @return this instance - */ - public Selector byManufacturerModel(final String manufacturer, final String model) { - setFieldValue(PLATFORM_MANUFACTURER, manufacturer); - setFieldValue(PLATFORM_MODEL, model); - return this; - } - - /** - * Specify the device name that rims must have to be considered - * as matching. - * @param deviceName string for the deviceName - * @return this instance - */ - public Selector byDeviceName(final String deviceName) { - setFieldValue("deviceName", deviceName); - return this; - } - - /** - * Specify the file name that rims should have. - * @param fileName the name of the file associated with the rim - * @return this instance - */ - public Selector byFileName(final String fileName) { - setFieldValue(RIM_FILENAME_FIELD, fileName); - return this; - } - - /** - * Specify the RIM hash associated with the support RIM. - * @param hexDecHash the hash of the file associated with the rim - * @return this instance - */ - public Selector byHexDecHash(final String hexDecHash) { - setFieldValue(HEX_DEC_HASH_FIELD, hexDecHash); - return this; - } - } - /** * Main constructor for the RIM object. This takes in a byte array of a * valid swidtag file and parses the information. @@ -143,17 +69,6 @@ public class SupportReferenceManifest extends ReferenceManifest { this.pcrHash = 0; } - /** - * Get a Selector for use in retrieving ReferenceManifest. - * - * @param rimMan the ReferenceManifestService to be used to retrieve - * persisted RIMs - * @return a Selector instance to use for retrieving RIMs - */ - public static Selector select(final ReferenceManifestServiceImpl rimMan) { - return new Selector(rimMan); - } - /** * Getter method for the expected PCR values contained within the support * RIM. diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/exceptions/CertificateProcessingException.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/exceptions/CertificateProcessingException.java new file mode 100644 index 00000000..cab0c091 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/exceptions/CertificateProcessingException.java @@ -0,0 +1,27 @@ +package hirs.attestationca.persist.exceptions; + +/** + * Generic exception thrown while a {@link hirs.attestationca.persist.AttestationCertificateAuthority} + * is processing a newly created Attestation Certificate for a validated identity. + */ +public class CertificateProcessingException extends RuntimeException { + /** + * Constructs a generic instance of this exception using the specified reason. + * + * @param reason for the exception + */ + public CertificateProcessingException(final String reason) { + super(reason); + } + + /** + * Constructs a instance of this exception with the specified reason and backing root + * exception. + * + * @param reason for this exception + * @param rootException causing this exception + */ + public CertificateProcessingException(final String reason, final Throwable rootException) { + super(reason, rootException); + } +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/exceptions/IdentityProcessingException.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/exceptions/IdentityProcessingException.java new file mode 100644 index 00000000..16578473 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/exceptions/IdentityProcessingException.java @@ -0,0 +1,27 @@ +package hirs.attestationca.persist.exceptions; + +/** + * Generic exception thrown while a {@link hirs.attestationca.persist.AttestationCertificateAuthority} + * is processing a newly submitted Identity. + */ +public class IdentityProcessingException extends RuntimeException { + /** + * Constructs a generic instance of this exception using the specified reason. + * + * @param reason for the exception + */ + public IdentityProcessingException(final String reason) { + super(reason); + } + + /** + * Constructs a instance of this exception with the specified reason and backing root + * exception. + * + * @param reason for this exception + * @param rootException causing this exception + */ + public IdentityProcessingException(final String reason, final Throwable rootException) { + super(reason, rootException); + } +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/exceptions/UnexpectedServerException.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/exceptions/UnexpectedServerException.java new file mode 100644 index 00000000..ed3da171 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/exceptions/UnexpectedServerException.java @@ -0,0 +1,27 @@ +package hirs.attestationca.persist.exceptions; + +/** + * Generic exception thrown when a {@link hirs.attestationca.persist.AttestationCertificateAuthority} + * encounters an unexpected condition that can't be handled. + */ +public class UnexpectedServerException extends RuntimeException { + /** + * Constructs a generic instance of this exception using the specified reason. + * + * @param reason for the exception + */ + public UnexpectedServerException(final String reason) { + super(reason); + } + + /** + * Constructs a instance of this exception with the specified reason and backing root + * exception. + * + * @param reason for this exception + * @param rootException causing this exception + */ + public UnexpectedServerException(final String reason, final Throwable rootException) { + super(reason, rootException); + } +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/AbstractRequestHandler.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/AbstractRequestHandler.java new file mode 100644 index 00000000..aac5a61d --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/AbstractRequestHandler.java @@ -0,0 +1,485 @@ +package hirs.attestationca.persist.provision; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import hirs.attestationca.configuration.provisionerTpm2.ProvisionerTpm2; +import hirs.attestationca.persist.entity.manager.CertificateRepository; +import hirs.attestationca.persist.entity.manager.PolicyRepository; +import hirs.attestationca.persist.entity.userdefined.Certificate; +import hirs.attestationca.persist.entity.userdefined.Device; +import hirs.attestationca.persist.entity.userdefined.PolicySettings; +import hirs.attestationca.persist.entity.userdefined.certificate.EndorsementCredential; +import hirs.attestationca.persist.entity.userdefined.certificate.IssuedAttestationCertificate; +import hirs.attestationca.persist.entity.userdefined.certificate.PlatformCredential; +import hirs.attestationca.persist.entity.userdefined.info.TPMInfo; +import hirs.attestationca.persist.exceptions.CertificateProcessingException; +import hirs.attestationca.persist.exceptions.IdentityProcessingException; +import hirs.attestationca.persist.exceptions.UnexpectedServerException; +import hirs.attestationca.persist.provision.helper.CredentialManagementHelper; +import hirs.attestationca.persist.provision.helper.IssuedCertificateAttributeHelper; +import hirs.utils.HexUtils; +import hirs.utils.enums.DeviceInfoEnums; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.extern.log4j.Log4j2; +import org.apache.commons.codec.binary.Hex; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; + +import java.io.IOException; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.RSAPublicKeySpec; +import java.util.Calendar; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +@Log4j2 +@NoArgsConstructor +public class AbstractRequestHandler { + /** + * The default size for IV blocks. + */ + public final static int DEFAULT_IV_SIZE = 16; + /** + * Defines the well known exponent. + * https://en.wikipedia.org/wiki/65537_(number)#Applications + */ + private final static BigInteger EXPONENT = new BigInteger("010001", DEFAULT_IV_SIZE); + /** + * Number of bytes to include in the TPM2.0 nonce. + */ + public static final int NONCE_LENGTH = 20; + public static final int SEED_LENGTH = 32; + public static final int MAX_SECRET_LENGTH = 32; + public static final int RSA_MODULUS_LENGTH = 256; + public static final int AES_KEY_LENGTH_BYTES = 16; + public static final int HMAC_KEY_LENGTH_BYTES = 32; + public static final int HMAC_SIZE_LENGTH_BYTES = 2; + public static final int TPM2_CREDENTIAL_BLOB_SIZE = 392; + // Constants used to parse out the ak name from the ak public data. Used in generateAkName + public static final String AK_NAME_PREFIX = "000b"; + public static final String AK_NAME_HASH_PREFIX = + "0001000b00050072000000100014000b0800000000000100"; + public static final String TPM_SIGNATURE_ALG = "sha"; + + @Getter + private int validDays; + @Getter + private PrivateKey privateKey; + @Setter + @Getter + private String tpmQuoteHash = ""; + @Setter + @Getter + private String tpmQuoteSignature = ""; + @Setter + @Getter + private PolicyRepository policyRepository; + + public AbstractRequestHandler(final PrivateKey privateKey, + final int validDays) { + this.privateKey = privateKey; + this.validDays = validDays; + } + + /** + * Parse public key from public data segment generated by TPM 2.0. + * @param publicArea the public area segment to parse + * @return the RSA public key of the supplied public data + */ + RSAPublicKey parsePublicKey(final byte[] publicArea) { + int pubLen = publicArea.length; + if (pubLen < RSA_MODULUS_LENGTH) { + throw new IllegalArgumentException( + "EK or AK public data segment is not long enough"); + } + // public data ends with 256 byte modulus + byte[] modulus = HexUtils.subarray(publicArea, + pubLen - RSA_MODULUS_LENGTH, + pubLen - 1); + return (RSAPublicKey) assemblePublicKey(modulus); + } + + /** + * Constructs a public key where the modulus is in raw form. + * + * @param modulus + * in byte array form + * @return public key using specific modulus and the well known exponent + */ + PublicKey assemblePublicKey(final byte[] modulus) { + return assemblePublicKey(Hex.encodeHexString(modulus)); + } + + /** + * Constructs a public key where the modulus is Hex encoded. + * + * @param modulus + * hex encoded modulus + * @return public key using specific modulus and the well known exponent + */ + PublicKey assemblePublicKey(final String modulus) { + return assemblePublicKey(new BigInteger(modulus, DEFAULT_IV_SIZE)); + } + + /** + * Assembles a public key using a defined big int modulus and the well known exponent. + */ + private PublicKey assemblePublicKey(final BigInteger modulus) { + // generate a key spec using mod and exp + RSAPublicKeySpec keySpec = new RSAPublicKeySpec(modulus, EXPONENT); + + // create the public key + try { + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + return keyFactory.generatePublic(keySpec); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + throw new UnexpectedServerException( + "Encountered unexpected error creating public key: " + e.getMessage(), e); + } + } + + /** + * Helper method to parse a byte array into an {@link ProvisionerTpm2.IdentityClaim}. + * + * @param identityClaim byte array that should be converted to a Protobuf IdentityClaim + * object + * @throws {@link IdentityProcessingException} if byte array could not be parsed + * @return the Protobuf generated Identity Claim object + */ + public ProvisionerTpm2.IdentityClaim parseIdentityClaim(final byte[] identityClaim) { + try { + return ProvisionerTpm2.IdentityClaim.parseFrom(identityClaim); + } catch (InvalidProtocolBufferException ipbe) { + throw new IdentityProcessingException( + "Could not deserialize Protobuf Identity Claim object.", ipbe); + } + } + + /** + * Helper method to extract a DER encoded ASN.1 certificate from an X509 certificate. + * + * @param certificate the X509 certificate to be converted to DER encoding + * @throws {@link UnexpectedServerException} if error occurs during encoding retrieval + * @return the byte array representing the DER encoded certificate + */ + public byte[] getDerEncodedCertificate(final X509Certificate certificate) { + try { + return certificate.getEncoded(); + } catch (CertificateEncodingException ceEx) { + log.error("Error converting certificate to ASN.1 DER Encoding.", ceEx); + throw new UnexpectedServerException( + "Encountered error while converting X509 Certificate to ASN.1 DER Encoding: " + + ceEx.getMessage(), ceEx); + } + } + + /** + * Generates a array of random bytes. + * + * @param numberOfBytes + * to be generated + * @return byte array filled with the specified number of bytes. + */ + public byte[] generateRandomBytes(final int numberOfBytes) { + byte[] bytes = new byte[numberOfBytes]; + SecureRandom random = new SecureRandom(); + random.nextBytes(bytes); + return bytes; + } + + /** + * Generates a credential using the specified public key. + * + * @param publicKey cannot be null + * @param endorsementCredential the endorsement credential + * @param platformCredentials the set of platform credentials + * @param deviceName The host name used in the subject alternative name + * @return identity credential + */ + protected X509Certificate generateCredential(final PublicKey publicKey, + final EndorsementCredential endorsementCredential, + final List platformCredentials, + final String deviceName, final X509Certificate acaCertificate) { + try { + // have the certificate expire in the configured number of days + Calendar expiry = Calendar.getInstance(); + expiry.add(Calendar.DAY_OF_YEAR, getValidDays()); + + X500Name issuer = + new X500Name(acaCertificate.getSubjectX500Principal().getName()); + Date notBefore = new Date(); + Date notAfter = expiry.getTime(); + BigInteger serialNumber = BigInteger.valueOf(System.currentTimeMillis()); + + SubjectPublicKeyInfo subjectPublicKeyInfo = + SubjectPublicKeyInfo.getInstance(publicKey.getEncoded()); + + // The subject should be left blank, per spec + X509v3CertificateBuilder builder = + new X509v3CertificateBuilder(issuer, serialNumber, + notBefore, notAfter, null /* subjectName */, subjectPublicKeyInfo); + + Extension subjectAlternativeName = + IssuedCertificateAttributeHelper.buildSubjectAlternativeNameFromCerts( + endorsementCredential, platformCredentials, deviceName); + + Extension authKeyIdentifier = IssuedCertificateAttributeHelper + .buildAuthorityKeyIdentifier(endorsementCredential); + + builder.addExtension(subjectAlternativeName); + if (authKeyIdentifier != null) { + builder.addExtension(authKeyIdentifier); + } + // identify cert as an AIK with this extension + if (IssuedCertificateAttributeHelper.EXTENDED_KEY_USAGE_EXTENSION != null) { + builder.addExtension(IssuedCertificateAttributeHelper.EXTENDED_KEY_USAGE_EXTENSION); + } else { + log.warn("Failed to build extended key usage extension and add to AIK"); + throw new IllegalStateException("Extended Key Usage attribute unavailable. " + + "Unable to issue certificates"); + } + + ContentSigner signer = new JcaContentSignerBuilder("SHA1WithRSA") + .setProvider("BC").build(getPrivateKey()); + X509CertificateHolder holder = builder.build(signer); + return new JcaX509CertificateConverter() + .setProvider("BC").getCertificate(holder); + } catch (IOException | OperatorCreationException | CertificateException exception) { + throw new CertificateProcessingException("Encountered error while generating " + + "identity credential: " + exception.getMessage(), exception); + } + } + + /** + * Helper method to parse an Endorsement Credential from a Protobuf generated + * IdentityClaim. Will also check if the Endorsement Credential was already uploaded. + * Persists the Endorsement Credential if it does not already exist. + * + * @param identityClaim a Protobuf generated Identity Claim object + * @param ekPub the endorsement public key from the Identity Claim object + * @param certificateRepository db connector from certificates + * @return the Endorsement Credential, if one exists, null otherwise + */ + protected EndorsementCredential parseEcFromIdentityClaim( + final ProvisionerTpm2.IdentityClaim identityClaim, + final PublicKey ekPub, final CertificateRepository certificateRepository) { + EndorsementCredential endorsementCredential = null; + if (identityClaim.hasEndorsementCredential()) { + endorsementCredential = CredentialManagementHelper.storeEndorsementCredential( + certificateRepository, + identityClaim.getEndorsementCredential().toByteArray()); + } else if (ekPub != null) { + log.warn("Endorsement Cred was not in the identity claim from the client." + + " Checking for uploads."); + endorsementCredential = getEndorsementCredential(ekPub, certificateRepository); + } else { + log.warn("No endorsement credential was received in identity claim and no EK Public" + + " Key was provided to check for uploaded certificates."); + } + return endorsementCredential; + } + + /** + * Helper method to parse a set of Platform Credentials from a Protobuf generated + * IdentityClaim and Endorsement Credential. Persists the Platform Credentials if they + * do not already exist. + * + * @param identityClaim a Protobuf generated Identity Claim object + * @param endorsementCredential an endorsement credential to check if platform credentials + * exist + * @param certificateRepository db connector from certificates + * @return the Set of Platform Credentials, if they exist, an empty set otherwise + */ + protected List parsePcsFromIdentityClaim( + final ProvisionerTpm2.IdentityClaim identityClaim, + final EndorsementCredential endorsementCredential, + final CertificateRepository certificateRepository) { + List platformCredentials = new LinkedList<>(); + if (identityClaim.getPlatformCredentialCount() > 0) { + for (ByteString platformCredential : identityClaim.getPlatformCredentialList()) { + if (!platformCredential.isEmpty()) { + platformCredentials.add(CredentialManagementHelper.storePlatformCredential( + certificateRepository, platformCredential.toByteArray())); + } + } + } else if (endorsementCredential != null) { + // if none in the identity claim, look for uploaded platform credentials + log.warn("PC was not in the identity claim from the client. Checking for uploads."); + platformCredentials.addAll(getPlatformCredentials(certificateRepository, endorsementCredential)); + } else { + log.warn("No platform credential received in identity claim."); + } + return platformCredentials; + } + + /** + * Gets the Endorsement Credential from the DB given the EK public key. + * @param ekPublicKey the EK public key + * @return the Endorsement credential, if found, otherwise null + */ + private EndorsementCredential getEndorsementCredential(final PublicKey ekPublicKey, + final CertificateRepository certificateRepository) { + log.debug("Searching for endorsement credential based on public key: " + ekPublicKey); + + if (ekPublicKey == null) { + throw new IllegalArgumentException("Cannot look up an EC given a null public key"); + } + + EndorsementCredential credential = null; + + try { + credential = certificateRepository.findByPublicKeyModulusHexValue( + Certificate.getPublicKeyModulus(ekPublicKey) + .toString(Certificate.HEX_BASE)); + } catch (IOException e) { + log.error("Could not extract public key modulus", e); + } + + if (credential == null) { + log.warn("Unable to find endorsement credential for public key."); + } else { + log.debug("Endorsement credential found."); + } + + return credential; + } + + /** + * This method takes the provided TPM Quote and splits it between the PCR + * quote and the signature hash. + * @param tpmQuote contains hash values for the quote and the signature + */ + public boolean parseTPMQuote(final String tpmQuote) { + boolean success = false; + if (tpmQuote != null) { + String[] lines = tpmQuote.split(":"); + if (lines[1].contains("signature")) { + this.tpmQuoteHash = lines[1].replace("signature", "").trim(); + } else { + this.tpmQuoteHash = lines[1].trim(); + } + this.tpmQuoteSignature = lines[2].trim(); + success = true; + } + + return success; + } + + /** + * Helper method to create the TPMInfo used for device. + * @return a new TPMInfo Instance + */ + public TPMInfo createTpmInfo(final String pcrValues) { + return new TPMInfo(DeviceInfoEnums.NOT_SPECIFIED, + (short) 0, + (short) 0, + (short) 0, + (short) 0, + pcrValues.getBytes(StandardCharsets.UTF_8), + this.getTpmQuoteHash().getBytes(StandardCharsets.UTF_8), + this.getTpmQuoteSignature().getBytes(StandardCharsets.UTF_8)); + } + + /** + * Helper method to create an {@link IssuedAttestationCertificate} object, set its + * corresponding device and persist it. + * + * @param derEncodedAttestationCertificate the byte array representing the Attestation + * certificate + * @param endorsementCredential the endorsement credential used to generate the AC + * @param platformCredentials the platform credentials used to generate the AC + * @param device the device to which the attestation certificate is tied + * @throws {@link CertificateProcessingException} if error occurs in persisting the Attestation + * Certificate + */ + public void saveAttestationCertificate(final CertificateRepository certificateRepository, + final byte[] derEncodedAttestationCertificate, + final EndorsementCredential endorsementCredential, + final List platformCredentials, + final Device device) { + IssuedAttestationCertificate issuedAc; + boolean generateCertificate = true; + PolicyRepository scp = this.getPolicyRepository(); + PolicySettings policySettings = scp.findByName("Default"); + Date currentDate = new Date(); + int days; + try { + // save issued certificate + IssuedAttestationCertificate attCert = new IssuedAttestationCertificate( + derEncodedAttestationCertificate, endorsementCredential, platformCredentials); + + if (scp != null) { + issuedAc = certificateRepository.findByDeviceId(device.getId()); + + generateCertificate = policySettings.isIssueAttestationCertificate(); + if (issuedAc != null && policySettings.isGenerateOnExpiration()) { + if (issuedAc.getEndValidity().after(currentDate)) { + // so the issued AC is not expired + // however are we within the threshold + days = daysBetween(currentDate, issuedAc.getEndValidity()); + if (days < Integer.parseInt(policySettings.getReissueThreshold())) { + generateCertificate = true; + } else { + generateCertificate = false; + } + } + } + } + if (generateCertificate) { + attCert.setDeviceName(device.getName()); + certificateRepository.save(attCert); + } + } catch (Exception e) { + log.error("Error saving generated Attestation Certificate to database.", e); + throw new CertificateProcessingException( + "Encountered error while storing Attestation Certificate: " + + e.getMessage(), e); + } + } + + private List getPlatformCredentials(final CertificateRepository certificateRepository, + final EndorsementCredential ec) { + List credentials = null; + + if (ec == null) { + log.warn("Cannot look for platform credential(s). Endorsement credential was null."); + } else { + log.debug("Searching for platform credential(s) based on holder serial number: " + + ec.getSerialNumber()); + credentials = certificateRepository.getByHolderSerialNumber(ec.getSerialNumber()); + if (credentials == null || credentials.isEmpty()) { + log.warn("No platform credential(s) found"); + } else { + log.debug("Platform Credential(s) found: " + credentials.size()); + } + } + + return credentials; + } + + @SuppressWarnings("magicnumber") + private int daysBetween(final Date date1, final Date date2) { + return (int) ((date2.getTime() - date1.getTime()) / (1000 * 60 * 60 * 24)); + } +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/CertificateRequestHandler.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/CertificateRequestHandler.java new file mode 100644 index 00000000..f2c3cce1 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/CertificateRequestHandler.java @@ -0,0 +1,218 @@ +package hirs.attestationca.persist.provision; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import hirs.attestationca.configuration.provisionerTpm2.ProvisionerTpm2; +import hirs.attestationca.persist.entity.manager.CertificateRepository; +import hirs.attestationca.persist.entity.manager.DeviceRepository; +import hirs.attestationca.persist.entity.manager.TPM2ProvisionerStateRepository; +import hirs.attestationca.persist.entity.tpm.TPM2ProvisionerState; +import hirs.attestationca.persist.entity.userdefined.Device; +import hirs.attestationca.persist.entity.userdefined.SupplyChainValidationSummary; +import hirs.attestationca.persist.entity.userdefined.certificate.EndorsementCredential; +import hirs.attestationca.persist.entity.userdefined.certificate.PlatformCredential; +import hirs.attestationca.persist.entity.userdefined.info.TPMInfo; +import hirs.attestationca.persist.entity.userdefined.report.DeviceInfoReport; +import hirs.attestationca.persist.enums.AppraisalStatus; +import hirs.attestationca.persist.exceptions.CertificateProcessingException; +import hirs.attestationca.persist.service.SupplyChainValidationService; +import lombok.extern.log4j.Log4j2; +import org.apache.commons.lang3.ArrayUtils; + +import java.nio.charset.StandardCharsets; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPublicKey; +import java.util.List; + +@Log4j2 +public class CertificateRequestHandler extends AbstractRequestHandler { + + private SupplyChainValidationService supplyChainValidationService; + private CertificateRepository certificateRepository; + private DeviceRepository deviceRepository; + private X509Certificate acaCertificate; + private TPM2ProvisionerStateRepository tpm2ProvisionerStateRepository; + + /** + * Constructor. + * @param certificateRepository db connector for all certificates. + * @param deviceRepository database connector for Devices. + * @param validDays int for the time in which a certificate is valid. + * @param tpm2ProvisionerStateRepository db connector for provisioner state. + */ + public CertificateRequestHandler(final SupplyChainValidationService supplyChainValidationService, + final CertificateRepository certificateRepository, + final DeviceRepository deviceRepository, + final PrivateKey privateKey, + final X509Certificate acaCertificate, + final int validDays, + final TPM2ProvisionerStateRepository tpm2ProvisionerStateRepository) { + super(privateKey, validDays); + this.supplyChainValidationService = supplyChainValidationService; + this.certificateRepository = certificateRepository; + this.deviceRepository = deviceRepository; + this.acaCertificate = acaCertificate; + this.tpm2ProvisionerStateRepository = tpm2ProvisionerStateRepository; + } + + /** + * Basic implementation of the ACA processCertificateRequest method. + * Parses the nonce, validates its correctness, generates the signed, + * public attestation certificate, stores it, and returns it to the client. + * + * @param certificateRequest request containing nonce from earlier identity + * claim handshake + * @return a certificateResponse containing the signed certificate + */ + public byte[] processCertificateRequest(final byte[] certificateRequest) { + log.info("Certificate Request received..."); + + if (ArrayUtils.isEmpty(certificateRequest)) { + throw new IllegalArgumentException("The CertificateRequest sent by the client" + + " cannot be null or empty."); + } + + // attempt to deserialize Protobuf CertificateRequest + ProvisionerTpm2.CertificateRequest request; + try { + request = ProvisionerTpm2.CertificateRequest.parseFrom(certificateRequest); + } catch (InvalidProtocolBufferException ipbe) { + throw new CertificateProcessingException( + "Could not deserialize Protobuf Certificate Request object.", ipbe); + } + + // attempt to retrieve provisioner state based on nonce in request + TPM2ProvisionerState tpm2ProvisionerState = getTpm2ProvisionerState(request); + if (tpm2ProvisionerState != null) { + // Reparse Identity Claim to gather necessary components + byte[] identityClaim = tpm2ProvisionerState.getIdentityClaim(); + ProvisionerTpm2.IdentityClaim claim = parseIdentityClaim(identityClaim); + + // Get endorsement public key + RSAPublicKey ekPub = parsePublicKey(claim.getEkPublicArea().toByteArray()); + + // Get attestation public key + RSAPublicKey akPub = parsePublicKey(claim.getAkPublicArea().toByteArray()); + + // Get Endorsement Credential if it exists or was uploaded + EndorsementCredential endorsementCredential = parseEcFromIdentityClaim(claim, ekPub, certificateRepository); + + // Get Platform Credentials if they exist or were uploaded + List platformCredentials = parsePcsFromIdentityClaim(claim, + endorsementCredential, certificateRepository); + + // Get device name and device + String deviceName = claim.getDv().getNw().getHostname(); + Device device = deviceRepository.findByName(deviceName); + + // Parse through the Provisioner supplied TPM Quote and pcr values + // these fields are optional + if (request.getQuote() != null && !request.getQuote().isEmpty()) { + parseTPMQuote(request.getQuote().toStringUtf8()); + TPMInfo savedInfo = device.getDeviceInfo().getTpmInfo(); + TPMInfo tpmInfo = new TPMInfo(savedInfo.getTpmMake(), + savedInfo.getTpmVersionMajor(), + savedInfo.getTpmVersionMinor(), + savedInfo.getTpmVersionRevMajor(), + savedInfo.getTpmVersionRevMinor(), + savedInfo.getPcrValues(), + getTpmQuoteHash().getBytes(StandardCharsets.UTF_8), + getTpmQuoteSignature().getBytes(StandardCharsets.UTF_8)); + + DeviceInfoReport dvReport = new DeviceInfoReport( + device.getDeviceInfo().getNetworkInfo(), + device.getDeviceInfo().getOSInfo(), + device.getDeviceInfo().getFirmwareInfo(), + device.getDeviceInfo().getHardwareInfo(), tpmInfo, + claim.getClientVersion()); + + device.setDeviceInfo(dvReport); + device = this.deviceRepository.save(device); + } + + AppraisalStatus.Status validationResult = doQuoteValidation(device); + if (validationResult == AppraisalStatus.Status.PASS) { + // Create signed, attestation certificate + X509Certificate attestationCertificate = generateCredential(akPub, + endorsementCredential, platformCredentials, deviceName, acaCertificate); + byte[] derEncodedAttestationCertificate = getDerEncodedCertificate( + attestationCertificate); + + // We validated the nonce and made use of the identity claim so state can be deleted + tpm2ProvisionerStateRepository.delete(tpm2ProvisionerState); + + // Package the signed certificate into a response + ByteString certificateBytes = ByteString + .copyFrom(derEncodedAttestationCertificate); + ProvisionerTpm2.CertificateResponse response = ProvisionerTpm2.CertificateResponse + .newBuilder().setCertificate(certificateBytes) + .setStatus(ProvisionerTpm2.ResponseStatus.PASS) + .build(); + + saveAttestationCertificate(certificateRepository, derEncodedAttestationCertificate, + endorsementCredential, platformCredentials, device); + + return response.toByteArray(); + } else { + log.error("Supply chain validation did not succeed. " + + "Firmware Quote Validation failed. Result is: " + + validationResult); + ProvisionerTpm2.CertificateResponse response = ProvisionerTpm2.CertificateResponse + .newBuilder() + .setStatus(ProvisionerTpm2.ResponseStatus.FAIL) + .build(); + return response.toByteArray(); + } + } else { + log.error("Could not process credential request. Invalid nonce provided: " + + request.getNonce().toString()); + throw new CertificateProcessingException("Invalid nonce given in request by client."); + } + } + + /** + * Helper method to unwrap the certificate request sent by the client and verify the + * provided nonce. + * + * @param request Client Certificate Request containing nonce to complete identity claim + * @return the {@link TPM2ProvisionerState} if valid nonce provided / null, otherwise + */ + private TPM2ProvisionerState getTpm2ProvisionerState( + final ProvisionerTpm2.CertificateRequest request) { + if (request.hasNonce()) { + byte[] nonce = request.getNonce().toByteArray(); + return TPM2ProvisionerState.getTPM2ProvisionerState(tpm2ProvisionerStateRepository, + nonce); + } + return null; + } + + /** + * Performs supply chain validation for just the quote under Firmware validation. + * Performed after main supply chain validation and a certificate request. + * + * @param device associated device to validate. + * @return the {@link AppraisalStatus} of the supply chain validation + */ + private AppraisalStatus.Status doQuoteValidation(final Device device) { + // perform supply chain validation + SupplyChainValidationSummary scvs = supplyChainValidationService.validateQuote( + device); + AppraisalStatus.Status validationResult; + + // either validation wasn't enabled or device already failed + if (scvs == null) { + // this will just allow for the certificate to be saved. + validationResult = AppraisalStatus.Status.PASS; + } else { + device.setSummaryId(scvs.getId().toString()); + // update the validation result in the device + validationResult = scvs.getOverallValidationResult(); + device.setSupplyChainValidationStatus(validationResult); + deviceRepository.save(device); + } + + return validationResult; + } +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/IdentityClaimHandler.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/IdentityClaimHandler.java new file mode 100644 index 00000000..99f0b648 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/IdentityClaimHandler.java @@ -0,0 +1,815 @@ +package hirs.attestationca.persist.provision; + +import com.google.protobuf.ByteString; +import hirs.attestationca.configuration.provisionerTpm2.ProvisionerTpm2; +import hirs.attestationca.persist.entity.manager.CertificateRepository; +import hirs.attestationca.persist.entity.manager.DeviceRepository; +import hirs.attestationca.persist.entity.manager.PolicyRepository; +import hirs.attestationca.persist.entity.manager.ReferenceDigestValueRepository; +import hirs.attestationca.persist.entity.manager.ReferenceManifestRepository; +import hirs.attestationca.persist.entity.manager.TPM2ProvisionerStateRepository; +import hirs.attestationca.persist.entity.tpm.TPM2ProvisionerState; +import hirs.attestationca.persist.entity.userdefined.Device; +import hirs.attestationca.persist.entity.userdefined.PolicySettings; +import hirs.attestationca.persist.entity.userdefined.ReferenceManifest; +import hirs.attestationca.persist.entity.userdefined.SupplyChainValidationSummary; +import hirs.attestationca.persist.entity.userdefined.certificate.EndorsementCredential; +import hirs.attestationca.persist.entity.userdefined.certificate.PlatformCredential; +import hirs.attestationca.persist.entity.userdefined.info.FirmwareInfo; +import hirs.attestationca.persist.entity.userdefined.info.HardwareInfo; +import hirs.attestationca.persist.entity.userdefined.info.NetworkInfo; +import hirs.attestationca.persist.entity.userdefined.info.OSInfo; +import hirs.attestationca.persist.entity.userdefined.info.TPMInfo; +import hirs.attestationca.persist.entity.userdefined.report.DeviceInfoReport; +import hirs.attestationca.persist.entity.userdefined.rim.BaseReferenceManifest; +import hirs.attestationca.persist.entity.userdefined.rim.EventLogMeasurements; +import hirs.attestationca.persist.entity.userdefined.rim.ReferenceDigestValue; +import hirs.attestationca.persist.entity.userdefined.rim.SupportReferenceManifest; +import hirs.attestationca.persist.enums.AppraisalStatus; +import hirs.attestationca.persist.exceptions.IdentityProcessingException; +import hirs.attestationca.persist.service.SupplyChainValidationService; +import hirs.utils.HexUtils; +import hirs.utils.SwidResource; +import hirs.utils.enums.DeviceInfoEnums; +import hirs.utils.tpm.eventlog.TCGEventLog; +import hirs.utils.tpm.eventlog.TpmPcrEvent; +import lombok.extern.log4j.Log4j2; +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.lang3.ArrayUtils; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.Mac; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.OAEPParameterSpec; +import javax.crypto.spec.PSource; +import javax.crypto.spec.SecretKeySpec; +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.cert.CertificateException; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.MGF1ParameterSpec; +import java.util.ArrayList; +import java.util.Base64; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Log4j2 +public class IdentityClaimHandler extends AbstractRequestHandler { + 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"; + + private static final int NUM_OF_VARIABLES = 5; + /** + * Number of bytes to include in the TPM2.0 nonce. + */ + public static final int NONCE_LENGTH = 20; + private static final int MAC_BYTES = 6; + + private SupplyChainValidationService supplyChainValidationService; + private CertificateRepository certificateRepository; + private ReferenceManifestRepository referenceManifestRepository; + private ReferenceDigestValueRepository referenceDigestValueRepository; + private DeviceRepository deviceRepository; + private TPM2ProvisionerStateRepository tpm2ProvisionerStateRepository; + + /** + * Constructor + */ + public IdentityClaimHandler( + final SupplyChainValidationService supplyChainValidationService, + final CertificateRepository certificateRepository, + final ReferenceManifestRepository referenceManifestRepository, + final ReferenceDigestValueRepository referenceDigestValueRepository, + final DeviceRepository deviceRepository, + final TPM2ProvisionerStateRepository tpm2ProvisionerStateRepository, + final PolicyRepository policyRepository) { + this.supplyChainValidationService = supplyChainValidationService; + this.certificateRepository = certificateRepository; + this.referenceManifestRepository = referenceManifestRepository; + this.referenceDigestValueRepository = referenceDigestValueRepository; + this.deviceRepository = deviceRepository; + this.tpm2ProvisionerStateRepository = tpm2ProvisionerStateRepository; + setPolicyRepository(policyRepository); + } + + /** + * Basic implementation of the ACA processIdentityClaimTpm2 method. Parses the claim, + * stores the device info, performs supply chain validation, generates a nonce, + * and wraps that nonce with the make credential process before returning it to the client. + * attCert.setPcrValues(pcrValues); + + * @param identityClaim the request to process, cannot be null + * @return an identity claim response for the specified request containing a wrapped blob + */ + public byte[] processIdentityClaimTpm2(final byte[] identityClaim) { + log.error("Identity Claim received..."); + + if (ArrayUtils.isEmpty(identityClaim)) { + log.error("Identity claim empty throwing exception."); + throw new IllegalArgumentException("The IdentityClaim sent by the client" + + " cannot be null or empty."); + } + + // attempt to deserialize Protobuf IdentityClaim + ProvisionerTpm2.IdentityClaim claim = parseIdentityClaim(identityClaim); + + // parse the EK Public key from the IdentityClaim once for use in supply chain validation + // and later tpm20MakeCredential function + RSAPublicKey ekPub = parsePublicKey(claim.getEkPublicArea().toByteArray()); + AppraisalStatus.Status validationResult = AppraisalStatus.Status.FAIL; + + try { + validationResult = doSupplyChainValidation(claim, ekPub); + } catch (Exception ex) { + for (StackTraceElement ste : ex.getStackTrace()) { + log.error(ste.toString()); + } + } + + ByteString blobStr = ByteString.copyFrom(new byte[]{}); + if (validationResult == AppraisalStatus.Status.PASS) { + RSAPublicKey akPub = parsePublicKey(claim.getAkPublicArea().toByteArray()); + byte[] nonce = generateRandomBytes(NONCE_LENGTH); + blobStr = tpm20MakeCredential(ekPub, akPub, nonce); + PolicyRepository scp = this.getPolicyRepository(); + PolicySettings policySettings = scp.findByName("Default"); + String pcrQuoteMask = PCR_QUOTE_MASK; + + String strNonce = HexUtils.byteArrayToHexString(nonce); + log.info("Sending nonce: " + strNonce); + log.info("Persisting claim of length: " + identityClaim.length); + + tpm2ProvisionerStateRepository.save(new TPM2ProvisionerState(nonce, identityClaim)); + + if (policySettings != null && policySettings.isIgnoreImaEnabled()) { + pcrQuoteMask = PCR_QUOTE_MASK.replace("10,", ""); + } + // Package response + ProvisionerTpm2.IdentityClaimResponse response + = ProvisionerTpm2.IdentityClaimResponse.newBuilder() + .setCredentialBlob(blobStr).setPcrMask(pcrQuoteMask) + .setStatus(ProvisionerTpm2.ResponseStatus.PASS) + .build(); + return response.toByteArray(); + } else { + log.error("Supply chain validation did not succeed. Result is: " + + validationResult); + // empty response + ProvisionerTpm2.IdentityClaimResponse response + = ProvisionerTpm2.IdentityClaimResponse.newBuilder() + .setCredentialBlob(blobStr) + .setStatus(ProvisionerTpm2.ResponseStatus.FAIL) + .build(); + return response.toByteArray(); + } + } + + /** + * Performs supply chain validation. + * + * @param claim the identity claim + * @param ekPub the public endorsement key + * @return the {@link AppraisalStatus} of the supply chain validation + */ + private AppraisalStatus.Status doSupplyChainValidation( + final ProvisionerTpm2.IdentityClaim claim, final PublicKey ekPub) { + // attempt to find an endorsement credential to validate + EndorsementCredential endorsementCredential = parseEcFromIdentityClaim(claim, ekPub, certificateRepository); + + // attempt to find platform credentials to validate + List platformCredentials = parsePcsFromIdentityClaim(claim, + endorsementCredential, certificateRepository); + + // Parse and save device info + Device device = processDeviceInfo(claim); + + // There are situations in which the claim is sent with no PCs + // or a PC from the tpm which will be deprecated + // this is to check what is in the platform object and pull + // additional information from the DB if information exists + if (platformCredentials.size() == 1) { + for (PlatformCredential pc : platformCredentials) { + if (pc != null && pc.getPlatformSerial() != null) { + platformCredentials.addAll(certificateRepository + .byBoardSerialNumber(pc.getPlatformSerial())); + } + } + } + // perform supply chain validation + SupplyChainValidationSummary summary = supplyChainValidationService.validateSupplyChain( + endorsementCredential, platformCredentials, device); + device.setSummaryId(summary.getId().toString()); + // update the validation result in the device + AppraisalStatus.Status validationResult = summary.getOverallValidationResult(); + device.setSupplyChainValidationStatus(validationResult); + this.deviceRepository.save(device); + return validationResult; + } + + private Device processDeviceInfo(final ProvisionerTpm2.IdentityClaim claim) { + DeviceInfoReport deviceInfoReport = null; + String deviceName = deviceInfoReport.getNetworkInfo().getHostname(); + + try { + deviceInfoReport = parseDeviceInfo(claim); + } catch (NoSuchAlgorithmException noSaEx) { + log.error(noSaEx); + } + + if (deviceInfoReport == null) { + log.error("Failed to deserialize Device Info Report"); + throw new IdentityProcessingException("Device Info Report failed to deserialize " + + "from Identity Claim"); + } + + log.info("Processing Device Info Report"); + // store device and device info report. + Device device = this.deviceRepository.findByName(deviceName); + device.setDeviceInfo(deviceInfoReport); + return this.deviceRepository.save(device); + } + + /** + * Converts a protobuf DeviceInfo object to a HIRS Utils DeviceInfoReport object. + * @param claim the protobuf serialized identity claim containing the device info + * @return a HIRS Utils DeviceInfoReport representation of device info + */ + @SuppressWarnings("methodlength") + private DeviceInfoReport parseDeviceInfo(final ProvisionerTpm2.IdentityClaim claim) + throws NoSuchAlgorithmException { + ProvisionerTpm2.DeviceInfo dv = claim.getDv(); + String pcrValues = ""; + + // Get network info + ProvisionerTpm2.NetworkInfo nwProto = dv.getNw(); + + InetAddress ip = null; + try { + ip = InetAddress.getByName(nwProto.getIpAddress()); + } catch (UnknownHostException e) { + log.error("Unable to parse IP address: ", e); + } + String[] macAddressParts = nwProto.getMacAddress().split(":"); + + // convert mac hex string to byte values + byte[] macAddressBytes = new byte[MAC_BYTES]; + Integer hex; + if (macAddressParts.length == MAC_BYTES) { + for (int i = 0; i < MAC_BYTES; i++) { + hex = HexUtils.hexToInt(macAddressParts[i]); + macAddressBytes[i] = hex.byteValue(); + } + } + NetworkInfo nw = new NetworkInfo(nwProto.getHostname(), ip, macAddressBytes); + + // Get firmware info + ProvisionerTpm2.FirmwareInfo fwProto = dv.getFw(); + FirmwareInfo fw = new FirmwareInfo(fwProto.getBiosVendor(), fwProto.getBiosVersion(), + fwProto.getBiosReleaseDate()); + + // Get OS info + ProvisionerTpm2.OsInfo osProto = dv.getOs(); + OSInfo os = new OSInfo(osProto.getOsName(), osProto.getOsVersion(), osProto.getOsArch(), + osProto.getDistribution(), osProto.getDistributionRelease()); + + // Get hardware info + ProvisionerTpm2.HardwareInfo hwProto = dv.getHw(); + // Make sure chassis info has at least one chassis + String firstChassisSerialNumber = DeviceInfoEnums.NOT_SPECIFIED; + if (hwProto.getChassisInfoCount() > 0) { + firstChassisSerialNumber = hwProto.getChassisInfo(0).getSerialNumber(); + } + // Make sure baseboard info has at least one baseboard + String firstBaseboardSerialNumber = DeviceInfoEnums.NOT_SPECIFIED; + if (hwProto.getBaseboardInfoCount() > 0) { + firstBaseboardSerialNumber = hwProto.getBaseboardInfo(0).getSerialNumber(); + } + HardwareInfo hw = new HardwareInfo(hwProto.getManufacturer(), hwProto.getProductName(), + hwProto.getProductVersion(), hwProto.getSystemSerialNumber(), + firstChassisSerialNumber, firstBaseboardSerialNumber); + + if (dv.hasPcrslist()) { + pcrValues = dv.getPcrslist().toStringUtf8(); + } + + // check for RIM Base and Support files, if they don't exists in the database, load them + String defaultClientName = String.format("%s_%s", + dv.getHw().getManufacturer(), + dv.getHw().getProductName()); + BaseReferenceManifest dbBaseRim = null; + SupportReferenceManifest support; + EventLogMeasurements measurements; + String tagId = ""; + String fileName = ""; + Pattern pattern = Pattern.compile("([^\\s]+(\\.(?i)(rimpcr|rimel|bin|log))$)"); + Matcher matcher; + MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); + List listOfSavedRims = new LinkedList<>(); + + if (dv.getLogfileCount() > 0) { + for (ByteString logFile : dv.getLogfileList()) { + try { + support = (SupportReferenceManifest) referenceManifestRepository.findByHexDecHash( + Hex.encodeHexString(messageDigest.digest(logFile.toByteArray()))); + if (support == null) { + support = new SupportReferenceManifest( + String.format("%s.rimel", + defaultClientName), + logFile.toByteArray()); + // this is a validity check + new TCGEventLog(support.getRimBytes()); + // no issues, continue + support.setPlatformManufacturer(dv.getHw().getManufacturer()); + support.setPlatformModel(dv.getHw().getProductName()); + support.setFileName(String.format("%s_[%s].rimel", defaultClientName, + support.getHexDecHash().substring( + support.getHexDecHash().length() - NUM_OF_VARIABLES))); + support.setDeviceName(dv.getNw().getHostname()); + this.referenceManifestRepository.save(support); + } else { + log.info("Client provided Support RIM already loaded in database."); + if (support.isArchived()) { + support.restore(); + support.resetCreateTime(); + this.referenceManifestRepository.save(support); + } + } + } catch (IOException ioEx) { + log.error(ioEx); + } catch (Exception ex) { + log.error(String.format("Failed to load support rim: %s", messageDigest.digest( + logFile.toByteArray()).toString())); + } + } + } else { + log.warn(String.format("%s did not send support RIM file...", + dv.getNw().getHostname())); + } + + if (dv.getSwidfileCount() > 0) { + for (ByteString swidFile : dv.getSwidfileList()) { + try { + dbBaseRim = (BaseReferenceManifest) referenceManifestRepository + .findByBase64Hash(Base64.getEncoder() + .encodeToString(messageDigest + .digest(swidFile.toByteArray()))); + if (dbBaseRim == null) { + dbBaseRim = new BaseReferenceManifest( + String.format("%s.swidtag", + defaultClientName), + swidFile.toByteArray()); + dbBaseRim.setDeviceName(dv.getNw().getHostname()); + this.referenceManifestRepository.save(dbBaseRim); + } else { + log.info("Client provided Base RIM already loaded in database."); + /** + * Leaving this as is for now, however can there be a condition + * in which the provisioner sends swidtags without support rims? + */ + if (dbBaseRim.isArchived()) { + dbBaseRim.restore(); + dbBaseRim.resetCreateTime(); + this.referenceManifestRepository.save(dbBaseRim); + } + } + } catch (IOException ioEx) { + log.error(ioEx); + } + } + } else { + log.warn(String.format("%s did not send swid tag file...", + dv.getNw().getHostname())); + } + + //update Support RIMs and Base RIMs. + for (ByteString swidFile : dv.getSwidfileList()) { + dbBaseRim = (BaseReferenceManifest) referenceManifestRepository + .findByBase64Hash(Base64.getEncoder().encodeToString(messageDigest.digest( + swidFile.toByteArray()))); + if (dbBaseRim != null) { + // get file name to use + for (SwidResource swid : dbBaseRim.getFileResources()) { + matcher = pattern.matcher(swid.getName()); + if (matcher.matches()) { + //found the file name + int dotIndex = swid.getName().lastIndexOf("."); + fileName = swid.getName().substring(0, dotIndex); + dbBaseRim.setFileName(String.format("%s.swidtag", + fileName)); + } + + // now update support rim + SupportReferenceManifest dbSupport = (SupportReferenceManifest) referenceManifestRepository + .findByHexDecHash(swid.getHashValue()); + if (dbSupport != null) { + dbSupport.setFileName(swid.getName()); + dbSupport.setSwidTagVersion(dbBaseRim.getSwidTagVersion()); + dbSupport.setTagId(dbBaseRim.getTagId()); + dbSupport.setSwidTagVersion(dbBaseRim.getSwidTagVersion()); + dbSupport.setSwidVersion(dbBaseRim.getSwidVersion()); + dbSupport.setSwidPatch(dbBaseRim.isSwidPatch()); + dbSupport.setSwidSupplemental(dbBaseRim.isSwidSupplemental()); + dbBaseRim.setAssociatedRim(dbSupport.getId()); + dbSupport.setUpdated(true); + dbSupport.setAssociatedRim(dbBaseRim.getId()); + this.referenceManifestRepository.save(dbSupport); + listOfSavedRims.add(dbSupport); + } + } + this.referenceManifestRepository.save(dbBaseRim); + listOfSavedRims.add(dbBaseRim); + } + } + + generateDigestRecords(hw.getManufacturer(), hw.getProductName()); + + if (dv.hasLivelog()) { + log.info("Device sent bios measurement log..."); + fileName = String.format("%s.measurement", + dv.getNw().getHostname()); + try { + EventLogMeasurements temp = new EventLogMeasurements(fileName, + dv.getLivelog().toByteArray()); + // find previous version. + measurements = referenceManifestRepository + .byMeasurementDeviceName(dv.getNw().getHostname()); + + if (measurements != null) { + // Find previous log and delete it + referenceManifestRepository.delete(measurements); + } + + BaseReferenceManifest baseRim = referenceManifestRepository + .getBaseByManufacturerModel(dv.getHw().getManufacturer(), + dv.getHw().getProductName()); + measurements = temp; + measurements.setPlatformManufacturer(dv.getHw().getManufacturer()); + measurements.setPlatformModel(dv.getHw().getProductName()); + measurements.setTagId(tagId); + measurements.setDeviceName(dv.getNw().getHostname()); + if (baseRim != null) { + measurements.setAssociatedRim(baseRim.getAssociatedRim()); + } + this.referenceManifestRepository.save(measurements); + + if (baseRim != null) { + // pull the base versions of the swidtag and rimel and set the + // event log hash for use during provision + SupportReferenceManifest sBaseRim = (SupportReferenceManifest) referenceManifestRepository + .findByBase64Hash(baseRim.getBase64Hash()); + baseRim.setEventLogHash(temp.getHexDecHash()); + sBaseRim.setEventLogHash(temp.getHexDecHash()); + referenceManifestRepository.save(baseRim); + referenceManifestRepository.save(sBaseRim); + } + } catch (IOException ioEx) { + log.error(ioEx); + } + } else { + log.warn(String.format("%s did not send bios measurement log...", + dv.getNw().getHostname())); + } + + // Get TPM info, currently unimplemented + TPMInfo tpm = createTpmInfo(pcrValues); + + // Create final report + DeviceInfoReport dvReport = new DeviceInfoReport(nw, os, fw, hw, tpm, + claim.getClientVersion()); + dvReport.setPaccorOutputString(claim.getPaccorOutput()); + + return dvReport; + } + + private boolean generateDigestRecords(final String manufacturer, final String model) { + List rdValues = new LinkedList<>(); + SupportReferenceManifest baseSupportRim = null; + List supplementalRims = new ArrayList<>(); + List patchRims = new ArrayList<>(); + List dbSupportRims = this.referenceManifestRepository + .getSupportByManufacturerModel(manufacturer, model); + List sourcedValues = referenceDigestValueRepository + .findByManufacturerAndModel(manufacturer, model); + + Map digestValueMap = new HashMap<>(); + sourcedValues.stream().forEach((rdv) -> { + digestValueMap.put(rdv.getDigestValue(), rdv); + }); + + for (SupportReferenceManifest dbSupport : dbSupportRims) { + if (dbSupport.isSwidPatch()) { + patchRims.add(dbSupport); + } else if (dbSupport.isSwidSupplemental()) { + supplementalRims.add(dbSupport); + } else { + // we have a base support rim (verify this is getting set) + baseSupportRim = dbSupport; + } + } + + if (baseSupportRim != null + && referenceDigestValueRepository.findBySupportRimHash(baseSupportRim.getHexDecHash()).isEmpty()) { + try { + TCGEventLog logProcessor = new TCGEventLog(baseSupportRim.getRimBytes()); + ReferenceDigestValue rdv; + for (TpmPcrEvent tpe : logProcessor.getEventList()) { + rdv = new ReferenceDigestValue(baseSupportRim.getAssociatedRim(), + baseSupportRim.getId(), manufacturer, model, tpe.getPcrIndex(), + tpe.getEventDigestStr(), baseSupportRim.getHexDecHash(), + tpe.getEventTypeStr(), + false, false, true, tpe.getEventContent()); + rdValues.add(rdv); + } + + // since I have the base already I don't have to care about the backward + // linkage + for (SupportReferenceManifest supplemental : supplementalRims) { + logProcessor = new TCGEventLog(supplemental.getRimBytes()); + for (TpmPcrEvent tpe : logProcessor.getEventList()) { + // all RDVs will have the same base rim + rdv = new ReferenceDigestValue(baseSupportRim.getAssociatedRim(), + supplemental.getId(), manufacturer, model, tpe.getPcrIndex(), + tpe.getEventDigestStr(), baseSupportRim.getHexDecHash(), + tpe.getEventTypeStr(), + false, false, true, tpe.getEventContent()); + rdValues.add(rdv); + } + } + + // Save all supplemental values + ReferenceDigestValue tempRdv; + for (ReferenceDigestValue subRdv : rdValues) { + // check if the value already exists + if (digestValueMap.containsKey(subRdv.getDigestValue())) { + tempRdv = digestValueMap.get(subRdv.getDigestValue()); + if (tempRdv.getPcrIndex() != subRdv.getPcrIndex() + && !tempRdv.getEventType().equals(subRdv.getEventType())) { + referenceDigestValueRepository.save(subRdv); + } else { + // will this be a problem down the line? + referenceDigestValueRepository.save(subRdv); + } + } else { + referenceDigestValueRepository.save(subRdv); + } + digestValueMap.put(subRdv.getDigestValue(), subRdv); + } + + // if a patch value doesn't exist, error? + ReferenceDigestValue dbRdv; + String patchedValue; + for (SupportReferenceManifest patch : patchRims) { + logProcessor = new TCGEventLog(patch.getRimBytes()); + for (TpmPcrEvent tpe : logProcessor.getEventList()) { + patchedValue = tpe.getEventDigestStr(); + dbRdv = digestValueMap.get(patchedValue); + + if (dbRdv == null) { + log.error(String.format("Patching value does not exist (%s)", + patchedValue)); + } else { + /** + * Until we get patch examples, this is WIP + */ + dbRdv.setPatched(true); + } + } + } + } catch (CertificateException cEx) { + log.error(cEx); + } catch (NoSuchAlgorithmException noSaEx) { + log.error(noSaEx); + } catch (IOException ioEx) { + log.error(ioEx); + } + } + + return true; + } + + /** + * Performs the first step of the TPM 2.0 identity claim process. Takes an ek, ak, and secret + * and then generates a seed that is used to generate AES and HMAC keys. Parses the ak name. + * Encrypts the seed with the public ek. Uses the AES key to encrypt the secret. Uses the HMAC + * key to generate an HMAC to cover the encrypted secret and the ak name. The output is an + * encrypted blob that acts as the first part of a challenge-response authentication mechanism + * to validate an identity claim. + * + * Equivalent to calling tpm2_makecredential using tpm2_tools. + * + * @param ek endorsement key in the identity claim + * @param ak attestation key in the identity claim + * @param secret a nonce + * @return the encrypted blob forming the identity claim challenge + */ + protected ByteString tpm20MakeCredential(final RSAPublicKey ek, final RSAPublicKey ak, + final byte[] secret) { + // check size of the secret + if (secret.length > MAX_SECRET_LENGTH) { + throw new IllegalArgumentException("Secret must be " + MAX_SECRET_LENGTH + + " bytes or smaller."); + } + + // generate a random 32 byte seed + byte[] seed = generateRandomBytes(SEED_LENGTH); + + try { + // encrypt seed with pubEk + Cipher asymCipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding"); + OAEPParameterSpec oaepSpec = new OAEPParameterSpec("SHA-256", "MGF1", + MGF1ParameterSpec.SHA256, new PSource.PSpecified("IDENTITY\0".getBytes())); + asymCipher.init(Cipher.PUBLIC_KEY, ek, oaepSpec); + asymCipher.update(seed); + byte[] encSeed = asymCipher.doFinal(); + + // generate ak name from akMod + byte[] akModTemp = ak.getModulus().toByteArray(); + byte[] akMod = new byte[RSA_MODULUS_LENGTH]; + int startpos = 0; + // BigIntegers are signed, so a modulus that has a first bit of 1 + // will be padded with a zero byte that must be removed + if (akModTemp[0] == 0x00) { + startpos = 1; + } + System.arraycopy(akModTemp, startpos, akMod, 0, RSA_MODULUS_LENGTH); + byte[] akName = generateAkName(akMod); + + // generate AES and HMAC keys from seed + byte[] aesKey = cryptKDFa(seed, "STORAGE", akName, AES_KEY_LENGTH_BYTES); + byte[] hmacKey = cryptKDFa(seed, "INTEGRITY", null, HMAC_KEY_LENGTH_BYTES); + + // use two bytes to add a size prefix on secret + ByteBuffer b; + b = ByteBuffer.allocate(2); + b.putShort((short) (secret.length)); + byte[] secretLength = b.array(); + byte[] secretBytes = new byte[secret.length + 2]; + System.arraycopy(secretLength, 0, secretBytes, 0, 2); + System.arraycopy(secret, 0, secretBytes, 2, secret.length); + + // encrypt size prefix + secret with AES key + Cipher symCipher = Cipher.getInstance("AES/CFB/NoPadding"); + byte[] defaultIv = HexUtils.hexStringToByteArray("00000000000000000000000000000000"); + IvParameterSpec ivSpec = new IvParameterSpec(defaultIv); + symCipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(aesKey, "AES"), ivSpec); + byte[] encSecret = symCipher.doFinal(secretBytes); + + // generate HMAC covering encrypted secret and ak name + Mac integrityHmac = Mac.getInstance("HmacSHA256"); + SecretKeySpec integrityKey = new SecretKeySpec(hmacKey, integrityHmac.getAlgorithm()); + integrityHmac.init(integrityKey); + byte[] message = new byte[encSecret.length + akName.length]; + System.arraycopy(encSecret, 0, message, 0, encSecret.length); + System.arraycopy(akName, 0, message, encSecret.length, akName.length); + integrityHmac.update(message); + byte[] integrity = integrityHmac.doFinal(); + b = ByteBuffer.allocate(2); + b.putShort((short) (HMAC_SIZE_LENGTH_BYTES + HMAC_KEY_LENGTH_BYTES + encSecret.length)); + byte[] topSize = b.array(); + + // return ordered blob of assembled credentials + byte[] bytesToReturn = assembleCredential(topSize, integrity, encSecret, encSeed); + return ByteString.copyFrom(bytesToReturn); + + } catch (BadPaddingException | IllegalBlockSizeException | NoSuchAlgorithmException + | InvalidKeyException | InvalidAlgorithmParameterException + | NoSuchPaddingException e) { + throw new IdentityProcessingException( + "Encountered error while making the identity claim challenge: " + + e.getMessage(), e); + } + } + + @SuppressWarnings("magicnumber") + private byte[] assembleCredential(final byte[] topSize, final byte[] integrityHmac, + final byte[] encryptedSecret, + final byte[] encryptedSeed) { + /* + * Credential structure breakdown with endianness: + * 0-1 topSize (2), LE + * 2-3 hashsize (2), BE always 0x0020 + * 4-35 integrity HMac (32) + * 36-133 (98 = 32*3 +2) of zeros, copy over from encSecret starting at [36] + * 134-135 (2) LE size, always 0x0001 + * 136-391 (256) copy over with encSeed + * */ + byte[] credentialBlob = new byte[TPM2_CREDENTIAL_BLOB_SIZE]; + credentialBlob[0] = topSize[1]; + credentialBlob[1] = topSize[0]; + credentialBlob[2] = 0x00; + credentialBlob[3] = 0x20; + System.arraycopy(integrityHmac, 0, credentialBlob, 4, 32); + for (int i = 0; i < 98; i++) { + credentialBlob[36 + i] = 0x00; + } + System.arraycopy(encryptedSecret, 0, credentialBlob, 36, encryptedSecret.length); + credentialBlob[134] = 0x00; + credentialBlob[135] = 0x01; + System.arraycopy(encryptedSeed, 0, credentialBlob, 136, 256); + // return the result + return credentialBlob; + } + + /** + * Determines the AK name from the AK Modulus. + * @param akModulus modulus of an attestation key + * @return the ak name byte array + * @throws NoSuchAlgorithmException Underlying SHA256 method used a bad algorithm + */ + byte[] generateAkName(final byte[] akModulus) throws NoSuchAlgorithmException { + byte[] namePrefix = HexUtils.hexStringToByteArray(AK_NAME_PREFIX); + byte[] hashPrefix = HexUtils.hexStringToByteArray(AK_NAME_HASH_PREFIX); + byte[] toHash = new byte[hashPrefix.length + akModulus.length]; + System.arraycopy(hashPrefix, 0, toHash, 0, hashPrefix.length); + System.arraycopy(akModulus, 0, toHash, hashPrefix.length, akModulus.length); + byte[] nameHash = sha256hash(toHash); + byte[] toReturn = new byte[namePrefix.length + nameHash.length]; + System.arraycopy(namePrefix, 0, toReturn, 0, namePrefix.length); + System.arraycopy(nameHash, 0, toReturn, namePrefix.length, nameHash.length); + return toReturn; + } + + /** + * This replicates the TPM 2.0 CryptKDFa function to an extent. It will only work for generation + * that uses SHA-256, and will only generate values of 32 B or less. Counters above zero and + * multiple contexts are not supported in this implementation. This should work for all uses of + * the KDF for TPM2_MakeCredential. + * + * @param seed random value used to generate the key + * @param label first portion of message used to generate key + * @param context second portion of message used to generate key + * @param sizeInBytes size of key to generate in bytes + * @return the derived key + * @throws NoSuchAlgorithmException Wrong crypto algorithm selected + * @throws InvalidKeyException Invalid key used + */ + @SuppressWarnings("magicnumber") + private byte[] cryptKDFa(final byte[] seed, final String label, final byte[] context, + final int sizeInBytes) + throws NoSuchAlgorithmException, InvalidKeyException { + ByteBuffer b = ByteBuffer.allocate(4); + b.putInt(1); + byte[] counter = b.array(); + // get the label + String labelWithEnding = label; + if (label.charAt(label.length() - 1) != "\0".charAt(0)) { + labelWithEnding = label + "\0"; + } + byte[] labelBytes = labelWithEnding.getBytes(); + b = ByteBuffer.allocate(4); + b.putInt(sizeInBytes * 8); + byte[] desiredSizeInBits = b.array(); + int sizeOfMessage = 8 + labelBytes.length; + if (context != null) { + sizeOfMessage += context.length; + } + byte[] message = new byte[sizeOfMessage]; + int marker = 0; + System.arraycopy(counter, 0, message, marker, 4); + marker += 4; + System.arraycopy(labelBytes, 0, message, marker, labelBytes.length); + marker += labelBytes.length; + if (context != null) { + System.arraycopy(context, 0, message, marker, context.length); + marker += context.length; + } + System.arraycopy(desiredSizeInBits, 0, message, marker, 4); + Mac hmac; + byte[] toReturn = new byte[sizeInBytes]; + + hmac = Mac.getInstance("HmacSHA256"); + SecretKeySpec hmacKey = new SecretKeySpec(seed, hmac.getAlgorithm()); + hmac.init(hmacKey); + hmac.update(message); + byte[] hmacResult = hmac.doFinal(); + System.arraycopy(hmacResult, 0, toReturn, 0, sizeInBytes); + return toReturn; + } + + /** + * Computes the sha256 hash of the given blob. + * @param blob byte array to take the hash of + * @return sha256 hash of blob + * @throws NoSuchAlgorithmException improper algorithm selected + */ + private byte[] sha256hash(final byte[] blob) throws NoSuchAlgorithmException { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + md.update(blob); + return md.digest(); + } +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/IdentityRequestHandler.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/IdentityRequestHandler.java new file mode 100644 index 00000000..0bf80e36 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/IdentityRequestHandler.java @@ -0,0 +1,574 @@ +package hirs.attestationca.persist.provision; + +import hirs.attestationca.persist.entity.manager.CertificateRepository; +import hirs.attestationca.persist.entity.manager.DeviceRepository; +import hirs.attestationca.persist.entity.userdefined.Certificate; +import hirs.attestationca.persist.entity.userdefined.Device; +import hirs.attestationca.persist.entity.userdefined.SupplyChainValidationSummary; +import hirs.attestationca.persist.entity.userdefined.certificate.EndorsementCredential; +import hirs.attestationca.persist.entity.userdefined.certificate.PlatformCredential; +import hirs.attestationca.persist.entity.userdefined.report.DeviceInfoReport; +import hirs.attestationca.persist.enums.AppraisalStatus; +import hirs.attestationca.persist.exceptions.CertificateProcessingException; +import hirs.attestationca.persist.exceptions.IdentityProcessingException; +import hirs.attestationca.persist.provision.helper.CredentialManagementHelper; +import hirs.attestationca.persist.service.SupplyChainValidationService; +import hirs.structs.converters.SimpleStructBuilder; +import hirs.structs.converters.StructConverter; +import hirs.structs.elements.aca.IdentityRequestEnvelope; +import hirs.structs.elements.aca.IdentityResponseEnvelope; +import hirs.structs.elements.aca.SymmetricAttestation; +import hirs.structs.elements.tpm.EncryptionScheme; +import hirs.structs.elements.tpm.IdentityProof; +import hirs.structs.elements.tpm.IdentityRequest; +import hirs.structs.elements.tpm.SymmetricKey; +import hirs.structs.elements.tpm.SymmetricKeyParams; +import lombok.extern.log4j.Log4j2; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.SerializationUtils; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.OAEPParameterSpec; +import javax.crypto.spec.PSource; +import javax.crypto.spec.SecretKeySpec; +import java.io.IOException; +import java.math.BigInteger; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.security.spec.MGF1ParameterSpec; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +@Log4j2 +public class IdentityRequestHandler extends AbstractRequestHandler { + + /** + * Container wired ACA private key. + */ + private final PrivateKey privateKey; + private int validDays; + private StructConverter structConverter; + private CertificateRepository certificateRepository; + private DeviceRepository deviceRepository; + private SupplyChainValidationService supplyChainValidationService; + private X509Certificate acaCertificate; + + /** + * Constructor. + * @param structConverter the struct converter + * @param certificateRepository + * @param deviceRepository + * @param supplyChainValidationService the supply chain service + * @param privateKey + * @param validDays int for the time in which a certificate is valid. + * @param acaCertificate object holding the x509 certificate + */ + public IdentityRequestHandler(final StructConverter structConverter, + final CertificateRepository certificateRepository, + final DeviceRepository deviceRepository, + final SupplyChainValidationService supplyChainValidationService, + final PrivateKey privateKey, + final int validDays, final X509Certificate acaCertificate) { + super(privateKey, validDays); + this.structConverter = structConverter; + this.certificateRepository = certificateRepository; + this.deviceRepository = deviceRepository; + this.supplyChainValidationService = supplyChainValidationService; + this.privateKey = privateKey; + this.acaCertificate = acaCertificate; + } + + /** + * Basic implementation of the ACA processIdentityRequest method. + * + * @param identityRequest cannot be null + * @return an identity response for the specified request + */ + public byte[] processIdentityRequest(final byte[] identityRequest) { + log.info("Identity Request Received..."); + if (ArrayUtils.isEmpty(identityRequest)) { + throw new IllegalArgumentException("The IdentityRequest sent by the client" + + " cannot be null or empty."); + } + + log.debug("received request to process identity request"); + + // translate the bytes into the challenge + IdentityRequestEnvelope challenge = + structConverter.convert(identityRequest, IdentityRequestEnvelope.class); + + byte[] identityProof = unwrapIdentityRequest(challenge.getRequest()); + // the decrypted symmetric blob should be in the format of an IdentityProof. Use the + // struct converter to generate it. + IdentityProof proof = structConverter.convert(identityProof, IdentityProof.class); + + // convert the credential into an actual key. + log.debug("assembling public endorsement key"); + PublicKey ekPublicKey = null; + + // attempt to find an endorsement credential to validate + EndorsementCredential endorsementCredential = null; + + // first check the identity request for the endorsement credential + byte[] ecBytesFromIdentityRequest = proof.getEndorsementCredential(); + if (ArrayUtils.isNotEmpty(ecBytesFromIdentityRequest)) { + endorsementCredential = CredentialManagementHelper.storeEndorsementCredential( + this.certificateRepository, ecBytesFromIdentityRequest); + try { + BigInteger publicKeyModulus = Certificate.getPublicKeyModulus( + endorsementCredential.getX509Certificate()); + if (publicKeyModulus != null) { + ekPublicKey = assemblePublicKey(publicKeyModulus.toByteArray()); + } else { + throw new IdentityProcessingException("TPM 1.2 Provisioning requires EK " + + "Credentials to be created with RSA"); + } + } catch (IOException ioEx) { + log.error("Could not retrieve the public key modulus from the EK cert"); + } + } else if (ArrayUtils.isNotEmpty(challenge.getEndorsementCredentialModulus())) { + log.warn("EKC was not in the identity proof from the client. Checking for uploads."); + // Check if the EC was uploaded + ekPublicKey = + assemblePublicKey(new String(challenge.getEndorsementCredentialModulus())); + endorsementCredential = getEndorsementCredential(ekPublicKey); + } else { + log.warn("Zero-length endorsement credential received in identity request."); + } + + // get platform credential from the identity request + List platformCredentials = new LinkedList<>(); + byte[] pcBytesFromIdentityRequest = proof.getPlatformCredential(); + if (ArrayUtils.isNotEmpty(pcBytesFromIdentityRequest)) { + platformCredentials.add(CredentialManagementHelper.storePlatformCredential( + this.certificateRepository, pcBytesFromIdentityRequest)); + } else if (endorsementCredential != null) { + // if none in the identity request, look for uploaded platform credentials + log.warn("PC was not in the identity proof from the client. Checking for uploads."); + platformCredentials.addAll(getPlatformCredentials(endorsementCredential)); + } else { + // if none in the identity request, look for uploaded platform credentials + log.warn("Zero-length platform credential received in identity request."); + } + + log.debug("Processing serialized device info report structure of length {}", + challenge.getDeviceInfoReportLength()); + + DeviceInfoReport deviceInfoReport = (DeviceInfoReport) + SerializationUtils.deserialize(challenge.getDeviceInfoReport()); + + if (deviceInfoReport == null) { + log.error("Failed to deserialize Device Info Report"); + throw new IdentityProcessingException("Device Info Report failed to deserialize " + + "from Identity Request"); + } + + log.info("Processing Device Info Report"); + // store device and device info report. + String deviceName = deviceInfoReport.getNetworkInfo().getHostname(); + Device device = this.deviceRepository.findByName(deviceName); + device.setDeviceInfo(deviceInfoReport); + + // perform supply chain validation. Note: It's possible that this should be done earlier + // in this method. + SupplyChainValidationSummary summary = + supplyChainValidationService.validateSupplyChain(endorsementCredential, + platformCredentials, device); + + // update the validation result in the device + device.setSupplyChainValidationStatus(summary.getOverallValidationResult()); + deviceRepository.save(device); + // check if supply chain validation succeeded. + // If it did not, do not provide the IdentityResponseEnvelope + if (summary.getOverallValidationResult() == AppraisalStatus.Status.PASS) { + IdentityResponseEnvelope identityResponse = + generateIdentityResponseEnvelopeAndStoreIssuedCert(challenge, + ekPublicKey, endorsementCredential, platformCredentials, device); + + return structConverter.convert(identityResponse); + } else { + log.error("Supply chain validation did not succeed. Result is: " + + summary.getOverallValidationResult()); + return new byte[]{}; + } + } + + /** + * Given a successful supply chain validation, generate an Identity Response envelope and + * the issued certificate. The issued cert is stored in the database. The identity response + * envelope is returned, and sent back to the client using the struct converter. + * @param challenge the identity request envelope + * @param ekPublicKey the EK public key + * @param endorsementCredential the endorsement credential + * @param platformCredentials the set of platform credentials + * @param device the device associated + * @return the identity response envelope + */ + private IdentityResponseEnvelope generateIdentityResponseEnvelopeAndStoreIssuedCert( + final IdentityRequestEnvelope challenge, final PublicKey ekPublicKey, + final EndorsementCredential endorsementCredential, + final List platformCredentials, final Device device) { + // decrypt the asymmetric / symmetric blobs + log.debug("unwrapping identity request"); + byte[] identityProof = unwrapIdentityRequest(challenge.getRequest()); + + // the decrypted symmetric blob should be in the format of an IdentityProof. Use the + // struct converter to generate it. + IdentityProof proof = structConverter.convert(identityProof, IdentityProof.class); + + // generate a session key and convert to byte array + log.debug("generating symmetric key for response"); + SymmetricKey sessionKey = generateSymmetricKey(); + + // generate the asymmetric contents for the identity response + log.debug("generating asymmetric contents for response"); + byte[] asymmetricContents = generateAsymmetricContents(proof, sessionKey, ekPublicKey); + + // generate the identity credential + log.debug("generating credential from identity proof"); + + // transform the public key struct into a public key + PublicKey publicKey = assemblePublicKey(proof.getIdentityKey().getStorePubKey().getKey()); + X509Certificate credential = generateCredential(publicKey, endorsementCredential, + platformCredentials, device.getDeviceInfo() + .getNetworkInfo() + .getIpAddress() + .getHostName(), acaCertificate); + + // generate the attestation using the credential and the key for this session + log.debug("generating symmetric response"); + SymmetricAttestation attestation = generateAttestation(credential, sessionKey); + + // construct the response with the both the asymmetric contents and the CA attestation + IdentityResponseEnvelope identityResponse = + new SimpleStructBuilder<>(IdentityResponseEnvelope.class) + .set("asymmetricContents", asymmetricContents) + .set("symmetricAttestation", attestation).build(); + + // save new attestation certificate + byte[] derEncodedAttestationCertificate = getDerEncodedCertificate(credential); + saveAttestationCertificate(this.certificateRepository, derEncodedAttestationCertificate, + endorsementCredential, platformCredentials, device); + + return identityResponse; + } + + /** + * Unwraps a given identityRequest. That is to say, decrypt the asymmetric portion of a data + * structure to determine the method to decrypt the symmetric portion. + * + * @param identityRequest + * to be decrypted + * @return the decrypted symmetric portion of an identity request. + */ + private byte[] unwrapIdentityRequest(final byte[] identityRequest) { + IdentityRequest request = structConverter.convert(identityRequest, IdentityRequest.class); + + // in case the TPM did not specify the IV, it must be extracted from the symmetric blob. + // the IV will then be the the first block of the cipher text. + final byte[] iv; + SymmetricKeyParams symmetricKeyParams = request.getSymmetricAlgorithm(); + if (symmetricKeyParams != null && symmetricKeyParams.getParams() != null) { + iv = symmetricKeyParams.getParams().getIv(); + } else { + iv = extractInitialValue(request); + } + + // determine the encryption scheme from the algorithm + EncryptionScheme asymmetricScheme = + EncryptionScheme.fromInt(request.getAsymmetricAlgorithm().getEncryptionScheme()); + + // decrypt the asymmetric blob + byte[] decryptedAsymmetricBlob = + decryptAsymmetricBlob(request.getAsymmetricBlob(), asymmetricScheme); + + // construct our symmetric key structure from the decrypted asymmetric blob + SymmetricKey symmetricKey = + structConverter.convert(decryptedAsymmetricBlob, SymmetricKey.class); + + byte[] decryptedSymmetricBlob = + decryptSymmetricBlob(request.getSymmetricBlob(), symmetricKey.getKey(), iv, + "AES/CBC/PKCS5Padding"); + + // decrypt the symmetric blob + return decryptedSymmetricBlob; + } + + /** + * Gets the Endorsement Credential from the DB given the EK public key. + * @param ekPublicKey the EK public key + * @return the Endorsement credential, if found, otherwise null + */ + private EndorsementCredential getEndorsementCredential(final PublicKey ekPublicKey) { + log.debug("Searching for endorsement credential based on public key: " + ekPublicKey); + + if (ekPublicKey == null) { + throw new IllegalArgumentException("Cannot look up an EC given a null public key"); + } + + EndorsementCredential credential = null; + + try { + credential = certificateRepository.findByPublicKeyModulusHexValue(Certificate + .getPublicKeyModulus(ekPublicKey) + .toString()); + } catch (IOException ioEx) { + log.error("Could not extract public key modulus", ioEx); + } + + if (credential == null) { + log.warn("Unable to find endorsement credential for public key."); + } else { + log.debug("Endorsement credential found."); + } + + return credential; + } + + private List getPlatformCredentials(final EndorsementCredential ec) { + List credentials = null; + + if (ec == null) { + log.warn("Cannot look for platform credential(s). Endorsement credential was null."); + } else { + log.debug("Searching for platform credential(s) based on holder serial number: " + + ec.getSerialNumber()); + credentials = this.certificateRepository.getByHolderSerialNumber(ec.getSerialNumber()); + if (credentials == null || credentials.isEmpty()) { + log.warn("No platform credential(s) found"); + } else { + log.debug("Platform Credential(s) found: " + credentials.size()); + } + } + + return credentials; + } + + /** + * Will attempt to decrypt the asymmetric blob that originated from an + * {@link hirs.structs.elements.tpm.IdentityRequest} using the cipher transformation. + * + * @param asymmetricBlob to be decrypted + * @param scheme to decrypt with + * @return decrypted blob + */ + private byte[] decryptAsymmetricBlob(final byte[] asymmetricBlob, final EncryptionScheme scheme) { + try { + // create a cipher from the specified transformation + Cipher cipher = Cipher.getInstance(scheme.toString()); + + switch (scheme) { + case OAEP: + OAEPParameterSpec spec = + new OAEPParameterSpec("Sha1", "MGF1", MGF1ParameterSpec.SHA1, + new PSource.PSpecified("".getBytes())); + + cipher.init(Cipher.PRIVATE_KEY, privateKey, spec); + break; + default: + // initialize the cipher to decrypt using the ACA private key. + cipher.init(Cipher.DECRYPT_MODE, privateKey); + } + + cipher.update(asymmetricBlob); + + return cipher.doFinal(); + } catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException + | BadPaddingException | IllegalBlockSizeException + | InvalidAlgorithmParameterException e) { + throw new IdentityProcessingException( + "Encountered error while decrypting asymmetric blob of an identity request: " + + e.getMessage(), e); + } + } + + /** + * Will attempt to decrypt the symmetric blob that originated from an + * {@link hirs.structs.elements.tpm.IdentityRequest} using the specified symmetric key + * and cipher transformation. + * + * @param symmetricBlob to be decrypted + * @param symmetricKey to use to decrypt + * @param iv to use with decryption cipher + * @param transformation of the cipher + * @return decrypted symmetric blob + */ + private byte[] decryptSymmetricBlob(final byte[] symmetricBlob, final byte[] symmetricKey, + final byte[] iv, final String transformation) { + try { + // create a cipher from the specified transformation + Cipher cipher = Cipher.getInstance(transformation); + + // generate a key specification to initialize the cipher + SecretKeySpec keySpec = new SecretKeySpec(symmetricKey, "AES"); + + // initialize the cipher to decrypt using the symmetric key + cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv)); + + // decrypt the symmetric blob + return cipher.doFinal(symmetricBlob); + } catch (IllegalBlockSizeException | InvalidKeyException | NoSuchAlgorithmException + | BadPaddingException | NoSuchPaddingException + | InvalidAlgorithmParameterException exception) { + log.error("Encountered error while decrypting symmetric blob of an identity request: " + + exception.getMessage(), exception); + } + + return new byte[0]; + } + + private SymmetricKey generateSymmetricKey() { + // create a session key for the CA contents + byte[] responseSymmetricKey = + generateRandomBytes(DEFAULT_IV_SIZE); + + // create a symmetric key struct for the CA contents + SymmetricKey sessionKey = + new SimpleStructBuilder<>(SymmetricKey.class) + .set("algorithmId", SymmetricKey.ALGORITHM_AES) + .set("encryptionScheme", SymmetricKey.SCHEME_CBC) + .set("key", responseSymmetricKey).build(); + return sessionKey; + } + + /** + * Generate asymmetric contents part of the identity response. + * + * @param proof identity requests symmetric contents, otherwise, the identity proof + * @param symmetricKey identity response session key + * @param publicKey of the EK certificate contained within the identity proof + * @return encrypted asymmetric contents + */ + byte[] generateAsymmetricContents(final IdentityProof proof, + final SymmetricKey symmetricKey, + final PublicKey publicKey) { + try { + // obtain the identity key from the identity proof + byte[] identityKey = structConverter.convert(proof.getIdentityKey()); + byte[] sessionKey = structConverter.convert(symmetricKey); + + // create a SHA1 digest of the identity key + MessageDigest md = MessageDigest.getInstance("SHA-1"); + md.update(identityKey); + + // generate the digest + byte[] identityDigest = md.digest(); + + // combine the session key with the digest of the identity key + byte[] asymmetricContents = ArrayUtils.addAll(sessionKey, identityDigest); + + // encrypt the asymmetric contents and return + OAEPParameterSpec oaepSpec = + new OAEPParameterSpec("Sha1", "MGF1", MGF1ParameterSpec.SHA1, + new PSource.PSpecified("TCPA".getBytes())); + + // initialize the asymmetric cipher using the default OAEP transformation + Cipher cipher = Cipher.getInstance(EncryptionScheme.OAEP.toString()); + + // initialize the cipher using the public spec with the additional OAEP specification + cipher.init(Cipher.PUBLIC_KEY, publicKey, oaepSpec); + + return cipher.doFinal(asymmetricContents); + } catch (NoSuchAlgorithmException | IllegalBlockSizeException | NoSuchPaddingException + | InvalidKeyException | BadPaddingException + | InvalidAlgorithmParameterException e) { + throw new CertificateProcessingException( + "Encountered error while generating ACA session key: " + e.getMessage(), e); + } + } + + /** + * Extracts the IV from the identity request. That is, take the first block of data from the + * symmetric blob and treat that as the IV. This modifies the original symmetric block. + * + * @param identityRequest to extract the IV from + * @return the IV from the identity request + */ + private byte[] extractInitialValue(final IdentityRequest identityRequest) { + + // make a reference to the symmetric blob + byte[] symmetricBlob = identityRequest.getSymmetricBlob(); + + // create the IV + byte[] iv = new byte[DEFAULT_IV_SIZE]; + + // initialize a new symmetric blob with the length of the original minus the IV + byte[] updatedBlob = new byte[symmetricBlob.length - iv.length]; + + // copy the IV out of the original symmetric blob + System.arraycopy(symmetricBlob, 0, iv, 0, iv.length); + + // copy everything but the IV out of the original blob into the new blob + System.arraycopy(symmetricBlob, iv.length, updatedBlob, 0, updatedBlob.length); + + // reassign the symmetric blob to the request. + identityRequest.setSymmetricBlob(updatedBlob); + + return iv; + } + + /** + * Generate the Identity Response using the identity credential and the session key. + * + * @param credential the identity credential + * @param symmetricKey generated session key for this request/response chain + * @return identity response for an identity request + */ + SymmetricAttestation generateAttestation(final X509Certificate credential, + final SymmetricKey symmetricKey) { + try { + // initialize the symmetric cipher + Cipher aesCipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + + // generate a key specification to initialize the cipher + SecretKeySpec keySpec = new SecretKeySpec(symmetricKey.getKey(), "AES"); + + // fill IV with random bytes + byte[] credentialIV = generateRandomBytes(DEFAULT_IV_SIZE); + + // create IV encryption parameter specification + IvParameterSpec ivParameterSpec = new IvParameterSpec(credentialIV); + + // initialize the cipher to decrypt using the symmetric key + aesCipher.init(Cipher.ENCRYPT_MODE, keySpec, ivParameterSpec); + + // encrypt the credential + byte[] encryptedCredential = aesCipher.doFinal(credential.getEncoded()); + + // prepend the IV to the encrypted credential + byte[] credentialBytes = ArrayUtils.addAll(credentialIV, encryptedCredential); + + // create attestation for identity response that contains the credential + SymmetricAttestation attestation = + new SimpleStructBuilder<>(SymmetricAttestation.class) + .set("credential", credentialBytes) + .set("algorithm", + new SimpleStructBuilder<>(SymmetricKeyParams.class) + .set("algorithmId", SymmetricKeyParams.ALGORITHM_AES) + .set("encryptionScheme", + SymmetricKeyParams.SCHEME_CBC_PKCS5PADDING) + .set("signatureScheme", 0).build()).build(); + + return attestation; + + } catch (BadPaddingException | IllegalBlockSizeException | NoSuchAlgorithmException + | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException + | CertificateEncodingException exception) { + throw new CertificateProcessingException( + "Encountered error while generating Identity Response: " + + exception.getMessage(), exception); + } + } +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/helper/CredentialManagementHelper.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/helper/CredentialManagementHelper.java new file mode 100644 index 00000000..1b99e2e2 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/helper/CredentialManagementHelper.java @@ -0,0 +1,148 @@ +package hirs.attestationca.persist.provision.helper; + +import hirs.attestationca.persist.DBManagerException; +import hirs.attestationca.persist.entity.manager.CertificateRepository; +import hirs.attestationca.persist.entity.userdefined.certificate.EndorsementCredential; +import hirs.attestationca.persist.entity.userdefined.certificate.PlatformCredential; +import lombok.extern.log4j.Log4j2; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.List; +import java.util.stream.Collectors; + + +/** + * Utility class which includes credential management functions used by the ACA. + */ +@Log4j2 +public final class CredentialManagementHelper { + + private CredentialManagementHelper() { + + } + + /** + * Parses and stores the EK in the cert manager. If the cert is already present and archived, + * it is unarchived. + * @param certificateRepository the certificate manager used for storage + * @param endorsementBytes the raw EK bytes used for parsing + * @return the parsed, valid EK + * @throws IllegalArgumentException if the provided bytes are not a valid EK. + */ + public static EndorsementCredential storeEndorsementCredential( + final CertificateRepository certificateRepository, + final byte[] endorsementBytes) throws IllegalArgumentException { + + if (certificateRepository == null) { + throw new IllegalArgumentException("null certificate manager"); + } + + if (endorsementBytes == null) { + throw new IllegalArgumentException("null endorsement credential bytes"); + } + + if (endorsementBytes.length <= 1) { + throw new IllegalArgumentException( + String.format("%d-length byte array given for endorsement credential", + endorsementBytes.length) + ); + } + + log.info("Parsing Endorsement Credential of length " + endorsementBytes.length); + + EndorsementCredential endorsementCredential; + try { + endorsementCredential = EndorsementCredential + .parseWithPossibleHeader(endorsementBytes); + } catch (IllegalArgumentException iae) { + log.error(iae.getMessage()); + throw iae; + } + int certificateHash = endorsementCredential.getCertificateHash(); + EndorsementCredential existingCredential = (EndorsementCredential) certificateRepository + .findByCertificateHash(certificateHash); + if (existingCredential == null) { + log.info("No Endorsement Credential found with hash: " + certificateHash); + return (EndorsementCredential) certificateRepository.save(endorsementCredential); + } else if (existingCredential.isArchived()) { + // if the EK is stored in the DB and it's archived, unarchive. + log.info("Unarchiving credential"); + existingCredential.restore(); + existingCredential.resetCreateTime(); + certificateRepository.save(existingCredential); + } + return existingCredential; + } + + /** + * Parses and stores the PC in the cert manager. If the cert is already present and archived, + * it is unarchived. + * @param certificateRepository the certificate manager used for storage + * @param platformBytes the raw PC bytes used for parsing + * @return the parsed, valid PC, or null if the provided bytes are not a valid EK. + */ + public static PlatformCredential storePlatformCredential( + final CertificateRepository certificateRepository, + final byte[] platformBytes) { + + if (certificateRepository == null) { + throw new IllegalArgumentException("null certificate manager"); + } + + if (platformBytes == null) { + throw new IllegalArgumentException("null platform credential bytes"); + } + + if (platformBytes.length == 0) { + throw new IllegalArgumentException( + "zero-length byte array given for platform credential" + ); + } + + log.info("Parsing Platform Credential of length " + platformBytes.length); + try { + PlatformCredential platformCredential = + PlatformCredential.parseWithPossibleHeader(platformBytes); + if (platformCredential == null) { + return null; + } + PlatformCredential existingCredential = (PlatformCredential) certificateRepository + .findByCertificateHash(platformCredential.getCertificateHash()); + if (existingCredential == null) { + if (platformCredential.getPlatformSerial() != null) { + List certificates = certificateRepository + .byBoardSerialNumber(platformCredential.getPlatformSerial()); + if (!certificates.isEmpty()) { + // found associated certificates + for (PlatformCredential pc : certificates) { + if (pc.isPlatformBase() && platformCredential.isPlatformBase()) { + // found a base in the database associated with + // parsed certificate + log.error(String.format("Base certificate stored" + + " in database with same platform" + + "serial number. (%s)", + platformCredential.getPlatformSerial())); + return null; + } + } + } + } + return (PlatformCredential) certificateRepository.save(platformCredential); + } else if (existingCredential.isArchived()) { + // if the PC is stored in the DB and it's archived, unarchive. + log.info("Unarchiving credential"); + existingCredential.restore(); + certificateRepository.save(existingCredential); + return existingCredential; + } + + return existingCredential; + } catch (DBManagerException dbEx) { + log.error("Error retrieving or saving platform credential", dbEx); + } catch (Exception e) { + log.error("Error parsing platform credential", e); + } + return null; + } +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/helper/IssuedCertificateAttributeHelper.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/helper/IssuedCertificateAttributeHelper.java new file mode 100644 index 00000000..6a375735 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/helper/IssuedCertificateAttributeHelper.java @@ -0,0 +1,201 @@ +package hirs.attestationca.persist.provision.helper; + +import hirs.attestationca.persist.entity.userdefined.certificate.EndorsementCredential; +import hirs.attestationca.persist.entity.userdefined.certificate.PlatformCredential; +import lombok.extern.log4j.Log4j2; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.ASN1OctetString; +import org.bouncycastle.asn1.DEROctetString; +import org.bouncycastle.asn1.DERUTF8String; +import org.bouncycastle.asn1.x500.AttributeTypeAndValue; +import org.bouncycastle.asn1.x500.RDN; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x500.X500NameBuilder; +import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier; +import org.bouncycastle.asn1.x509.ExtendedKeyUsage; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.Extensions; +import org.bouncycastle.asn1.x509.GeneralName; +import org.bouncycastle.asn1.x509.GeneralNames; +import org.bouncycastle.asn1.x509.GeneralNamesBuilder; +import org.bouncycastle.asn1.x509.KeyPurposeId; +import org.bouncycastle.asn1.x509.TBSCertificate; +import org.bouncycastle.asn1.x509.AttributeCertificateInfo; + +import java.io.IOException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.util.Collection; + +/** + * Builds extensions based on Platform and Endorsement credentials to provide in an issued + * certificate. + */ +@Log4j2 +public final class IssuedCertificateAttributeHelper { + + private static final String TPM_ID_LABEL_OID = "2.23.133.2.15"; + + /** + * Object Identifier TCPA at TPM ID Label. + */ + public final static ASN1ObjectIdentifier TCPA_AT_TPM_ID_LABEL = + new ASN1ObjectIdentifier(TPM_ID_LABEL_OID); + /** + * The extended key usage extension. + */ + public final static Extension EXTENDED_KEY_USAGE_EXTENSION; + private final static ASN1ObjectIdentifier TCG_KP_AIK_CERTIFICATE_ATTRIBUTE = + new ASN1ObjectIdentifier("2.23.133.8.3"); + + static { + // Generates an extension that identifies a cert as an AIK cert + Extension extension = null; + try { + extension = new Extension(Extension.extendedKeyUsage, true, + new ExtendedKeyUsage(new KeyPurposeId[] { + KeyPurposeId.getInstance(TCG_KP_AIK_CERTIFICATE_ATTRIBUTE)}).getEncoded()); + } catch (IOException e) { +// log.error("Error generating extended key usage extension"); + } + EXTENDED_KEY_USAGE_EXTENSION = extension; + } + + private IssuedCertificateAttributeHelper() { + // do not construct publicly + } + + /** + * This method builds the AKI extension that will be stored in the generated + * Attestation Issued Certificate. + * @param endorsementCredential EK object to pull AKI from. + * @return the AKI extension. + * @throws IOException on bad get instance for AKI. + */ + public static Extension buildAuthorityKeyIdentifier( + final EndorsementCredential endorsementCredential) throws IOException { + if (endorsementCredential == null || endorsementCredential.getX509Certificate() == null) { + return null; + } + byte[] extValue = endorsementCredential.getX509Certificate() + .getExtensionValue(Extension.authorityKeyIdentifier.getId()); + + if (extValue == null) { + return null; + } + + byte[] authExtension = ASN1OctetString.getInstance(extValue).getOctets(); + AuthorityKeyIdentifier aki = AuthorityKeyIdentifier.getInstance(authExtension); + + return new Extension(Extension.authorityKeyIdentifier, true, aki.getEncoded()); + } + + /** + * Builds the subject alternative name based on the supplied certificates. + * @param endorsementCredential the endorsement credential + * @param platformCredentials the platform credentials + * @param hostName the host name + * @return the subject alternative name extension + * @throws IOException an IO exception occurs building the extension + * @throws IllegalArgumentException if the host name is null + */ + public static Extension buildSubjectAlternativeNameFromCerts( + final EndorsementCredential endorsementCredential, + final Collection platformCredentials, final String hostName) + throws IOException, IllegalArgumentException { + + if (StringUtils.isEmpty(hostName)) { + log.error("null host name"); + throw new IllegalArgumentException("must provide host name"); + } + + // assemble AIK cert SAN, using info from EC and PC + X500NameBuilder nameBuilder = new X500NameBuilder(); + populateEndorsementCredentialAttributes(endorsementCredential, nameBuilder); + if (platformCredentials != null) { + for (PlatformCredential platformCredential : platformCredentials) { + populatePlatformCredentialAttributes(platformCredential, nameBuilder); + } + } + + // add the OID for the TCG-required TPM ID label + DERUTF8String idLabel = new DERUTF8String(hostName); + nameBuilder.addRDN(new AttributeTypeAndValue(TCPA_AT_TPM_ID_LABEL, idLabel)); + + // put everything into the SAN, usable by the certificate builder + GeneralNamesBuilder genNamesBuilder = new GeneralNamesBuilder(); + genNamesBuilder.addName(new GeneralName(nameBuilder.build())); + DEROctetString sanContent = + new DEROctetString(genNamesBuilder.build().getEncoded()); + Extension subjectAlternativeName = new Extension(Extension.subjectAlternativeName, + true, sanContent); + + return subjectAlternativeName; + } + + private static void populatePlatformCredentialAttributes( + final PlatformCredential platformCredential, + final X500NameBuilder nameBuilder) throws IOException { + if (platformCredential == null) { + return; + } + + final RDN[] rdns; + try { + log.debug("Applying platform credential attributes to SAN"); + AttributeCertificateInfo platformCredentialAttributeHolders = + platformCredential.getAttributeCertificate().getAcinfo(); + rdns = ((X500Name) GeneralNames.fromExtensions( + platformCredentialAttributeHolders.getExtensions(), + Extension.subjectAlternativeName).getNames()[0].getName()).getRDNs(); + } catch (IllegalArgumentException iaEx) { + log.error("Unable to extract attributes from platform credential", iaEx); + return; + } + + populateRdnAttributesInNameBuilder(nameBuilder, rdns); + } + + private static void populateEndorsementCredentialAttributes( + final EndorsementCredential endorsementCredential, final X500NameBuilder nameBuilder) { + if (endorsementCredential == null) { + return; + } + + final RDN[] rdns; + try { + log.debug("Applying endorsement credential attributes to SAN"); + X509Certificate endorsementX509 = endorsementCredential.getX509Certificate(); + TBSCertificate tbsCertificate = TBSCertificate.getInstance( + endorsementX509.getTBSCertificate()); + Extensions extensions = tbsCertificate.getExtensions(); + GeneralNames names = GeneralNames.fromExtensions(extensions, + Extension.subjectAlternativeName); + if (names != null) { + X500Name x500 = (X500Name) names.getNames()[0].getName(); + rdns = x500.getRDNs(); + populateRdnAttributesInNameBuilder(nameBuilder, rdns); + } else { + log.error("No RDNs in endorsement credential attributes"); + return; + } + } catch (CertificateEncodingException e) { + log.error("Certificate encoding exception", e); + return; + } catch (IOException e) { + log.error("Error creating x509 cert from endorsement credential", e); + return; + } + + } + + private static void populateRdnAttributesInNameBuilder(final X500NameBuilder nameBuilder, + final RDN[] rdns) { + for (final RDN rdn : rdns) { + nameBuilder.addRDN(rdn.getTypesAndValues()[0]); + } + } +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/CertificateServiceImpl.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/CertificateServiceImpl.java deleted file mode 100644 index 1288dc78..00000000 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/CertificateServiceImpl.java +++ /dev/null @@ -1,91 +0,0 @@ -package hirs.attestationca.persist.service; - -import hirs.attestationca.persist.DBManagerException; -import hirs.attestationca.persist.entity.ArchivableEntity; -import hirs.attestationca.persist.entity.manager.CertificateRepository; -import hirs.attestationca.persist.entity.userdefined.Certificate; -import hirs.attestationca.persist.service.selector.CertificateSelector; -import lombok.NoArgsConstructor; -import lombok.extern.log4j.Log4j2; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.util.Set; -import java.util.UUID; - -@Log4j2 -@NoArgsConstructor -@Service -public class CertificateServiceImpl extends DefaultDbService { - -// @PersistenceContext // I'll need this if I want to make custom native calls -// private EntityManager entityManager; - - @Autowired - private CertificateRepository certificateRepository; - - /** - * Default Constructor. - */ - public CertificateServiceImpl(final Class clazz) { - super(clazz); - this.defineRepository(certificateRepository); - } - - /** - * This method does not need to be used directly as it is used by {@link CertificateSelector}'s - * get* methods. Regardless, it may be used to retrieve certificates by other code in this - * package, given a configured CertificateSelector. - * - * Example: - * - *

-     * {@code
-     * CertificateSelector certSelector =
-     *      new CertificateSelector(Certificate.Type.CERTIFICATE_AUTHORITY)
-     *      .byIssuer("CN=Some certain issuer");
-     *
-     * Set certificates = certificateManager.get(certSelector);}
-     * 
- * - * @param the type of certificate that will be retrieved - * @param certificateSelector a configured {@link CertificateSelector} to use for querying - * @return the resulting set of Certificates, possibly empty - */ - @SuppressWarnings("unchecked") - public Set get(final CertificateSelector certificateSelector) { -// return new HashSet<>( -// (List) getWithCriteria( -// certificateSelector.getCertificateClass(), -// Collections.singleton(certificateSelector.getCriterion()) -// ) -// ); - return null; - } - - - /** - * Archives the named object and updates it in the database. - * - * @param id UUID of the object to archive - * @return true if the object was successfully found and archived, false if the object was not - * found - * @throws hirs.attestationca.persist.DBManagerException if the object is not an instance of ArchivableEntity - */ - public final boolean archive(final UUID id) throws DBManagerException { - log.debug("archiving object: {}", id); - if (id == null) { - log.debug("null id argument"); - return false; - } - - T target = get(id); - if (target == null) { - return false; - } - - ((ArchivableEntity) target).archive(); - this.certificateRepository.save(target); - return true; - } -} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/DefaultDbService.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/DefaultDbService.java deleted file mode 100644 index a90799fd..00000000 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/DefaultDbService.java +++ /dev/null @@ -1,191 +0,0 @@ -package hirs.attestationca.persist.service; - -import hirs.attestationca.persist.DBManagerException; -import hirs.attestationca.persist.entity.AbstractEntity; -import jakarta.persistence.EntityManager; -import jakarta.persistence.PersistenceContext; -import lombok.NoArgsConstructor; -import lombok.extern.log4j.Log4j2; -import org.hibernate.StaleObjectStateException; -import org.hibernate.exception.LockAcquisitionException; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.retry.RetryCallback; -import org.springframework.retry.RetryContext; -import org.springframework.retry.RetryListener; -import org.springframework.retry.backoff.FixedBackOffPolicy; -import org.springframework.retry.policy.SimpleRetryPolicy; -import org.springframework.retry.support.RetryTemplate; -import org.springframework.stereotype.Service; - -import java.io.Serializable; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -@Log4j2 -@Service -@NoArgsConstructor -public class DefaultDbService { - /** - * The default maximum number of retries to attempt a database transaction. - */ - public static final int DEFAULT_MAX_RETRY_ATTEMPTS = 10; - /* - * The default number of milliseconds to wait before retrying a database transaction. - */ - private static final long DEFAULT_RETRY_WAIT_TIME_MS = 3000; - private static final int MAX_CLASS_CACHE_ENTRIES = 500; - - private Class clazz; - @PersistenceContext - private EntityManager entityManager; - private JpaRepository repository; - // structure for retrying methods in the database - private RetryTemplate retryTemplate; - - /** - * Creates a new DefaultDbService. - * - * @param clazz Class to search for when doing Hibernate queries, - * unfortunately class type of T cannot be determined using only T - */ - public DefaultDbService(final Class clazz) { - setRetryTemplate(); - } - - public void defineRepository(final JpaRepository repository) { - this.repository = repository; - } - - public List listAll() { - return this.repository.findAll(); - } - - public void save(final T entity) { - this.repository.save(entity); - } - - public void delete(final T entity) { - this.repository.delete(entity); - } - - public void delete(final UUID id) { - this.repository.deleteById(id); - } - - /** - * Set the parameters used to retry database transactions. The retry template will - * retry transactions that throw a LockAcquisitionException or StaleObjectStateException. - */ - public final void setRetryTemplate() { - Map, Boolean> exceptionsToRetry = new HashMap<>(); - exceptionsToRetry.put(LockAcquisitionException.class, true); - exceptionsToRetry.put(StaleObjectStateException.class, true); - - SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy( - DEFAULT_MAX_RETRY_ATTEMPTS, - exceptionsToRetry, - true, - false - ); - - FixedBackOffPolicy backoffPolicy = new FixedBackOffPolicy(); - backoffPolicy.setBackOffPeriod(DEFAULT_RETRY_WAIT_TIME_MS); - this.retryTemplate = new RetryTemplate(); - this.retryTemplate.setRetryPolicy(retryPolicy); - this.retryTemplate.setBackOffPolicy(backoffPolicy); - } - - /** - * Registers a retry listener to be notified of retry activity. - * @param retryListener the retry listener - */ - public void addRetryListener(final RetryListener retryListener) { - retryTemplate.registerListener(retryListener); - } - - /** - * Retrieves the Object from the database. This searches the - * database for an entry whose name matches name. It then - * reconstructs the Object from the database entry. - * - * @param name name of the object - * @return object if found, otherwise null. - * @throws DBManagerException if unable to search the database or recreate - * the Object - */ - public final T get(final String name) throws DBManagerException { - return retryTemplate.execute(new RetryCallback() { - @Override - public T doWithRetry(final RetryContext context) throws DBManagerException { - return doGet(name); - } - }); - } - - /** - * Retrieves the Object from the database. This searches the - * database for an entry whose id matches id. It then - * reconstructs the Object from the database entry. - * - * @param id id of the object - * @return object if found, otherwise null. - * @throws DBManagerException if unable to search the database or recreate - * the Object - */ - public final T get(final Serializable id) throws DBManagerException { - return retryTemplate.execute(new RetryCallback() { - @Override - public T doWithRetry(final RetryContext context) throws DBManagerException { - return doGet(id); - } - }); - } - - /** - * Retrieves the Object from the database. This searches the - * database for an entry whose name matches name. It then - * reconstructs the Object from the database entry. - * - * @param name name of the object - * @return object if found, otherwise null. - * @throws DBManagerException if unable to search the database or recreate - * the Object - */ - protected T doGet(final String name) throws DBManagerException { - log.debug("getting object: {}", name); - if (name == null) { - log.debug("null name argument"); - return null; - } - - Object entity = entityManager.find(clazz, name); - entityManager.detach(entity); - - return clazz.cast(entity); - } - - /** - * Retrieves the Object from the database. This searches the - * database for an entry whose id matches id. It then - * reconstructs the Object from the database entry. - * - * @param id id of the object - * @return object if found, otherwise null. - * @throws DBManagerException if unable to search the database or recreate - * the Object - */ - protected T doGet(final Serializable id) throws DBManagerException { - log.debug("getting object: {}", id); - if (id == null) { - log.debug("null id argument"); - return null; - } - - Object entity = entityManager.find(clazz, id); - entityManager.detach(entity); - - return clazz.cast(entity); - } -} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/DeviceServiceImpl.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/DeviceServiceImpl.java deleted file mode 100644 index ecd2df77..00000000 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/DeviceServiceImpl.java +++ /dev/null @@ -1,32 +0,0 @@ -package hirs.attestationca.persist.service; - -import hirs.attestationca.persist.entity.manager.DeviceRepository; -import hirs.attestationca.persist.entity.userdefined.Device; -import jakarta.persistence.EntityManager; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.util.List; - -/** - * https://github.com/darrachequesne/spring-data-jpa-datatables - */ -@Service -public class DeviceServiceImpl extends DefaultDbService { - - @Autowired - private EntityManager entityManager; - @Autowired - private DeviceRepository deviceRepository; - - - public void saveDevice(Device device) { - this.deviceRepository.save(device); - } - - public void saveDevices(List devices) { - for (Device device : devices) { - this.deviceRepository.save(device); - } - } -} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/FilesStorageService.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/FilesStorageService.java deleted file mode 100644 index 6d54f7ff..00000000 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/FilesStorageService.java +++ /dev/null @@ -1,20 +0,0 @@ -package hirs.attestationca.persist.service; - -import org.springframework.web.multipart.MultipartFile; - -import java.nio.file.Path; -import java.util.stream.Stream; - -public interface FilesStorageService { - public void init(); - - public void save(MultipartFile file); - - public Path load(String filename); - - public boolean delete(String filename); - - public void deleteAll(); - - public Stream loadAll(); -} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/FilesStorageServiceImpl.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/FilesStorageServiceImpl.java deleted file mode 100644 index d9ce0e3d..00000000 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/FilesStorageServiceImpl.java +++ /dev/null @@ -1,88 +0,0 @@ -package hirs.attestationca.persist.service; - -import hirs.attestationca.persist.StorageProperties; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.web.multipart.MultipartFile; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.FileAlreadyExistsException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; -import java.util.stream.Stream; - -@Service -public class FilesStorageServiceImpl implements FilesStorageService { - - private final Path rootLocation; - - @Autowired - public FilesStorageServiceImpl(StorageProperties properties) { - this.rootLocation = Paths.get(properties.getLocation()); - } - - @Override - public void init() { - try { - Files.createDirectories(rootLocation); - } catch (IOException e) { - throw new RuntimeException("Could not initialize folder for upload!"); - } - } - - @Override - public void save(MultipartFile file) { - try { - if (file.isEmpty()) { - return ; - } - Path destinationFile = this.rootLocation.resolve( - Paths.get(file.getOriginalFilename())) - .normalize().toAbsolutePath(); - if (!destinationFile.getParent().equals(this.rootLocation.toAbsolutePath())) { - // This is a security check - return ; - } - try (InputStream inputStream = file.getInputStream()) { - Files.copy(inputStream, destinationFile, - StandardCopyOption.REPLACE_EXISTING); - } - } catch (Exception e) { - if (e instanceof FileAlreadyExistsException) { - throw new RuntimeException("A file of that name already exists."); - } - - throw new RuntimeException(e.getMessage()); - } - } - - @Override - public Path load(String filename) { - return rootLocation.resolve(filename); - } - - @Override - public boolean delete(String filename) { - return false; - } - - @Override - public void deleteAll() { - - } - - @Override - public Stream loadAll() { - try { - return Files.walk(this.rootLocation, 1) - .filter(path -> !path.equals(this.rootLocation)) - .map(this.rootLocation::relativize); - } - catch (IOException e) { - return null; - } - } -} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/PolicyServiceImpl.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/PolicyServiceImpl.java deleted file mode 100644 index 47cc45ea..00000000 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/PolicyServiceImpl.java +++ /dev/null @@ -1,25 +0,0 @@ -package hirs.attestationca.persist.service; - -import hirs.attestationca.persist.entity.manager.PolicyRepository; -import hirs.attestationca.persist.entity.userdefined.PolicySettings; -import jakarta.persistence.EntityManager; -import org.springframework.beans.factory.annotation.Autowired; - -//@Service -public class PolicyServiceImpl extends DefaultDbService { - - @Autowired - private EntityManager entityManager; - - @Autowired - private PolicyRepository repository; - - public void saveSettings(PolicySettings settings) { - repository.save(settings); - } - - -// public Policy getDefaultPolicy(Appraiser appraiser) { -// return repository.findByAppraiser(appraiser); -// } -} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/ReferenceDigestValueServiceImpl.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/ReferenceDigestValueServiceImpl.java deleted file mode 100644 index cd44bd8e..00000000 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/ReferenceDigestValueServiceImpl.java +++ /dev/null @@ -1,21 +0,0 @@ -package hirs.attestationca.persist.service; - -import hirs.attestationca.persist.entity.manager.ReferenceDigestValueRepository; -import hirs.attestationca.persist.entity.userdefined.rim.ReferenceDigestValue; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.util.LinkedList; -import java.util.List; -import java.util.UUID; - -@Service -public class ReferenceDigestValueServiceImpl extends DefaultDbService { - - @Autowired - private ReferenceDigestValueRepository repository; - - public List getValuesByRimId(final UUID baseId) { - return new LinkedList<>(); - } -} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/ReferenceManifestServiceImpl.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/ReferenceManifestServiceImpl.java deleted file mode 100644 index 11592bfe..00000000 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/ReferenceManifestServiceImpl.java +++ /dev/null @@ -1,116 +0,0 @@ -package hirs.attestationca.persist.service; - -import hirs.attestationca.persist.CriteriaModifier; -import hirs.attestationca.persist.DBManagerException; -import hirs.attestationca.persist.FilteredRecordsList; -import hirs.attestationca.persist.OrderedListQuerier; -import hirs.attestationca.persist.entity.manager.ReferenceManifestRepository; -import hirs.attestationca.persist.entity.userdefined.ReferenceManifest; -import hirs.attestationca.persist.service.selector.ReferenceManifestSelector; -import jakarta.persistence.EntityManager; -import lombok.extern.log4j.Log4j2; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.xml.sax.SAXException; - -import javax.xml.transform.stream.StreamSource; -import javax.xml.validation.Schema; -import javax.xml.validation.SchemaFactory; -import java.io.IOException; -import java.io.InputStream; -import java.util.List; -import java.util.Map; - -@Log4j2 -@Service -public class ReferenceManifestServiceImpl extends DefaultDbService implements OrderedListQuerier { - - /** - * The variable that establishes a schema factory for xml processing. - */ - public static final SchemaFactory SCHEMA_FACTORY - = SchemaFactory.newInstance(ReferenceManifest.SCHEMA_LANGUAGE); - - @Autowired - private EntityManager entityManager; - - @Autowired - private ReferenceManifestRepository repository; - - private static Schema schema; - - public ReferenceManifestServiceImpl() { - getSchemaObject(); - } - - /** - * This method sets the xml schema for processing RIMs. - * - * @return the schema - */ - public static final Schema getSchemaObject() { - if (schema == null) { - InputStream is = null; - try { - is = ReferenceManifest.class - .getClassLoader() - .getResourceAsStream(ReferenceManifest.SCHEMA_URL); - schema = SCHEMA_FACTORY.newSchema(new StreamSource(is)); - } catch (SAXException saxEx) { - log.error(String.format("Error setting schema for validation!%n%s", - saxEx.getMessage())); - } finally { - if (is != null) { - try { - is.close(); - } catch (IOException ioEx) { - log.error(String.format("Error closing input stream%n%s", - ioEx.getMessage())); - } - } else { - log.error("Input stream variable is null"); - } - } - } - return schema; - } - - /** - * This method does not need to be used directly as it is used by - * {@link ReferenceManifestSelector}'s get* methods. Regardless, it may be - * used to retrieve ReferenceManifest by other code in this package, given a - * configured ReferenceManifestSelector. - * - * {@link ReferenceManifestSelector} to use for querying - * @return the resulting set of ReferenceManifest, possibly empty - */ - @SuppressWarnings("unchecked") - public List get( - final ReferenceManifestSelector referenceManifestSelector) { - log.info("Getting the full set of Reference Manifest files."); -// return new HashSet<>( -// (List) getWithCriteria( -// referenceManifestSelector.getReferenceManifestClass(), -// Collections.singleton(referenceManifestSelector.getCriterion()) -// ) -// ); - return (List) repository.findAll(); - } - - @Override - public FilteredRecordsList getOrderedList(Class clazz, - String columnToOrder, boolean ascending, int firstResult, - int maxResults, String search, - Map searchableColumns) throws DBManagerException { - return new FilteredRecordsList(); - } - - @Override - public FilteredRecordsList getOrderedList(Class clazz, - String columnToOrder, boolean ascending, - int firstResult, int maxResults, String search, - Map searchableColumns, - CriteriaModifier criteriaModifier) throws DBManagerException { - return new FilteredRecordsList<>(); - } -} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/SupplyChainValidationService.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/SupplyChainValidationService.java new file mode 100644 index 00000000..55e01b09 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/SupplyChainValidationService.java @@ -0,0 +1,42 @@ +package hirs.attestationca.persist.service; + +import hirs.attestationca.persist.entity.userdefined.Device; +import hirs.attestationca.persist.entity.userdefined.SupplyChainValidationSummary; +import hirs.attestationca.persist.entity.userdefined.certificate.EndorsementCredential; +import hirs.attestationca.persist.entity.userdefined.certificate.PlatformCredential; + +import java.util.List; + +/** + * Interface defining a component that will perform supply chain validations, which yields a + * {@link SupplyChainValidationSummary}. + */ +public interface SupplyChainValidationService { + /** + * The "main" method of supply chain validation. Takes the credentials from an identity + * request and validates the supply chain in accordance to the current supply chain + * policy. + * + * @param ec The endorsement credential from the identity request. + * @param pc The set of platform credentials from the identity request. + * @param device The device to be validated. + * @return True if validation is successful, false otherwise. + */ + SupplyChainValidationSummary validateSupplyChain(EndorsementCredential ec, + List pc, + Device device); + + /** + * A supplemental method that handles validating just the quote post main validation. + * + * @param device the associated device. + * @return True if validation is successful, false otherwise. + */ + SupplyChainValidationSummary validateQuote(Device device); + + /** + * Allows other service access to the policy information. + * @return supply chain policy + */ +// SupplyChainPolicy getPolicy(); +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/SupplyChainValidationServiceImpl.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/SupplyChainValidationServiceImpl.java index d9785bb5..02c7c7ab 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/SupplyChainValidationServiceImpl.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/SupplyChainValidationServiceImpl.java @@ -1,41 +1,238 @@ package hirs.attestationca.persist.service; +import hirs.attestationca.persist.entity.ArchivableEntity; +import hirs.attestationca.persist.DBManagerException; +import hirs.attestationca.persist.entity.manager.CACredentialRepository; import hirs.attestationca.persist.entity.manager.CertificateRepository; -import hirs.attestationca.persist.entity.manager.SupplyChainValidationRepository; +import hirs.attestationca.persist.entity.manager.ComponentResultRepository; +import hirs.attestationca.persist.entity.manager.PolicyRepository; +import hirs.attestationca.persist.entity.manager.ReferenceDigestValueRepository; +import hirs.attestationca.persist.entity.manager.ReferenceManifestRepository; +import hirs.attestationca.persist.entity.manager.SupplyChainValidationSummaryRepository; import hirs.attestationca.persist.entity.userdefined.Certificate; +import hirs.attestationca.persist.entity.userdefined.Device; +import hirs.attestationca.persist.entity.userdefined.PolicySettings; import hirs.attestationca.persist.entity.userdefined.SupplyChainValidation; +import hirs.attestationca.persist.entity.userdefined.SupplyChainValidationSummary; import hirs.attestationca.persist.entity.userdefined.certificate.CertificateAuthorityCredential; +import hirs.attestationca.persist.entity.userdefined.certificate.EndorsementCredential; import hirs.attestationca.persist.entity.userdefined.certificate.PlatformCredential; +import hirs.attestationca.persist.entity.userdefined.record.TPMMeasurementRecord; +import hirs.attestationca.persist.entity.userdefined.rim.EventLogMeasurements; +import hirs.attestationca.persist.entity.userdefined.rim.SupportReferenceManifest; +import hirs.attestationca.persist.enums.AppraisalStatus; +import hirs.attestationca.persist.validation.CredentialValidator; +import hirs.attestationca.persist.validation.PcrValidator; +import hirs.attestationca.persist.validation.SupplyChainCredentialValidator; import hirs.utils.BouncyCastleUtils; import lombok.extern.log4j.Log4j2; import org.bouncycastle.util.encoders.Hex; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; import java.io.IOException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; +import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; +import org.apache.logging.log4j.Level; + +import static hirs.attestationca.persist.enums.AppraisalStatus.Status.FAIL; +import static hirs.attestationca.persist.enums.AppraisalStatus.Status.PASS; @Log4j2 -//@Service -public class SupplyChainValidationServiceImpl extends DefaultDbService { +@Service +public class SupplyChainValidationServiceImpl implements SupplyChainValidationService { - @Autowired - SupplyChainValidationRepository repository; - @Autowired + private CACredentialRepository caCredentialRepository; + private PolicyRepository policyRepository; + private ReferenceManifestRepository referenceManifestRepository; + private ReferenceDigestValueRepository referenceDigestValueRepository; + private ComponentResultRepository componentResultRepository; private CertificateRepository certificateRepository; + private CredentialValidator supplyChainCredentialValidator; + private SupplyChainValidationSummaryRepository supplyChainValidationSummaryRepository; + /** + * Constructor to set just the CertificateRepository, so that cert chain validating + * methods can be called from outside classes. + * + * @param certificateRepository the cert repository + */ public SupplyChainValidationServiceImpl(final CertificateRepository certificateRepository) { - super(); this.certificateRepository = certificateRepository; } + /** + * Constructor. + * + * @param caCredentialRepository ca credential repository + * @param policyRepository the policy manager + * @param certificateRepository the cert manager + * @param componentResultRepository the comp result manager + * @param referenceManifestRepository the RIM manager + * @param supplyChainValidationSummaryRepository the summary manager + * @param supplyChainCredentialValidator the credential validator + * @param referenceDigestValueRepository the even manager + */ + @Autowired + @SuppressWarnings("ParameterNumberCheck") + public SupplyChainValidationServiceImpl( + final CACredentialRepository caCredentialRepository, + final PolicyRepository policyRepository, + final CertificateRepository certificateRepository, + final ComponentResultRepository componentResultRepository, + final ReferenceManifestRepository referenceManifestRepository, + final SupplyChainValidationSummaryRepository supplyChainValidationSummaryRepository, + final CredentialValidator supplyChainCredentialValidator, + final ReferenceDigestValueRepository referenceDigestValueRepository) { + this.caCredentialRepository = caCredentialRepository; + this.policyRepository = policyRepository; + this.certificateRepository = certificateRepository; + this.componentResultRepository = componentResultRepository; + this.referenceManifestRepository = referenceManifestRepository; + this.supplyChainValidationSummaryRepository = supplyChainValidationSummaryRepository; + this.supplyChainCredentialValidator = supplyChainCredentialValidator; + this.referenceDigestValueRepository = referenceDigestValueRepository; + } + + @Override + public SupplyChainValidationSummary validateSupplyChain(final EndorsementCredential ec, + final List pc, + final Device device) { + return null; + } + + /** + * A supplemental method that handles validating just the quote post main validation. + * + * @param device the associated device. + * @return True if validation is successful, false otherwise. + */ + @Override + public SupplyChainValidationSummary validateQuote(final Device device) { + SupplyChainValidation quoteScv = null; + SupplyChainValidationSummary summary = null; + Level level = Level.ERROR; + AppraisalStatus fwStatus = new AppraisalStatus(FAIL, + "Unknown exception caught during quote validation."); + SupportReferenceManifest sRim = null; + EventLogMeasurements eventLog = null; + + // check if the policy is enabled + if (getPolicySettings().isFirmwareValidationEnabled()) { + String[] baseline = new String[Integer.SIZE]; + String deviceName = device.getDeviceInfo() + .getNetworkInfo().getHostname(); + + try { + List supportRims = referenceManifestRepository.getSupportByManufacturerModel( + device.getDeviceInfo().getHardwareInfo().getManufacturer(), + device.getDeviceInfo().getHardwareInfo().getProductName()); + for (SupportReferenceManifest support : supportRims) { + if (support.isBaseSupport()) { + sRim = support; + } + } + eventLog = (EventLogMeasurements) referenceManifestRepository + .findByHexDecHash(sRim.getEventLogHash()); + + if (sRim == null) { + fwStatus = new AppraisalStatus(FAIL, + String.format("Firmware Quote validation failed: " + + "No associated Support RIM file " + + "could be found for %s", + deviceName)); + } else if (eventLog == null) { + fwStatus = new AppraisalStatus(FAIL, + String.format("Firmware Quote validation failed: " + + "No associated Client Log file " + + "could be found for %s", + deviceName)); + } else { + baseline = sRim.getExpectedPCRList(); + String[] storedPcrs = eventLog.getExpectedPCRList(); + PcrValidator pcrValidator = new PcrValidator(baseline); + // grab the quote + byte[] hash = device.getDeviceInfo().getTpmInfo().getTpmQuoteHash(); + if (pcrValidator.validateQuote(hash, storedPcrs, getPolicySettings())) { + level = Level.INFO; + fwStatus = new AppraisalStatus(PASS, + SupplyChainCredentialValidator.FIRMWARE_VALID); + fwStatus.setMessage("Firmware validation of TPM Quote successful."); + } else { + fwStatus.setMessage("Firmware validation of TPM Quote failed." + + "\nPCR hash and Quote hash do not match."); + } + eventLog.setOverallValidationResult(fwStatus.getAppStatus()); + this.referenceManifestRepository.save(eventLog); + } + } catch (Exception ex) { + log.error(ex); + } + + quoteScv = buildValidationRecord(SupplyChainValidation + .ValidationType.FIRMWARE, + fwStatus.getAppStatus(), fwStatus.getMessage(), eventLog, level); + + // Generate validation summary, save it, and return it. + List validations = new ArrayList<>(); + SupplyChainValidationSummary previous + = this.supplyChainValidationSummaryRepository.findByDevice(deviceName); + for (SupplyChainValidation scv : previous.getValidations()) { + if (scv.getValidationType() != SupplyChainValidation.ValidationType.FIRMWARE) { + validations.add(buildValidationRecord(scv.getValidationType(), + scv.getValidationResult(), scv.getMessage(), + scv.getCertificatesUsed().get(0), Level.INFO)); + } + } + validations.add(quoteScv); + previous.archive(); + supplyChainValidationSummaryRepository.save(previous); + summary = new SupplyChainValidationSummary(device, validations); + + // try removing the supply chain validation as well and resaving that + try { + supplyChainValidationSummaryRepository.save(summary); + } catch (DBManagerException dbEx) { + log.error("Failed to save Supply Chain Summary", dbEx); + } + } + + return summary; + } + + /** + * Creates a supply chain validation record and logs the validation message + * at the specified log level. + * + * @param validationType the type of validation + * @param result the appraisal status + * @param message the validation message to include in the summary and log + * @param archivableEntity the archivableEntity associated with the + * validation + * @param logLevel the log level + * @return a SupplyChainValidation + */ + private SupplyChainValidation buildValidationRecord( + final SupplyChainValidation.ValidationType validationType, + final AppraisalStatus.Status result, final String message, + final ArchivableEntity archivableEntity, final Level logLevel) { + List aeList = new ArrayList<>(); + if (archivableEntity != null) { + aeList.add(archivableEntity); + } + + log.log(logLevel, message); + return new SupplyChainValidation(validationType, result, aeList, message); + } + /** * This method is used to retrieve the entire CA chain (up to a trusted * self-signed certificate) for the given certificate. This method will look @@ -94,35 +291,32 @@ public class SupplyChainValidationServiceImpl extends DefaultDbService queriedOrganizations = new HashSet<>(previouslyQueriedSubjects); - queriedOrganizations.add(credential.getHolderIssuer()); + queriedOrganizations.add(credential.getIssuer()); HashSet caCreds = new HashSet<>(); for (CertificateAuthorityCredential cred : certAuthsWithMatchingIssuer) { caCreds.add(cred); - if (!BouncyCastleUtils.x500NameCompare(cred.getHolderIssuer(), + if (!BouncyCastleUtils.x500NameCompare(cred.getIssuer(), cred.getSubject())) { caCreds.addAll(getCaChainRec(cred, queriedOrganizations)); } } - return caCreds; } @@ -141,23 +335,43 @@ public class SupplyChainValidationServiceImpl extends DefaultDbService chainCertificates = certificateRepository - .byBoardSerialNumber(platformSerialNumber); + // we need to scroll through the entire list until we find + // a matching hash length + int offset = 1; - for (PlatformCredential pc : chainCertificates) { - if (baseCredential != null && pc.isPlatformBase()) { - multiple = true; - } else if (pc.isPlatformBase()) { - baseCredential = pc; + for (int i = 0; i < pcrSet.length; i++) { + if (pcrSet[i].contains("sha")) { + // entered a new set, check size + if (pcrSet[i + offset].split(":")[1].trim().length() + == algorithmLength) { + // found the matching set + for (int j = 0; j <= TPMMeasurementRecord.MAX_PCR_ID; j++) { + storedPcrs[j] = pcrSet[++i].split(":")[1].trim(); + } + break; } } } - return multiple; + return storedPcrs; + } + + /** + * Helper function to get a fresh load of the default policy from the DB. + * + * @return The default Supply Chain Policy + */ + private PolicySettings getPolicySettings() { + PolicySettings defaultSettings = this.policyRepository.findByName("Default"); + + if (defaultSettings == null) { + defaultSettings = new PolicySettings("Default", "Settings are configured for no validation flags set."); + } + return defaultSettings; } } diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/selector/CertificateSelector.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/selector/CertificateSelector.java index bc8f3c9a..5f28222c 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/selector/CertificateSelector.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/selector/CertificateSelector.java @@ -2,7 +2,6 @@ package hirs.attestationca.persist.service.selector; import com.google.common.base.Preconditions; import hirs.attestationca.persist.entity.userdefined.Certificate; -import hirs.attestationca.persist.service.CertificateServiceImpl; import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.CriteriaQuery; import jakarta.persistence.criteria.Predicate; @@ -17,17 +16,13 @@ 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.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; -import java.util.Set; import java.util.UUID; /** * This class is used to select one or many certificates in conjunction - * with a {@link CertificateServiceImpl}. To make use of this object, + * with a {@link }. To make use of this object, * use (some CertificateImpl).select(CertificateManager). * * This class loosely follows the builder pattern. It is instantiated with @@ -76,47 +71,36 @@ import java.util.UUID; */ public abstract class CertificateSelector { - private final CertificateServiceImpl certificateService; private final Class certificateClass; private final Map fieldValueSelections; private boolean excludeArchivedCertificates; /** - * Construct a new CertificateSelector that will use the given {@link CertificateServiceImpl} to + * Construct a new CertificateSelector that will use the given {@link } to * retrieve certificates of the given type. * - * @param certificateService the certificate manager to be used to retrieve certificates * @param certificateClass the class of certificate to be retrieved */ public CertificateSelector( - final CertificateServiceImpl certificateService, final Class certificateClass) { - this(certificateService, certificateClass, true); + this(certificateClass, true); } /** - * Construct a new CertificateSelector that will use the given {@link CertificateServiceImpl } to + * Construct a new CertificateSelector that will use the given {@link } to * retrieve certificates of the given type. * - * @param certificateService the certificate manager to be used to retrieve certificates * @param certificateClass the class of certificate to be retrieved * @param excludeArchivedCertificates true if excluding archived certificates */ public CertificateSelector( - final CertificateServiceImpl certificateService, final Class certificateClass, final boolean excludeArchivedCertificates) { - Preconditions.checkArgument( - certificateService != null, - "certificate manager cannot be null" - ); - Preconditions.checkArgument( certificateClass != null, "type cannot be null" ); - this.certificateService = certificateService; this.certificateClass = certificateClass; this.fieldValueSelections = new HashMap<>(); this.excludeArchivedCertificates = excludeArchivedCertificates; @@ -337,70 +321,6 @@ public abstract class CertificateSelector { fieldValueSelections.put(name, valueToAssign); } - /** - * Retrieve the result set as a single {@link Certificate}. - * This method is best used when selecting on a unique attribute. - * If the result set contains more than one certificate, one is chosen - * arbitrarily and returned. If no matching certificates are found, - * this method returns null. - * - * @return a matching certificate or null if none is found - */ - public T getCertificate() { - Set certs = execute(); - if (certs.size() == 0) { - return null; - } - return certs.iterator().next(); - } - - /** - * Retrieve the result set as a set of {@link Certificate}s. - * This method is best used when selecting on non-unique attributes. - * Certificates are populated into the set in no specific order. - * If no matching certificates are found, the returned Set will be empty. - * - * @return a Set of matching Certificates, possibly empty - */ - public Set getCertificates() { - return Collections.unmodifiableSet(new HashSet<>(execute())); - } - - /** - * Retrieve the result set as a single {@link X509Certificate}. - * This method is best used when selecting on a unique attribute. - * If the result set contains more than one certificate, one is chosen - * arbitrarily and returned. If no matching certificates are found, - * this method returns null. - * - * @return a matching certificate or null if none is found - * @throws IOException if there is a problem reconstructing the X509Certificate - */ - public X509Certificate getX509Certificate() throws IOException { - Certificate cert = getCertificate(); - if (cert == null) { - return null; - } - return cert.getX509Certificate(); - } - - /** - * Retrieve the result set as a set of {@link X509Certificate}s. - * This method is best used when selecting on non-unique attributes. - * Certificates are populated into the set in no specific order. - * If no matching certificates are found, the returned Set will be empty. - * - * @return a Set of matching Certificates, possibly empty - * @throws IOException if there is a problem reconstructing the X509Certificates - */ - public Set getX509Certificates() throws IOException { - Set certs = new HashSet<>(); - for (Certificate cert : getCertificates()) { - certs.add(cert.getX509Certificate()); - } - return Collections.unmodifiableSet(certs); - } - /** * Retrieve the result set populated into a {@link KeyStore}. * Certificates are populated into a JKS-formatted KeyStore, with their aliases @@ -415,9 +335,9 @@ public abstract class CertificateSelector { KeyStore keyStore = KeyStore.getInstance("JKS"); try { keyStore.load(null, "".toCharArray()); - for (Certificate cert : getCertificates()) { - keyStore.setCertificateEntry(cert.getId().toString(), cert.getX509Certificate()); - } +// for (Certificate cert : getCertificates()) { +// keyStore.setCertificateEntry(cert.getId().toString(), cert.getX509Certificate()); +// } } catch (IOException | CertificateException | NoSuchAlgorithmException e) { throw new IOException("Could not create and populate keystore", e); } @@ -456,11 +376,6 @@ public abstract class CertificateSelector { return certificateClass; } - // construct and execute query - private Set execute() { - return certificateService.get(this); - } - /** * Configures the selector to query for archived and unarchived certificates. * @return the selector diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/selector/ReferenceManifestSelector.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/selector/ReferenceManifestSelector.java index 6090fcd9..62442115 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/selector/ReferenceManifestSelector.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/selector/ReferenceManifestSelector.java @@ -3,7 +3,6 @@ package hirs.attestationca.persist.service.selector; import com.google.common.base.Preconditions; import hirs.attestationca.persist.entity.userdefined.Certificate; import hirs.attestationca.persist.entity.userdefined.ReferenceManifest; -import hirs.attestationca.persist.service.ReferenceManifestServiceImpl; import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.CriteriaQuery; import jakarta.persistence.criteria.Predicate; @@ -20,7 +19,7 @@ import java.util.UUID; /** * This class is used to select one or many RIMs in conjunction - * with a {@link ReferenceManifestServiceImpl}. To make use of this object, + * with a manger. To make use of this object, * use (some ReferenceManifest).select(ReferenceManifestManager). * * @param the type of Reference Integrity Manifest that will be retrieved. @@ -44,7 +43,6 @@ public abstract class ReferenceManifestSelector { public static final String RIM_FILENAME_FIELD = "fileName"; private static final String RIM_TYPE_FIELD = "rimType"; - private final ReferenceManifestServiceImpl referenceManifestManager; private final Class referenceTypeClass; private final Map fieldValueSelections; @@ -53,35 +51,25 @@ public abstract class ReferenceManifestSelector { /** * Default Constructor. * - * @param referenceManifestManager the RIM manager to be used to retrieve RIMs * @param referenceTypeClass the type of Reference Manifest to process. */ - public ReferenceManifestSelector(final ReferenceManifestServiceImpl referenceManifestManager, - final Class referenceTypeClass) { - this(referenceManifestManager, referenceTypeClass, true); + public ReferenceManifestSelector(final Class referenceTypeClass) { + this(referenceTypeClass, true); } /** * Standard Constructor for the Selector. * - * @param referenceManifestManager the RIM manager to be used to retrieve RIMs * @param referenceTypeClass the type of Reference Manifest to process. * @param excludeArchivedRims true if excluding archived RIMs */ - public ReferenceManifestSelector(final ReferenceManifestServiceImpl referenceManifestManager, - final Class referenceTypeClass, + public ReferenceManifestSelector(final Class referenceTypeClass, final boolean excludeArchivedRims) { - Preconditions.checkArgument( - referenceManifestManager != null, - "reference manifest manager cannot be null" - ); - Preconditions.checkArgument( referenceTypeClass != null, "type cannot be null" ); - this.referenceManifestManager = referenceManifestManager; this.referenceTypeClass = referenceTypeClass; this.excludeArchivedRims = excludeArchivedRims; this.fieldValueSelections = new HashMap<>(); @@ -153,36 +141,6 @@ public abstract class ReferenceManifestSelector { fieldValueSelections.put(name, valueToAssign); } - /** - * Retrieve the result set as a single - * {@link ReferenceManifest}. This method is best used - * when selecting on a unique attribute. If the result set contains more - * than one RIM, one is chosen arbitrarily and returned. If no matching RIMs - * are found, this method returns null. - * - * @return a matching RIM or null if none is found - */ - public T getRIM() { - List rims = execute(); - if (rims.isEmpty()) { - return null; - } - return rims.iterator().next(); - } - - /** - * Retrieve the result set as a set of - * {@link ReferenceManifest}s. This method is best used - * when selecting on non-unique attributes. ReferenceManifests are populated - * into the set in no specific order. If no matching certificates are found, - * the returned Set will be empty. - * - * @return a Set of matching RIMs, possibly empty - */ - public Set getRIMs() { - return Set.copyOf(execute()); - } - /** * Construct the criterion that can be used to query for rims matching the * configuration of this {@link ReferenceManifestSelector}. @@ -214,12 +172,6 @@ public abstract class ReferenceManifestSelector { return this.referenceTypeClass; } - // construct and execute query - private List execute() { - List results = this.referenceManifestManager.get(this); - return results; - } - /** * Configures the selector to query for archived and unarchived rims. * diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/tpm/PcrComposite.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/tpm/PcrComposite.java new file mode 100644 index 00000000..e6872e29 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/tpm/PcrComposite.java @@ -0,0 +1,119 @@ +package hirs.attestationca.persist.tpm; + +import hirs.attestationca.persist.entity.userdefined.record.TPMMeasurementRecord; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; +import jakarta.persistence.FetchType; +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlElement; +import jakarta.xml.bind.annotation.XmlType; +import lombok.extern.log4j.Log4j2; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Java class for PcrComposite, based on the code auto-generated using xjc + * command line tool. The PcrCompositeType complex type is used to aggregate + * multiple PCR values in a single structure and represents, in XML, the TPM's + * TPM_PCR_COMPOSITE structure returned from a call to Quote TPM PCRs. In the + * original class, the PcrValue was a list of PcrCompositeType.PcrValue objects. + * This was replaced as a list of TPMMeasurementRecords, and the TPMValue class + * was removed. This change was not TCG-compliant, as the auto-generated code + * would produce something like: + *

  + *

+ *     <PcrValue PcrNumber="0">06fl7EXo34MWxuLq9kcXI9la9NA=</ns3:PcrValue>
+ * 
+ *

+ * but using TPMMeasurementRecords result in something like: + *

  + *

+ *     <PcrValue PcrNumber="2">
+ *       <hash>
+ *         <digest>AAECAwQFBgcICQoLDA0ODxAREhM=</digest>
+ *         <algorithm>SHA1</algorithm>
+ *     </hash>
+ *   </PcrValue>
+ * 
+ * + */ +@Log4j2 +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "PcrComposite", + namespace = "http://www.trustedcomputinggroup.org/XML/SCHEMA/" + + "Integrity_Report_v1_0#", propOrder = {"pcrSelection", + "valueSize", "pcrValueList" }) +@Embeddable +public class PcrComposite { + + @XmlElement(name = "PcrSelection", required = true) + @Embedded + private final PcrSelection pcrSelection; + + @XmlElement(name = "PcrValue", required = true) + @ElementCollection(fetch = FetchType.EAGER) + private final List pcrValueList; + + /** + * Default constructor necessary for marshalling/unmarshalling xml. + */ + protected PcrComposite() { + pcrSelection = null; + pcrValueList = new ArrayList<>(); + } + + /** + * Constructor used to create a PcrComposite object. + * + * @param pcrSelection + * {@link PcrSelection } object, identifies which TPM PCRs are + * quoted + * @param pcrValueList + * List of TPMMeasurementRecords representing the PCR values + */ + public PcrComposite(final PcrSelection pcrSelection, + final List pcrValueList) { + if (pcrSelection == null) { + log.error("null pcrSelection value"); + throw new NullPointerException("pcrSelection"); + } + if (pcrValueList == null) { + log.error("null pcrValueList value"); + throw new NullPointerException("pcrValueList"); + } + this.pcrSelection = pcrSelection; + this.pcrValueList = pcrValueList; + } + + + + /** + * Gets the value of the valueSize property, the length in bytes of the + * array of PcrValue complex types. + * + * @return int value representing the valueSize + * + */ + @XmlElement(name = "ValueSize", required = true) + public final int getValueSize() { + int valueSize = 0; + for (TPMMeasurementRecord record : this.pcrValueList) { + valueSize += record.getHash().getDigest().length; + } + return valueSize; + } + + /** + * Gets the list of PCR values, represented as a List of + * TPMMeasurementRecord objects. + * + * @return list of TPMeasurementRecords + */ + public final List getPcrValueList() { + return Collections.unmodifiableList(pcrValueList); + } +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/tpm/PcrInfoShort.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/tpm/PcrInfoShort.java new file mode 100644 index 00000000..a81c87f0 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/tpm/PcrInfoShort.java @@ -0,0 +1,217 @@ +package hirs.attestationca.persist.tpm; + +import hirs.attestationca.persist.entity.userdefined.record.TPMMeasurementRecord; +import hirs.utils.digest.Digest; +import hirs.utils.digest.DigestAlgorithm; +import jakarta.persistence.AttributeOverride; +import jakarta.persistence.AttributeOverrides; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlElement; +import jakarta.xml.bind.annotation.XmlSchemaType; +import jakarta.xml.bind.annotation.XmlType; +import lombok.extern.log4j.Log4j2; +import org.apache.commons.codec.binary.Hex; + +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Iterator; +import java.util.List; + +/** + * Java class for PcrInfoShort complex type, which was modified from code + * auto-generated using the xjc command line tool. This code generates XML that + * matches what is specified for the PcrInfoShortType ComplexType in the + * IntegrityReportManifest_v1_0_1 schema file. + *

+ * The PcrInfoShortType complex type is an XML representation of the TPM's + * TPM_PCR_INFO_SHORT structure. PcrComposite is not part of the TPM + * PCR_INFO_SHORT structure, however CompositeHash is a hash of PcrComposite, + * thus PcrComposite is included to provide the data necessary to compute and + * validate CompositeHash. + */ +@Log4j2 +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "PcrInfoShort", + namespace = "http://www.trustedcomputinggroup.org/XML/SCHEMA/" + + "Integrity_Report_v1_0#", propOrder = {"pcrSelection", + "localityAtRelease", "compositeHash", "pcrComposite" }) +@Embeddable +public class PcrInfoShort { + + @XmlElement(name = "PcrSelection", required = true) + @Embedded + @AttributeOverrides({ + @AttributeOverride( + name = "pcrSelect", + column = @Column(name = "selectionPcrSelect") + ) + }) + private final PcrSelection pcrSelection; + + @XmlElement(name = "LocalityAtRelease") + @XmlSchemaType(name = "unsignedByte") + private final short localityAtRelease; + + @XmlElement(name = "CompositeHash", required = true) + private final byte[] compositeHash; + + @XmlElement(name = "PcrComposite", required = true) + @Embedded + private final PcrComposite pcrComposite; + + /** + * Default constructor necessary for marshalling/unmarshalling xml. + */ + protected PcrInfoShort() { + this.pcrSelection = new PcrSelection(); + this.pcrComposite = new PcrComposite(); + this.localityAtRelease = 0; + this.compositeHash = new byte[0]; + } + + /** + * Constructor used to create a PcrInfoShort object. + * + * @param pcrSelection + * PcrSelection defines which TPM PCRs are used in the TPM Quote. + * @param localityAtRelease + * short value includes locality information to provide the + * requestor a more complete view of the current platform + * configuration + * @param compositeHash + * A hash of PcrComposite + * @param pcrComposite + * A structure containing the actual values of the PCRs quoted. + */ + public PcrInfoShort(final PcrSelection pcrSelection, + final short localityAtRelease, final byte[] compositeHash, + final PcrComposite pcrComposite) { + if (pcrSelection == null) { + log.error("null pcrSelection value"); + throw new NullPointerException("pcrSelection"); + } + if (compositeHash == null) { + log.error("null compositeHash value"); + throw new NullPointerException("compositeHash"); + } + if (pcrComposite == null) { + log.error("null pcrComposite value"); + throw new NullPointerException("pcrComposite"); + } + + this.pcrSelection = pcrSelection; + this.localityAtRelease = localityAtRelease; + this.compositeHash = compositeHash.clone(); + this.pcrComposite = pcrComposite; + } + + /** + * Gets the value of the pcrComposite property, a structure containing the + * actual values of the PCRs quoted. + * + * @return possible object is {@link PcrComposite } + */ + public final PcrComposite getPcrComposite() { + return pcrComposite; + } + + /** + * Calculates the SHA-1 or SHA-256 digest of the PCR values the same way a TPM computes the + * digest contained in the quote. Useful for TPM appraisal and for ensuring the digest of the + * collected PCR values match the digest in the quote. + * + * @return byte array containing the digest + * @throws NoSuchAlgorithmException + * if MessageDigest doesn't recognize "SHA-1" or "SHA-256" + */ + public final byte[] getCalculatedDigest() throws NoSuchAlgorithmException { + if (this.isTpm1()) { + return getCalculatedDigestTpmV1p2(MessageDigest.getInstance("SHA-1")); + } else { + return getCalculatedDigestTpmV2p0(MessageDigest.getInstance("SHA-256")); + } + } + + /** + * Calculates the SHA-1 digest of the PCR values the same way a TPM computes the digest + * contained in the quote. Useful for TPM appraisal and for ensuring the digest of the + * collected PCR values match the digest in the quote. + * + * @param messageDigest message digest algorithm to use + * @return byte array containing the digest + */ + private byte[] getCalculatedDigestTpmV1p2(final MessageDigest messageDigest) { + byte[] computedDigest; + + final int sizeOfInt = 4; + int sizeOfByteBuffer = + this.pcrSelection.getLength() + sizeOfInt + + this.pcrComposite.getValueSize(); + + ByteBuffer byteBuffer = ByteBuffer.allocate(sizeOfByteBuffer); + log.debug("Size of the buffer allocated to hash: {}", sizeOfByteBuffer); + + byteBuffer.put(this.pcrSelection.getValue()); + byteBuffer.putInt(pcrComposite.getValueSize()); + + for (TPMMeasurementRecord record: pcrComposite.getPcrValueList()) { + byteBuffer.put(record.getHash().getDigest()); + } + + log.debug("PCR composite buffer to be hashed: {}", + Hex.encodeHexString(byteBuffer.array())); + computedDigest = messageDigest.digest(byteBuffer.array()); + log.debug("Calculated digest: {}", Hex.encodeHexString(computedDigest)); + + return computedDigest; + } + + /** + * Calculates the digest of the PCR values the same way a TPM computes the digest contained in + * the quote. Useful for TPM appraisal and for ensuring the digest of the collected PCR values + * match the digest in the quote. + * + * @param messageDigest message digest algorithm to use + * @return byte array containing the digest + */ + private byte[] getCalculatedDigestTpmV2p0(final MessageDigest messageDigest) { + int sizeOfByteBuffer = pcrComposite.getValueSize(); + ByteBuffer byteBuffer = ByteBuffer.allocate(sizeOfByteBuffer); + log.debug("Size of the buffer allocated to hash: {}", sizeOfByteBuffer); + Iterator iter = pcrComposite.getPcrValueList().iterator(); + + while (iter.hasNext()) { + TPMMeasurementRecord record = (TPMMeasurementRecord) iter.next(); + byteBuffer.put(record.getHash().getDigest()); + } + + log.debug("PCR composite buffer to be hashed: {}", + Hex.encodeHexString(byteBuffer.array())); + byte[] computedDigest = messageDigest.digest(byteBuffer.array()); + log.debug("Calculated digest: {}", Hex.encodeHexString(computedDigest)); + return computedDigest; + } + + /** + * Determines whether the TPM used to generate this pcr info is version 1.2 or not. + * + * @return whether the TPM used to generate this pcr info is version 1.2 or not + */ + public boolean isTpm1() { + // need to get an individual PCR and measure length to determine SHA1 v SHA 256 + List pcrs = this.getPcrComposite().getPcrValueList(); + if (pcrs.size() == 0) { + // it's the case of an empty pcrmask, so it doesn't matter + return false; + } + + Digest hash = pcrs.get(0).getHash(); + // check if the hash algorithm is SHA 1, if so it's TPM 1.2, if not it's TPM 2.0 + return hash.getAlgorithm() == DigestAlgorithm.SHA1; + } +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/tpm/PcrSelection.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/tpm/PcrSelection.java new file mode 100644 index 00000000..1692697c --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/tpm/PcrSelection.java @@ -0,0 +1,143 @@ +package hirs.attestationca.persist.tpm; + +import jakarta.persistence.Embeddable; +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlAttribute; +import jakarta.xml.bind.annotation.XmlSchemaType; +import jakarta.xml.bind.annotation.XmlType; +import lombok.extern.java.Log; +import lombok.extern.log4j.Log4j2; + +import java.nio.ByteBuffer; +import java.security.InvalidParameterException; +import java.util.Arrays; + +/** + * Java class for PcrSelection complex type, which was modified from code + * auto-generated using the xjc command line tool. This code generates XML that + * matches what is specified for the PcrSelectonType ComplexType in the + * IntegrityReportManifest_v1_0_1 schema file. + */ +@Log4j2 +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "PcrSelection", + namespace = "http://www.trustedcomputinggroup.org/XML/SCHEMA/" + + "Integrity_Report_v1_0#") +@Embeddable +public class PcrSelection { + + private static final int MAX_SIZE_PCR_ARRAY = 3; + /** + * All PCRs are on. + */ + public static final int ALL_PCRS_ON = 0xffffff; + + @XmlAttribute(name = "PcrSelect", required = true) + private final byte[] pcrSelect; + + /** + * Default constructor necessary for marshalling/unmarshalling xml. + */ + protected PcrSelection() { + this.pcrSelect = new byte[0]; + } + + /** + * Constructor used to create a PcrSelection object. + *

+ * PcrSelect is a contiguous bit map that shows which PCRs are selected. + * Each byte represents 8 PCRs. Byte 0 indicates PCRs 0-7, byte 1 8-15 and + * so on. For each byte, the individual bits represent a corresponding PCR. + * + * @param pcrSelect + * byte array indicating which PCRS are selected + * + */ + public PcrSelection(final byte[] pcrSelect) { + if (pcrSelect == null) { + log.error("null pcrSelect value"); + throw new NullPointerException("pcrSelect"); + } + if (pcrSelect.length > MAX_SIZE_PCR_ARRAY) { + log.error( + "pcrSelect byte array is {}, must be length {} or less", + MAX_SIZE_PCR_ARRAY, pcrSelect.length); + throw new InvalidParameterException("pcrSelect"); + } + this.pcrSelect = new byte[MAX_SIZE_PCR_ARRAY]; + System.arraycopy(pcrSelect, 0, this.pcrSelect, 0, pcrSelect.length); + } + + /** + * Constructor used to create a PcrSelection object using a long as the + * selection value. For example, to select the first 3 PCRs, one would use + * the long value 7 (b0000 0000 0000 0111). + * + * @param pcrSelectLong + * long value representing the bits to be selected + */ + public PcrSelection(final long pcrSelectLong) { + if (pcrSelectLong > ALL_PCRS_ON) { + log.error("pcrSelect long value must be less than 3 bytes"); + throw new InvalidParameterException("pcrSelect"); + } + final int bytesInLong = 8; + this.pcrSelect = Arrays.copyOfRange( + ByteBuffer.allocate(bytesInLong) + .putLong(Long.reverse(pcrSelectLong)).array(), + 0, MAX_SIZE_PCR_ARRAY); + } + + /** + * Gets the value of the sizeOfSelect property, which represents the size in + * bytes of the PcrSelect structure. + * + * @return int sizeOfSelect + */ + @XmlAttribute(name = "SizeOfSelect", required = true) + @XmlSchemaType(name = "unsignedShort") + public final int getSizeOfSelect() { + return this.pcrSelect.length; + } + + /** + * Gets the value of the pcrSelect property. PcrSelect is a contiguous bit + * map that shows which PCRs are selected. Each byte represents 8 PCRs. Byte + * 0 indicates PCRs 0-7, byte 1 8-15 and so on. For each byte, the + * individual bits represent a corresponding PCR. + * + * @return possible object is byte[] + */ + public final byte[] getPcrSelect() { + return pcrSelect.clone(); + } + + /** + * Returns the length of the PcrSelection object if it were represented as a byte array. The + * byte array contains the size of the PCR selection mask in 2 bytes (a short) in addition to + * the PCR selection mask. + * + * @return length of the byte array representing the PcrSelection object. + */ + public final int getLength() { + return 2 + this.getSizeOfSelect(); + } + + /** + * Returns the value of the PcrSelection object as it is represented as a byte array. The first + * two bytes of the byte array represent the size of the PCR selection mask and the rest of the + * byte array is the PCR selection mask itself. + * + * @return byte array representing the PcrSelection object. + */ + public final byte[] getValue() { + ByteBuffer byteBuffer = ByteBuffer.allocate(this.getLength()); + + byteBuffer.putShort((short) this.getSizeOfSelect()); + byteBuffer.put(this.getPcrSelect()); + + return byteBuffer.array(); + + } +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/validation/PcrValidator.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/validation/PcrValidator.java new file mode 100644 index 00000000..9e8b738c --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/validation/PcrValidator.java @@ -0,0 +1,229 @@ +package hirs.attestationca.persist.validation; + +import hirs.attestationca.persist.entity.userdefined.PolicySettings; +import hirs.attestationca.persist.entity.userdefined.record.TPMMeasurementRecord; +import hirs.attestationca.persist.entity.userdefined.rim.ReferenceDigestValue; +import hirs.attestationca.persist.tpm.PcrComposite; +import hirs.attestationca.persist.tpm.PcrInfoShort; +import hirs.attestationca.persist.tpm.PcrSelection; +import hirs.utils.tpm.eventlog.TCGEventLog; +import hirs.utils.tpm.eventlog.TpmPcrEvent; +import lombok.extern.log4j.Log4j2; +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Hex; + +import java.nio.charset.StandardCharsets; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import static hirs.attestationca.persist.tpm.PcrSelection.ALL_PCRS_ON; + +/** + * The class handles the flags that ignore certain PCRs for validation. + */ +@Log4j2 +public class PcrValidator { + + private static final int NUM_TO_SKIP = 1; + private static final int NUM_OF_TBOOT_PCR = 3; + // PCR 5-16 + private static final int PXE_PCR_START = 5; + private static final int PXE_PCR_END = 16; + // PCR 10 + private static final int IMA_PCR = 10; + // PCR 17-19 + private static final int TBOOT_PCR_START = 17; + 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"; + private static final String EVT_EFI_VAR = "EV_EFI_VARIABLE_BOOT"; + private static final String EVT_EFI_GPT = "EV_EFI_GPT_EVENT"; + private static final String EVT_EFI_CFG = "EV_EFI_VARIABLE_DRIVER_CONFIG"; + + private String[] baselinePcrs; + + /** + * Default constructor. + */ + public PcrValidator() { + baselinePcrs = new String[TPMMeasurementRecord.MAX_PCR_ID + 1]; + } + + /** + * Constructor to parse PCR values. + * + * @param pcrValues RIM provided baseline PCRs + */ + public PcrValidator(final String[] pcrValues) { + baselinePcrs = new String[TPMMeasurementRecord.MAX_PCR_ID + 1]; + for (int i = 0; i <= TPMMeasurementRecord.MAX_PCR_ID; i++) { + baselinePcrs[i] = pcrValues[i]; + } + } + + /** + * Getter for the array of baseline PCRs. + * @return instance of the PCRs. + */ + public String[] getBaselinePcrs() { + return baselinePcrs.clone(); + } + + /** + * Setter for the array of baseline PCRs. + * @param baselinePcrs instance of the PCRs. + */ + public void setBaselinePcrs(final String[] baselinePcrs) { + this.baselinePcrs = baselinePcrs.clone(); + } + + /** + * Compares the baseline pcr list and the quote pcr list. If the + * ignore flags are set, 10 and 17-19 will be skipped for comparison. + * + * @param storedPcrs non-baseline pcr list + * @param policySettings db entity that holds all of policy + * @return a StringBuilder that is empty if everything passes. + */ + public StringBuilder validatePcrs(final String[] storedPcrs, + final PolicySettings policySettings) { + StringBuilder sb = new StringBuilder(); + String failureMsg = "PCR %d does not match%n"; + if (storedPcrs[0] == null || storedPcrs[0].isEmpty()) { + sb.append("failureMsg"); + } else { + for (int i = 0; i <= TPMMeasurementRecord.MAX_PCR_ID; i++) { + if (policySettings.isIgnoreImaEnabled() && i == IMA_PCR) { + log.info("PCR Policy IMA Ignore enabled."); + i += NUM_TO_SKIP; + } + + if (policySettings.isIgnoretBootEnabled() && i == TBOOT_PCR_START) { + log.info("PCR Policy TBoot Ignore enabled."); + i += NUM_OF_TBOOT_PCR; + } + + if (policySettings.isIgnoreGptEnabled() && i == GPT_PCR) { + log.info("PCR Policy GPT Ignore enabled."); + i += NUM_TO_SKIP; + } + + if (!baselinePcrs[i].equals(storedPcrs[i])) { + log.error(String.format("%s =/= %s", baselinePcrs[i], storedPcrs[i])); + sb.append(String.format(failureMsg, i)); + } + } + } + + return sb; + } + + /** + * Checks that the expected FM events occurring. There are policy options that + * will ignore certin PCRs, Event Types and Event Variables present. + * @param tcgMeasurementLog Measurement log from the client + * @param eventValueMap The events stored as baseline to compare + * @param policySettings db entity that holds all of policy + * @return the events that didn't pass + */ + public List validateTpmEvents(final TCGEventLog tcgMeasurementLog, + final Map eventValueMap, + final PolicySettings policySettings) { + List tpmPcrEvents = new LinkedList<>(); + for (TpmPcrEvent tpe : tcgMeasurementLog.getEventList()) { + if (policySettings.isIgnoreImaEnabled() && tpe.getPcrIndex() == IMA_PCR) { + log.info(String.format("IMA Ignored -> %s", tpe)); + } else if (policySettings.isIgnoretBootEnabled() && (tpe.getPcrIndex() >= TBOOT_PCR_START + && tpe.getPcrIndex() <= TBOOT_PCR_END)) { + log.info(String.format("TBOOT Ignored -> %s", tpe)); + } else if (policySettings.isIgnoreOsEvtEnabled() && (tpe.getPcrIndex() >= PXE_PCR_START + && tpe.getPcrIndex() <= PXE_PCR_END)) { + log.info(String.format("OS Evt Ignored -> %s", tpe)); + } else { + if (policySettings.isIgnoreGptEnabled() && tpe.getEventTypeStr().contains(EVT_EFI_GPT)) { + log.info(String.format("GPT Ignored -> %s", tpe)); + } else if (policySettings.isIgnoreOsEvtEnabled() && (tpe.getEventTypeStr().contains(EVT_EFI_BOOT) + || tpe.getEventTypeStr().contains(EVT_EFI_VAR))) { + log.info(String.format("OS Evt Ignored -> %s", tpe)); + } else if (policySettings.isIgnoreOsEvtEnabled() && (tpe.getEventTypeStr().contains(EVT_EFI_CFG) + && tpe.getEventContentStr().contains("SecureBoot"))) { + log.info(String.format("OS Evt Config Ignored -> %s", tpe)); + } else { + if (!eventValueMap.containsKey(tpe.getEventDigestStr())) { + tpmPcrEvents.add(tpe); + } + } + } + } + + return tpmPcrEvents; + } + + /** + * Compares hashs to validate the quote from the client. + * + * @param tpmQuote the provided quote + * @param storedPcrs values from the RIM file + * @param policySettings db entity that holds all of policy + * @return true if validated, false if not + */ + public boolean validateQuote(final byte[] tpmQuote, final String[] storedPcrs, + final PolicySettings policySettings) { + log.info("Validating quote from associated device."); + boolean validated = false; + short localityAtRelease = 0; + String quoteString = new String(tpmQuote, StandardCharsets.UTF_8); + int pcrMaskSelection = ALL_PCRS_ON; + + if (policySettings.isIgnoreImaEnabled()) { + pcrMaskSelection = IMA_MASK; + } + + ArrayList measurements = new ArrayList<>(); + + try { + for (int i = 0; i < storedPcrs.length; i++) { + if (i == IMA_PCR && policySettings.isIgnoreImaEnabled()) { + log.info("Ignore IMA PCR policy is enabled."); + } else { + measurements.add(new TPMMeasurementRecord(i, storedPcrs[i])); + } + } + } catch (DecoderException deEx) { + log.error(deEx); + } + + PcrSelection pcrSelection = new PcrSelection(pcrMaskSelection); + PcrComposite pcrComposite = new PcrComposite( + pcrSelection, measurements); + PcrInfoShort pcrInfoShort = new PcrInfoShort(pcrSelection, + localityAtRelease, + tpmQuote, pcrComposite); + + try { + /** + * The calculated string is being used in the contains method + * because the TPM Quote's hash isn't just for PCR values, + * it contains the calculated digest of the PCRs, along with + * other information. + */ + String calculatedString = Hex.encodeHexString( + pcrInfoShort.getCalculatedDigest()); + validated = quoteString.contains(calculatedString); + if (!validated) { + log.warn(calculatedString + " not found in " + quoteString); + } + } catch (NoSuchAlgorithmException naEx) { + log.error(naEx); + } + + return validated; + } +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/validation/SupplyChainCredentialValidator.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/validation/SupplyChainCredentialValidator.java new file mode 100644 index 00000000..70aaec2c --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/validation/SupplyChainCredentialValidator.java @@ -0,0 +1,72 @@ +package hirs.attestationca.persist.validation; + +import hirs.attestationca.persist.entity.userdefined.SupplyChainValidation; +import hirs.attestationca.persist.entity.userdefined.certificate.ComponentResult; +import hirs.attestationca.persist.entity.userdefined.certificate.EndorsementCredential; +import hirs.attestationca.persist.entity.userdefined.certificate.PlatformCredential; +import hirs.attestationca.persist.entity.userdefined.report.DeviceInfoReport; +import hirs.attestationca.persist.enums.AppraisalStatus; +import lombok.NoArgsConstructor; +import lombok.extern.log4j.Log4j2; + +import java.security.KeyStore; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +@Log4j2 +@NoArgsConstructor +public class SupplyChainCredentialValidator implements CredentialValidator { + + /** + * AppraisalStatus message for a valid endorsement credential appraisal. + */ + public static final String ENDORSEMENT_VALID = "Endorsement credential validated"; + + /** + * AppraisalStatus message for a valid platform credential appraisal. + */ + public static final String PLATFORM_VALID = "Platform credential validated"; + + /** + * AppraisalStatus message for a valid platform credential attributes appraisal. + */ + public static final String PLATFORM_ATTRIBUTES_VALID = + "Platform credential attributes validated"; + + /** + * AppraisalStatus message for a valid firmware appraisal. + */ + public static final String FIRMWARE_VALID = "Firmware validated"; + + private static List componentResultList = new LinkedList<>(); + + @Override + public AppraisalStatus validatePlatformCredential(final PlatformCredential pc, + final KeyStore trustStore, + final boolean acceptExpired) { + return null; + } + + @Override + public AppraisalStatus validatePlatformCredentialAttributes(final PlatformCredential pc, + final DeviceInfoReport deviceInfoReport, + final EndorsementCredential ec) { + return null; + } + + @Override + public AppraisalStatus validateDeltaPlatformCredentialAttributes(final PlatformCredential delta, + final DeviceInfoReport deviceInfoReport, + final PlatformCredential base, + final Map deltaMapping) { + return null; + } + + @Override + public AppraisalStatus validateEndorsementCredential(final EndorsementCredential ec, + final KeyStore trustStore, + final boolean acceptExpired) { + return null; + } +} diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/PersistenceJPAConfig.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/PersistenceJPAConfig.java index fc9c3518..285f2f7e 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/PersistenceJPAConfig.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/PersistenceJPAConfig.java @@ -1,5 +1,7 @@ package hirs.attestationca.portal; +import hirs.attestationca.persist.service.SupplyChainValidationService; +import hirs.attestationca.persist.service.SupplyChainValidationServiceImpl; import jakarta.annotation.PostConstruct; import lombok.extern.log4j.Log4j2; import org.bouncycastle.jce.provider.BouncyCastleProvider; @@ -26,14 +28,14 @@ import org.springframework.web.servlet.config.annotation.DefaultServletHandlerCo import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +//import javax.sql.DataSource; import javax.sql.DataSource; -import java.io.FileOutputStream; -import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.security.KeyStore; import java.security.KeyStoreException; +import java.security.PrivateKey; import java.security.Security; import java.security.cert.X509Certificate; import java.util.Properties; @@ -56,7 +58,7 @@ import java.util.Properties; @PropertySource(value = "file:/etc/hirs/aca/application.properties", ignoreResourceNotFound = true) }) -@ComponentScan({"hirs.attestationca.portal", "hirs.attestationca.portal.page.controllers", "hirs.attestationca.persist.entity"}) +@ComponentScan({"hirs.attestationca.portal", "hirs.attestationca.portal.page.controllers", "hirs.attestationca.persist.entity", "hirs.attestationca.persist.service"}) @EnableJpaRepositories(basePackages = "hirs.attestationca.persist.entity.manager") public class PersistenceJPAConfig implements WebMvcConfigurer { @@ -88,6 +90,11 @@ public class PersistenceJPAConfig implements WebMvcConfigurer { return entityManagerBean; } +// @Bean +// public SupplyChainValidationService supplyChainValidationService() { +// return new SupplyChainValidationServiceImpl(); +// } + @Bean public DataSource dataSource() { final DriverManagerDataSource dataSource = new DriverManagerDataSource(); @@ -130,6 +137,34 @@ public class PersistenceJPAConfig implements WebMvcConfigurer { // } } + /** + * @return the {@link PrivateKey} of the ACA + */ + @Bean + public PrivateKey privateKey() { + + // obtain the key store + KeyStore keyStore = keyStore(); + + try { + + // load the key from the key store + PrivateKey acaKey = (PrivateKey) keyStore.getKey(keyAlias, + keyStorePassword.toCharArray()); + + // break early if the certificate is not available. + if (acaKey == null) { + throw new BeanInitializationException(String.format("Key with alias " + + "%s was not in KeyStore %s. Ensure that the KeyStore has the " + + "specified certificate. ", keyAlias, keyStoreLocation)); + } + return acaKey; + } catch (Exception ex) { + throw new BeanInitializationException("Encountered error loading ACA private key " + + "from key store: " + ex.getMessage(), ex); + } + } + /** * @return the {@link X509Certificate} of the ACA */ @@ -169,13 +204,13 @@ public class PersistenceJPAConfig implements WebMvcConfigurer { keyStore.load(Files.newInputStream(keyStorePath), keyStorePassword.toCharArray()); return keyStore; - } catch (Exception e) { + } catch (Exception ex) { log.error(String.format( "Encountered error while loading ACA key store. The most common issue is " + "that configured password does not work on the configured key" + " store %s.", keyStorePath)); - log.error(String.format("Exception message: %s", e.getMessage())); - throw new BeanInitializationException(e.getMessage(), e); + log.error(String.format("Exception message: %s", ex.getMessage())); + throw new BeanInitializationException(ex.getMessage(), ex); } } diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificateDetailsPageController.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificateDetailsPageController.java index 6b7d542b..e9a76940 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificateDetailsPageController.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificateDetailsPageController.java @@ -90,15 +90,15 @@ public class CertificateDetailsPageController extends PageController baseRims = this.referenceManifestRepository + List baseRims = new LinkedList<>(); + baseRims.add(this.referenceManifestRepository .getBaseByManufacturerModel(supportRim.getPlatformManufacturer(), - supportRim.getPlatformModel()); + supportRim.getPlatformModel())); for (BaseReferenceManifest base : baseRims) { if (base.isBase()) { diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/utils/CertificateStringMapBuilder.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/utils/CertificateStringMapBuilder.java index e5825507..29374ce1 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/utils/CertificateStringMapBuilder.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/utils/CertificateStringMapBuilder.java @@ -43,7 +43,8 @@ public final class CertificateStringMapBuilder { * @return a hash map with the general certificate information. */ public static HashMap getGeneralCertificateInfo( - final Certificate certificate, final CertificateRepository certificateRepository) { + final Certificate certificate, final CertificateRepository certificateRepository, + final CACredentialRepository caCertificateRepository) { HashMap data = new HashMap<>(); if (certificate != null) { @@ -102,7 +103,7 @@ public final class CertificateStringMapBuilder { //Get issuer ID if not self signed if (data.get("isSelfSigned").equals("false")) { //Get the missing certificate chain for not self sign - Certificate missingCert = containsAllChain(certificate, certificateRepository); + Certificate missingCert = containsAllChain(certificate, certificateRepository, caCertificateRepository); String issuerResult; if (missingCert != null) { @@ -145,7 +146,8 @@ public final class CertificateStringMapBuilder { */ public static Certificate containsAllChain( final Certificate certificate, - final CertificateRepository certificateRepository) { + final CertificateRepository certificateRepository, + final CACredentialRepository caCredentialRepository) { List issuerCertificates = new LinkedList<>(); CertificateAuthorityCredential skiCA = null; String issuerResult; @@ -154,7 +156,7 @@ public final class CertificateStringMapBuilder { if (certificate.getAuthorityKeyIdentifier() != null && !certificate.getAuthorityKeyIdentifier().isEmpty()) { byte[] bytes = Hex.decode(certificate.getAuthorityKeyIdentifier()); - skiCA = (CertificateAuthorityCredential) certificateRepository.findBySubjectKeyIdentifier(bytes); + skiCA = caCredentialRepository.findBySubjectKeyIdentifier(bytes); } else { log.error(String.format("Certificate (%s) for %s has no authority key identifier.", certificate.getClass().toString(), certificate.getSubject())); @@ -185,7 +187,7 @@ public final class CertificateStringMapBuilder { issuerCert.getSubject())) { return null; } - return containsAllChain(issuerCert, certificateRepository); + return containsAllChain(issuerCert, certificateRepository, caCredentialRepository); } } catch (IOException ioEx) { log.error(ioEx); @@ -237,7 +239,7 @@ public final class CertificateStringMapBuilder { HashMap data = new HashMap<>(); if (certificate != null) { - data.putAll(getGeneralCertificateInfo(certificate, certificateRepository)); + data.putAll(getGeneralCertificateInfo(certificate, certificateRepository, caCertificateRepository)); data.put("subjectKeyIdentifier", Arrays.toString(certificate.getSubjectKeyIdentifier())); //x509 credential version @@ -258,12 +260,13 @@ public final class CertificateStringMapBuilder { * @return a hash map with the endorsement certificate information. */ public static HashMap getEndorsementInformation(final UUID uuid, - final CertificateRepository certificateRepository) { + final CertificateRepository certificateRepository, + final CACredentialRepository caCertificateRepository) { HashMap data = new HashMap<>(); EndorsementCredential certificate = (EndorsementCredential) certificateRepository.getCertificate(uuid); if (certificate != null) { - data.putAll(getGeneralCertificateInfo(certificate, certificateRepository)); + data.putAll(getGeneralCertificateInfo(certificate, certificateRepository, caCertificateRepository)); // Set extra fields data.put("manufacturer", certificate.getManufacturer()); data.put("model", certificate.getModel()); @@ -302,13 +305,14 @@ public final class CertificateStringMapBuilder { */ public static HashMap getPlatformInformation(final UUID uuid, final CertificateRepository certificateRepository, - final ComponentResultRepository componentResultRepository) + final ComponentResultRepository componentResultRepository, + final CACredentialRepository caCertificateRepository) throws IllegalArgumentException, IOException { HashMap data = new HashMap<>(); PlatformCredential certificate = (PlatformCredential) certificateRepository.getCertificate(uuid); if (certificate != null) { - data.putAll(getGeneralCertificateInfo(certificate, certificateRepository)); + data.putAll(getGeneralCertificateInfo(certificate, certificateRepository, caCertificateRepository)); data.put("credentialType", certificate.getCredentialType()); data.put("platformType", certificate.getPlatformChainType()); data.put("manufacturer", certificate.getManufacturer()); @@ -463,12 +467,13 @@ public final class CertificateStringMapBuilder { * @return a hash map with the endorsement certificate information. */ public static HashMap getIssuedInformation(final UUID uuid, - final CertificateRepository certificateRepository) { + final CertificateRepository certificateRepository, + final CACredentialRepository caCredentialRepository) { HashMap data = new HashMap<>(); IssuedAttestationCertificate certificate = (IssuedAttestationCertificate) certificateRepository.getCertificate(uuid); if (certificate != null) { - data.putAll(getGeneralCertificateInfo(certificate, certificateRepository)); + data.putAll(getGeneralCertificateInfo(certificate, certificateRepository, caCredentialRepository)); // add endorsement credential ID if not null if (certificate.getEndorsementCredential() != null) { diff --git a/HIRS_AttestationCAPortal/src/main/resources/application.properties b/HIRS_AttestationCAPortal/src/main/resources/application.properties index 8cdec2dc..0d840bfc 100644 --- a/HIRS_AttestationCAPortal/src/main/resources/application.properties +++ b/HIRS_AttestationCAPortal/src/main/resources/application.properties @@ -15,7 +15,7 @@ jakarta.persistence.sharedCache.mode = UNSPECIFIED spring.datasource.driver-class-name=org.mariadb.jdbc.Driver #spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver #spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver - +aca.certificates.validity = 3652 # Tomcat Config server.tomcat.additional-tld-skip-patterns=jakarta.persistence-api*.jar, jakarta.xml.bind-api*.jar, txw2*.jar, *commons*.jar, *annotations*.jar, *checker*.jar, *lombok*.jar, *jsr*.jar, *guava*.jar, *access*.jar, *activation*.jar, *bcprov*.jar, *bcmail*.jar, *bcutil*.jar, *bcpkix*.jar, *json*.jar server.tomcat.basedir=/opt/embeddedtomcat diff --git a/HIRS_Structs/build.gradle b/HIRS_Structs/build.gradle index 5defd1c7..894a2794 100644 --- a/HIRS_Structs/build.gradle +++ b/HIRS_Structs/build.gradle @@ -1,33 +1,45 @@ -apply plugin: 'java' -apply plugin: 'checkstyle' - -sourceCompatibility = 1.8 - -dependencies { - compile libs.commons_lang - - testCompile libs.mockito - testCompile libs.testng +plugins { + id 'java' +// id 'checkstyle' } -ext.configDir = new File(projectDir, 'config') -ext.checkstyleConfigDir = "$configDir/checkstyle" - -checkstyle { - toolVersion = '5.7' - configFile = checkstyleConfigFile - configProperties.put('basedir', checkstyleConfigDir) - ignoreFailures = false - showViolations = true -} - -publishing { - publications { - maven(MavenPublication) { - artifactId 'hirs-structs' - from components.java - } +java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) } } +repositories { + mavenCentral() + + flatDir { dirs "lib" } +} + +dependencies { + implementation 'org.apache.commons:commons-lang3:3.13.0' + +// testCompile libs.mockito + testImplementation libs.testng +} + +//ext.configDir = new File(projectDir, 'config') +//ext.checkstyleConfigDir = "$configDir/checkstyle" + +//checkstyle { +// toolVersion = '5.7' +// configFile = checkstyleConfigFile +// configProperties.put('basedir', checkstyleConfigDir) +// ignoreFailures = false +// showViolations = true +//} +// +//publishing { +// publications { +// maven(MavenPublication) { +// artifactId 'hirs-structs' +// from components.java +// } +// } +//} + diff --git a/settings.gradle b/settings.gradle index 90528e0c..f691999a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,6 +1,7 @@ rootProject.name = 'HIRS' include 'HIRS_Utils', + 'HIRS_Structs', 'HIRS_AttestationCA', 'HIRS_AttestationCAPortal', ':tools:tcg_eventlog_tool', @@ -13,13 +14,14 @@ dependencyResolutionManagement { version('jackson', '2.14.2') library('commons-codec', 'commons-codec:commons-codec:1.15') library('commons_io', 'commons-io:commons-io:2.11.0') - library('commons-lang3', 'org.apache.commons:commons-lang3:3.12.0') + library('commons-lang3', 'org.apache.commons:commons-lang3:3.13.0') library('bouncycastle', 'org.bouncycastle:bcmail-jdk15on:1.70') library('glassfish_json', 'org.glassfish:javax.json:1.1.4') library('glassfish_jaxb_runtime', 'org.glassfish.jaxb:jaxb-runtime:2.3.1') library('gson', 'com.google.code.gson:gson:2.10.1') library('guava', 'com.google.guava:guava:31.1-jre') library('minimal-json', 'com.eclipsesource.minimal-json:minimal-json:0.9.5') + library('protobuf-java', 'com.google.protobuf:protobuf-java:3.24.1') library('jakarta-servlet', 'org.glassfish.web:jakarta.servlet.jsp.jstl:3.0.0') library('jakarta-api', 'jakarta.persistence:jakarta.persistence-api:3.1.0') library('jakarta-xml', 'jakarta.xml.bind:jakarta.xml.bind-api:4.0.0') From d2963ef99a6bce0572143601811b2efe427d7143 Mon Sep 17 00:00:00 2001 From: Cyrus <24922493+cyrus-dev@users.noreply.github.com> Date: Thu, 31 Aug 2023 15:01:05 -0400 Subject: [PATCH 02/16] Resolved an issue with createTPMInfo. Created a new static class to contain most of the generic generation code instead of the standard provision handler classes. --- .../provision/AbstractRequestHandler.java | 196 +----- .../provision/CertificateRequestHandler.java | 16 +- .../provision/IdentityClaimHandler.java | 258 +------ .../provision/IdentityRequestHandler.java | 267 +------- .../provision/helper/ProvisionUtils.java | 642 ++++++++++++++++++ 5 files changed, 703 insertions(+), 676 deletions(-) create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/helper/ProvisionUtils.java diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/AbstractRequestHandler.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/AbstractRequestHandler.java index aac5a61d..18bb3f54 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/AbstractRequestHandler.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/AbstractRequestHandler.java @@ -1,7 +1,6 @@ package hirs.attestationca.persist.provision; import com.google.protobuf.ByteString; -import com.google.protobuf.InvalidProtocolBufferException; import hirs.attestationca.configuration.provisionerTpm2.ProvisionerTpm2; import hirs.attestationca.persist.entity.manager.CertificateRepository; import hirs.attestationca.persist.entity.manager.PolicyRepository; @@ -11,19 +10,14 @@ import hirs.attestationca.persist.entity.userdefined.PolicySettings; import hirs.attestationca.persist.entity.userdefined.certificate.EndorsementCredential; import hirs.attestationca.persist.entity.userdefined.certificate.IssuedAttestationCertificate; import hirs.attestationca.persist.entity.userdefined.certificate.PlatformCredential; -import hirs.attestationca.persist.entity.userdefined.info.TPMInfo; import hirs.attestationca.persist.exceptions.CertificateProcessingException; -import hirs.attestationca.persist.exceptions.IdentityProcessingException; -import hirs.attestationca.persist.exceptions.UnexpectedServerException; import hirs.attestationca.persist.provision.helper.CredentialManagementHelper; import hirs.attestationca.persist.provision.helper.IssuedCertificateAttributeHelper; -import hirs.utils.HexUtils; -import hirs.utils.enums.DeviceInfoEnums; +import hirs.attestationca.persist.provision.helper.ProvisionUtils; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.extern.log4j.Log4j2; -import org.apache.commons.codec.binary.Hex; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x509.Extension; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; @@ -36,18 +30,10 @@ import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import java.io.IOException; import java.math.BigInteger; -import java.nio.charset.StandardCharsets; -import java.security.KeyFactory; -import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; -import java.security.SecureRandom; -import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; -import java.security.interfaces.RSAPublicKey; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.RSAPublicKeySpec; import java.util.Calendar; import java.util.Date; import java.util.LinkedList; @@ -56,31 +42,6 @@ import java.util.List; @Log4j2 @NoArgsConstructor public class AbstractRequestHandler { - /** - * The default size for IV blocks. - */ - public final static int DEFAULT_IV_SIZE = 16; - /** - * Defines the well known exponent. - * https://en.wikipedia.org/wiki/65537_(number)#Applications - */ - private final static BigInteger EXPONENT = new BigInteger("010001", DEFAULT_IV_SIZE); - /** - * Number of bytes to include in the TPM2.0 nonce. - */ - public static final int NONCE_LENGTH = 20; - public static final int SEED_LENGTH = 32; - public static final int MAX_SECRET_LENGTH = 32; - public static final int RSA_MODULUS_LENGTH = 256; - public static final int AES_KEY_LENGTH_BYTES = 16; - public static final int HMAC_KEY_LENGTH_BYTES = 32; - public static final int HMAC_SIZE_LENGTH_BYTES = 2; - public static final int TPM2_CREDENTIAL_BLOB_SIZE = 392; - // Constants used to parse out the ak name from the ak public data. Used in generateAkName - public static final String AK_NAME_PREFIX = "000b"; - public static final String AK_NAME_HASH_PREFIX = - "0001000b00050072000000100014000b0800000000000100"; - public static final String TPM_SIGNATURE_ALG = "sha"; @Getter private int validDays; @@ -88,12 +49,6 @@ public class AbstractRequestHandler { private PrivateKey privateKey; @Setter @Getter - private String tpmQuoteHash = ""; - @Setter - @Getter - private String tpmQuoteSignature = ""; - @Setter - @Getter private PolicyRepository policyRepository; public AbstractRequestHandler(final PrivateKey privateKey, @@ -102,112 +57,6 @@ public class AbstractRequestHandler { this.validDays = validDays; } - /** - * Parse public key from public data segment generated by TPM 2.0. - * @param publicArea the public area segment to parse - * @return the RSA public key of the supplied public data - */ - RSAPublicKey parsePublicKey(final byte[] publicArea) { - int pubLen = publicArea.length; - if (pubLen < RSA_MODULUS_LENGTH) { - throw new IllegalArgumentException( - "EK or AK public data segment is not long enough"); - } - // public data ends with 256 byte modulus - byte[] modulus = HexUtils.subarray(publicArea, - pubLen - RSA_MODULUS_LENGTH, - pubLen - 1); - return (RSAPublicKey) assemblePublicKey(modulus); - } - - /** - * Constructs a public key where the modulus is in raw form. - * - * @param modulus - * in byte array form - * @return public key using specific modulus and the well known exponent - */ - PublicKey assemblePublicKey(final byte[] modulus) { - return assemblePublicKey(Hex.encodeHexString(modulus)); - } - - /** - * Constructs a public key where the modulus is Hex encoded. - * - * @param modulus - * hex encoded modulus - * @return public key using specific modulus and the well known exponent - */ - PublicKey assemblePublicKey(final String modulus) { - return assemblePublicKey(new BigInteger(modulus, DEFAULT_IV_SIZE)); - } - - /** - * Assembles a public key using a defined big int modulus and the well known exponent. - */ - private PublicKey assemblePublicKey(final BigInteger modulus) { - // generate a key spec using mod and exp - RSAPublicKeySpec keySpec = new RSAPublicKeySpec(modulus, EXPONENT); - - // create the public key - try { - KeyFactory keyFactory = KeyFactory.getInstance("RSA"); - return keyFactory.generatePublic(keySpec); - } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { - throw new UnexpectedServerException( - "Encountered unexpected error creating public key: " + e.getMessage(), e); - } - } - - /** - * Helper method to parse a byte array into an {@link ProvisionerTpm2.IdentityClaim}. - * - * @param identityClaim byte array that should be converted to a Protobuf IdentityClaim - * object - * @throws {@link IdentityProcessingException} if byte array could not be parsed - * @return the Protobuf generated Identity Claim object - */ - public ProvisionerTpm2.IdentityClaim parseIdentityClaim(final byte[] identityClaim) { - try { - return ProvisionerTpm2.IdentityClaim.parseFrom(identityClaim); - } catch (InvalidProtocolBufferException ipbe) { - throw new IdentityProcessingException( - "Could not deserialize Protobuf Identity Claim object.", ipbe); - } - } - - /** - * Helper method to extract a DER encoded ASN.1 certificate from an X509 certificate. - * - * @param certificate the X509 certificate to be converted to DER encoding - * @throws {@link UnexpectedServerException} if error occurs during encoding retrieval - * @return the byte array representing the DER encoded certificate - */ - public byte[] getDerEncodedCertificate(final X509Certificate certificate) { - try { - return certificate.getEncoded(); - } catch (CertificateEncodingException ceEx) { - log.error("Error converting certificate to ASN.1 DER Encoding.", ceEx); - throw new UnexpectedServerException( - "Encountered error while converting X509 Certificate to ASN.1 DER Encoding: " - + ceEx.getMessage(), ceEx); - } - } - - /** - * Generates a array of random bytes. - * - * @param numberOfBytes - * to be generated - * @return byte array filled with the specified number of bytes. - */ - public byte[] generateRandomBytes(final int numberOfBytes) { - byte[] bytes = new byte[numberOfBytes]; - SecureRandom random = new SecureRandom(); - random.nextBytes(bytes); - return bytes; - } - /** * Generates a credential using the specified public key. * @@ -365,42 +214,6 @@ public class AbstractRequestHandler { return credential; } - /** - * This method takes the provided TPM Quote and splits it between the PCR - * quote and the signature hash. - * @param tpmQuote contains hash values for the quote and the signature - */ - public boolean parseTPMQuote(final String tpmQuote) { - boolean success = false; - if (tpmQuote != null) { - String[] lines = tpmQuote.split(":"); - if (lines[1].contains("signature")) { - this.tpmQuoteHash = lines[1].replace("signature", "").trim(); - } else { - this.tpmQuoteHash = lines[1].trim(); - } - this.tpmQuoteSignature = lines[2].trim(); - success = true; - } - - return success; - } - - /** - * Helper method to create the TPMInfo used for device. - * @return a new TPMInfo Instance - */ - public TPMInfo createTpmInfo(final String pcrValues) { - return new TPMInfo(DeviceInfoEnums.NOT_SPECIFIED, - (short) 0, - (short) 0, - (short) 0, - (short) 0, - pcrValues.getBytes(StandardCharsets.UTF_8), - this.getTpmQuoteHash().getBytes(StandardCharsets.UTF_8), - this.getTpmQuoteSignature().getBytes(StandardCharsets.UTF_8)); - } - /** * Helper method to create an {@link IssuedAttestationCertificate} object, set its * corresponding device and persist it. @@ -437,7 +250,7 @@ public class AbstractRequestHandler { if (issuedAc.getEndValidity().after(currentDate)) { // so the issued AC is not expired // however are we within the threshold - days = daysBetween(currentDate, issuedAc.getEndValidity()); + days = ProvisionUtils.daysBetween(currentDate, issuedAc.getEndValidity()); if (days < Integer.parseInt(policySettings.getReissueThreshold())) { generateCertificate = true; } else { @@ -477,9 +290,4 @@ public class AbstractRequestHandler { return credentials; } - - @SuppressWarnings("magicnumber") - private int daysBetween(final Date date1, final Date date2) { - return (int) ((date2.getTime() - date1.getTime()) / (1000 * 60 * 60 * 24)); - } } diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/CertificateRequestHandler.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/CertificateRequestHandler.java index f2c3cce1..2ca0611c 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/CertificateRequestHandler.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/CertificateRequestHandler.java @@ -15,6 +15,7 @@ import hirs.attestationca.persist.entity.userdefined.info.TPMInfo; import hirs.attestationca.persist.entity.userdefined.report.DeviceInfoReport; import hirs.attestationca.persist.enums.AppraisalStatus; import hirs.attestationca.persist.exceptions.CertificateProcessingException; +import hirs.attestationca.persist.provision.helper.ProvisionUtils; import hirs.attestationca.persist.service.SupplyChainValidationService; import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.ArrayUtils; @@ -87,13 +88,13 @@ public class CertificateRequestHandler extends AbstractRequestHandler { if (tpm2ProvisionerState != null) { // Reparse Identity Claim to gather necessary components byte[] identityClaim = tpm2ProvisionerState.getIdentityClaim(); - ProvisionerTpm2.IdentityClaim claim = parseIdentityClaim(identityClaim); + ProvisionerTpm2.IdentityClaim claim = ProvisionUtils.parseIdentityClaim(identityClaim); // Get endorsement public key - RSAPublicKey ekPub = parsePublicKey(claim.getEkPublicArea().toByteArray()); + RSAPublicKey ekPub = ProvisionUtils.parsePublicKey(claim.getEkPublicArea().toByteArray()); // Get attestation public key - RSAPublicKey akPub = parsePublicKey(claim.getAkPublicArea().toByteArray()); + RSAPublicKey akPub = ProvisionUtils.parsePublicKey(claim.getAkPublicArea().toByteArray()); // Get Endorsement Credential if it exists or was uploaded EndorsementCredential endorsementCredential = parseEcFromIdentityClaim(claim, ekPub, certificateRepository); @@ -109,7 +110,6 @@ public class CertificateRequestHandler extends AbstractRequestHandler { // Parse through the Provisioner supplied TPM Quote and pcr values // these fields are optional if (request.getQuote() != null && !request.getQuote().isEmpty()) { - parseTPMQuote(request.getQuote().toStringUtf8()); TPMInfo savedInfo = device.getDeviceInfo().getTpmInfo(); TPMInfo tpmInfo = new TPMInfo(savedInfo.getTpmMake(), savedInfo.getTpmVersionMajor(), @@ -117,8 +117,10 @@ public class CertificateRequestHandler extends AbstractRequestHandler { savedInfo.getTpmVersionRevMajor(), savedInfo.getTpmVersionRevMinor(), savedInfo.getPcrValues(), - getTpmQuoteHash().getBytes(StandardCharsets.UTF_8), - getTpmQuoteSignature().getBytes(StandardCharsets.UTF_8)); + ProvisionUtils.parseTPMQuoteHash(request.getQuote().toStringUtf8()) + .getBytes(StandardCharsets.UTF_8), + ProvisionUtils.parseTPMQuoteSignature(request.getQuote().toStringUtf8()) + .getBytes(StandardCharsets.UTF_8)); DeviceInfoReport dvReport = new DeviceInfoReport( device.getDeviceInfo().getNetworkInfo(), @@ -136,7 +138,7 @@ public class CertificateRequestHandler extends AbstractRequestHandler { // Create signed, attestation certificate X509Certificate attestationCertificate = generateCredential(akPub, endorsementCredential, platformCredentials, deviceName, acaCertificate); - byte[] derEncodedAttestationCertificate = getDerEncodedCertificate( + byte[] derEncodedAttestationCertificate = ProvisionUtils.getDerEncodedCertificate( attestationCertificate); // We validated the nonce and made use of the identity claim so state can be deleted diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/IdentityClaimHandler.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/IdentityClaimHandler.java index 99f0b648..a2d6dfc8 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/IdentityClaimHandler.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/IdentityClaimHandler.java @@ -27,6 +27,7 @@ import hirs.attestationca.persist.entity.userdefined.rim.ReferenceDigestValue; import hirs.attestationca.persist.entity.userdefined.rim.SupportReferenceManifest; import hirs.attestationca.persist.enums.AppraisalStatus; import hirs.attestationca.persist.exceptions.IdentityProcessingException; +import hirs.attestationca.persist.provision.helper.ProvisionUtils; import hirs.attestationca.persist.service.SupplyChainValidationService; import hirs.utils.HexUtils; import hirs.utils.SwidResource; @@ -37,28 +38,15 @@ import lombok.extern.log4j.Log4j2; import org.apache.commons.codec.binary.Hex; import org.apache.commons.lang3.ArrayUtils; -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.Mac; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.OAEPParameterSpec; -import javax.crypto.spec.PSource; -import javax.crypto.spec.SecretKeySpec; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; -import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.cert.CertificateException; import java.security.interfaces.RSAPublicKey; -import java.security.spec.MGF1ParameterSpec; import java.util.ArrayList; import java.util.Base64; import java.util.HashMap; @@ -126,11 +114,11 @@ public class IdentityClaimHandler extends AbstractRequestHandler { } // attempt to deserialize Protobuf IdentityClaim - ProvisionerTpm2.IdentityClaim claim = parseIdentityClaim(identityClaim); + ProvisionerTpm2.IdentityClaim claim = ProvisionUtils.parseIdentityClaim(identityClaim); // parse the EK Public key from the IdentityClaim once for use in supply chain validation // and later tpm20MakeCredential function - RSAPublicKey ekPub = parsePublicKey(claim.getEkPublicArea().toByteArray()); + RSAPublicKey ekPub = ProvisionUtils.parsePublicKey(claim.getEkPublicArea().toByteArray()); AppraisalStatus.Status validationResult = AppraisalStatus.Status.FAIL; try { @@ -143,9 +131,9 @@ public class IdentityClaimHandler extends AbstractRequestHandler { ByteString blobStr = ByteString.copyFrom(new byte[]{}); if (validationResult == AppraisalStatus.Status.PASS) { - RSAPublicKey akPub = parsePublicKey(claim.getAkPublicArea().toByteArray()); - byte[] nonce = generateRandomBytes(NONCE_LENGTH); - blobStr = tpm20MakeCredential(ekPub, akPub, nonce); + RSAPublicKey akPub = ProvisionUtils.parsePublicKey(claim.getAkPublicArea().toByteArray()); + byte[] nonce = ProvisionUtils.generateRandomBytes(NONCE_LENGTH); + blobStr = ProvisionUtils.tpm20MakeCredential(ekPub, akPub, nonce); PolicyRepository scp = this.getPolicyRepository(); PolicySettings policySettings = scp.findByName("Default"); String pcrQuoteMask = PCR_QUOTE_MASK; @@ -223,7 +211,6 @@ public class IdentityClaimHandler extends AbstractRequestHandler { private Device processDeviceInfo(final ProvisionerTpm2.IdentityClaim claim) { DeviceInfoReport deviceInfoReport = null; - String deviceName = deviceInfoReport.getNetworkInfo().getHostname(); try { deviceInfoReport = parseDeviceInfo(claim); @@ -239,7 +226,7 @@ public class IdentityClaimHandler extends AbstractRequestHandler { log.info("Processing Device Info Report"); // store device and device info report. - Device device = this.deviceRepository.findByName(deviceName); + Device device = this.deviceRepository.findByName(deviceInfoReport.getNetworkInfo().getHostname()); device.setDeviceInfo(deviceInfoReport); return this.deviceRepository.save(device); } @@ -261,8 +248,8 @@ public class IdentityClaimHandler extends AbstractRequestHandler { InetAddress ip = null; try { ip = InetAddress.getByName(nwProto.getIpAddress()); - } catch (UnknownHostException e) { - log.error("Unable to parse IP address: ", e); + } catch (UnknownHostException uhEx) { + log.error("Unable to parse IP address: ", uhEx); } String[] macAddressParts = nwProto.getMacAddress().split(":"); @@ -486,10 +473,16 @@ public class IdentityClaimHandler extends AbstractRequestHandler { } // Get TPM info, currently unimplemented - TPMInfo tpm = createTpmInfo(pcrValues); + TPMInfo tpmInfo = new TPMInfo(DeviceInfoEnums.NOT_SPECIFIED, + (short) 0, + (short) 0, + (short) 0, + (short) 0, + pcrValues.getBytes(StandardCharsets.UTF_8), + null, null); // Create final report - DeviceInfoReport dvReport = new DeviceInfoReport(nw, os, fw, hw, tpm, + DeviceInfoReport dvReport = new DeviceInfoReport(nw, os, fw, hw, tpmInfo, claim.getClientVersion()); dvReport.setPaccorOutputString(claim.getPaccorOutput()); @@ -602,214 +595,25 @@ public class IdentityClaimHandler extends AbstractRequestHandler { return true; } - /** - * Performs the first step of the TPM 2.0 identity claim process. Takes an ek, ak, and secret - * and then generates a seed that is used to generate AES and HMAC keys. Parses the ak name. - * Encrypts the seed with the public ek. Uses the AES key to encrypt the secret. Uses the HMAC - * key to generate an HMAC to cover the encrypted secret and the ak name. The output is an - * encrypted blob that acts as the first part of a challenge-response authentication mechanism - * to validate an identity claim. - * - * Equivalent to calling tpm2_makecredential using tpm2_tools. - * - * @param ek endorsement key in the identity claim - * @param ak attestation key in the identity claim - * @param secret a nonce - * @return the encrypted blob forming the identity claim challenge - */ - protected ByteString tpm20MakeCredential(final RSAPublicKey ek, final RSAPublicKey ak, - final byte[] secret) { - // check size of the secret - if (secret.length > MAX_SECRET_LENGTH) { - throw new IllegalArgumentException("Secret must be " + MAX_SECRET_LENGTH - + " bytes or smaller."); - } - // generate a random 32 byte seed - byte[] seed = generateRandomBytes(SEED_LENGTH); - try { - // encrypt seed with pubEk - Cipher asymCipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding"); - OAEPParameterSpec oaepSpec = new OAEPParameterSpec("SHA-256", "MGF1", - MGF1ParameterSpec.SHA256, new PSource.PSpecified("IDENTITY\0".getBytes())); - asymCipher.init(Cipher.PUBLIC_KEY, ek, oaepSpec); - asymCipher.update(seed); - byte[] encSeed = asymCipher.doFinal(); + private List getPlatformCredentials(final CertificateRepository certificateRepository, + final EndorsementCredential ec) { + List credentials = null; - // generate ak name from akMod - byte[] akModTemp = ak.getModulus().toByteArray(); - byte[] akMod = new byte[RSA_MODULUS_LENGTH]; - int startpos = 0; - // BigIntegers are signed, so a modulus that has a first bit of 1 - // will be padded with a zero byte that must be removed - if (akModTemp[0] == 0x00) { - startpos = 1; + if (ec == null) { + log.warn("Cannot look for platform credential(s). Endorsement credential was null."); + } else { + log.debug("Searching for platform credential(s) based on holder serial number: " + + ec.getSerialNumber()); + credentials = certificateRepository.getByHolderSerialNumber(ec.getSerialNumber()); + if (credentials == null || credentials.isEmpty()) { + log.warn("No platform credential(s) found"); + } else { + log.debug("Platform Credential(s) found: " + credentials.size()); } - System.arraycopy(akModTemp, startpos, akMod, 0, RSA_MODULUS_LENGTH); - byte[] akName = generateAkName(akMod); - - // generate AES and HMAC keys from seed - byte[] aesKey = cryptKDFa(seed, "STORAGE", akName, AES_KEY_LENGTH_BYTES); - byte[] hmacKey = cryptKDFa(seed, "INTEGRITY", null, HMAC_KEY_LENGTH_BYTES); - - // use two bytes to add a size prefix on secret - ByteBuffer b; - b = ByteBuffer.allocate(2); - b.putShort((short) (secret.length)); - byte[] secretLength = b.array(); - byte[] secretBytes = new byte[secret.length + 2]; - System.arraycopy(secretLength, 0, secretBytes, 0, 2); - System.arraycopy(secret, 0, secretBytes, 2, secret.length); - - // encrypt size prefix + secret with AES key - Cipher symCipher = Cipher.getInstance("AES/CFB/NoPadding"); - byte[] defaultIv = HexUtils.hexStringToByteArray("00000000000000000000000000000000"); - IvParameterSpec ivSpec = new IvParameterSpec(defaultIv); - symCipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(aesKey, "AES"), ivSpec); - byte[] encSecret = symCipher.doFinal(secretBytes); - - // generate HMAC covering encrypted secret and ak name - Mac integrityHmac = Mac.getInstance("HmacSHA256"); - SecretKeySpec integrityKey = new SecretKeySpec(hmacKey, integrityHmac.getAlgorithm()); - integrityHmac.init(integrityKey); - byte[] message = new byte[encSecret.length + akName.length]; - System.arraycopy(encSecret, 0, message, 0, encSecret.length); - System.arraycopy(akName, 0, message, encSecret.length, akName.length); - integrityHmac.update(message); - byte[] integrity = integrityHmac.doFinal(); - b = ByteBuffer.allocate(2); - b.putShort((short) (HMAC_SIZE_LENGTH_BYTES + HMAC_KEY_LENGTH_BYTES + encSecret.length)); - byte[] topSize = b.array(); - - // return ordered blob of assembled credentials - byte[] bytesToReturn = assembleCredential(topSize, integrity, encSecret, encSeed); - return ByteString.copyFrom(bytesToReturn); - - } catch (BadPaddingException | IllegalBlockSizeException | NoSuchAlgorithmException - | InvalidKeyException | InvalidAlgorithmParameterException - | NoSuchPaddingException e) { - throw new IdentityProcessingException( - "Encountered error while making the identity claim challenge: " - + e.getMessage(), e); } - } - @SuppressWarnings("magicnumber") - private byte[] assembleCredential(final byte[] topSize, final byte[] integrityHmac, - final byte[] encryptedSecret, - final byte[] encryptedSeed) { - /* - * Credential structure breakdown with endianness: - * 0-1 topSize (2), LE - * 2-3 hashsize (2), BE always 0x0020 - * 4-35 integrity HMac (32) - * 36-133 (98 = 32*3 +2) of zeros, copy over from encSecret starting at [36] - * 134-135 (2) LE size, always 0x0001 - * 136-391 (256) copy over with encSeed - * */ - byte[] credentialBlob = new byte[TPM2_CREDENTIAL_BLOB_SIZE]; - credentialBlob[0] = topSize[1]; - credentialBlob[1] = topSize[0]; - credentialBlob[2] = 0x00; - credentialBlob[3] = 0x20; - System.arraycopy(integrityHmac, 0, credentialBlob, 4, 32); - for (int i = 0; i < 98; i++) { - credentialBlob[36 + i] = 0x00; - } - System.arraycopy(encryptedSecret, 0, credentialBlob, 36, encryptedSecret.length); - credentialBlob[134] = 0x00; - credentialBlob[135] = 0x01; - System.arraycopy(encryptedSeed, 0, credentialBlob, 136, 256); - // return the result - return credentialBlob; - } - - /** - * Determines the AK name from the AK Modulus. - * @param akModulus modulus of an attestation key - * @return the ak name byte array - * @throws NoSuchAlgorithmException Underlying SHA256 method used a bad algorithm - */ - byte[] generateAkName(final byte[] akModulus) throws NoSuchAlgorithmException { - byte[] namePrefix = HexUtils.hexStringToByteArray(AK_NAME_PREFIX); - byte[] hashPrefix = HexUtils.hexStringToByteArray(AK_NAME_HASH_PREFIX); - byte[] toHash = new byte[hashPrefix.length + akModulus.length]; - System.arraycopy(hashPrefix, 0, toHash, 0, hashPrefix.length); - System.arraycopy(akModulus, 0, toHash, hashPrefix.length, akModulus.length); - byte[] nameHash = sha256hash(toHash); - byte[] toReturn = new byte[namePrefix.length + nameHash.length]; - System.arraycopy(namePrefix, 0, toReturn, 0, namePrefix.length); - System.arraycopy(nameHash, 0, toReturn, namePrefix.length, nameHash.length); - return toReturn; - } - - /** - * This replicates the TPM 2.0 CryptKDFa function to an extent. It will only work for generation - * that uses SHA-256, and will only generate values of 32 B or less. Counters above zero and - * multiple contexts are not supported in this implementation. This should work for all uses of - * the KDF for TPM2_MakeCredential. - * - * @param seed random value used to generate the key - * @param label first portion of message used to generate key - * @param context second portion of message used to generate key - * @param sizeInBytes size of key to generate in bytes - * @return the derived key - * @throws NoSuchAlgorithmException Wrong crypto algorithm selected - * @throws InvalidKeyException Invalid key used - */ - @SuppressWarnings("magicnumber") - private byte[] cryptKDFa(final byte[] seed, final String label, final byte[] context, - final int sizeInBytes) - throws NoSuchAlgorithmException, InvalidKeyException { - ByteBuffer b = ByteBuffer.allocate(4); - b.putInt(1); - byte[] counter = b.array(); - // get the label - String labelWithEnding = label; - if (label.charAt(label.length() - 1) != "\0".charAt(0)) { - labelWithEnding = label + "\0"; - } - byte[] labelBytes = labelWithEnding.getBytes(); - b = ByteBuffer.allocate(4); - b.putInt(sizeInBytes * 8); - byte[] desiredSizeInBits = b.array(); - int sizeOfMessage = 8 + labelBytes.length; - if (context != null) { - sizeOfMessage += context.length; - } - byte[] message = new byte[sizeOfMessage]; - int marker = 0; - System.arraycopy(counter, 0, message, marker, 4); - marker += 4; - System.arraycopy(labelBytes, 0, message, marker, labelBytes.length); - marker += labelBytes.length; - if (context != null) { - System.arraycopy(context, 0, message, marker, context.length); - marker += context.length; - } - System.arraycopy(desiredSizeInBits, 0, message, marker, 4); - Mac hmac; - byte[] toReturn = new byte[sizeInBytes]; - - hmac = Mac.getInstance("HmacSHA256"); - SecretKeySpec hmacKey = new SecretKeySpec(seed, hmac.getAlgorithm()); - hmac.init(hmacKey); - hmac.update(message); - byte[] hmacResult = hmac.doFinal(); - System.arraycopy(hmacResult, 0, toReturn, 0, sizeInBytes); - return toReturn; - } - - /** - * Computes the sha256 hash of the given blob. - * @param blob byte array to take the hash of - * @return sha256 hash of blob - * @throws NoSuchAlgorithmException improper algorithm selected - */ - private byte[] sha256hash(final byte[] blob) throws NoSuchAlgorithmException { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - md.update(blob); - return md.digest(); + return credentials; } } diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/IdentityRequestHandler.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/IdentityRequestHandler.java index 0bf80e36..142179e4 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/IdentityRequestHandler.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/IdentityRequestHandler.java @@ -9,9 +9,9 @@ import hirs.attestationca.persist.entity.userdefined.certificate.EndorsementCred import hirs.attestationca.persist.entity.userdefined.certificate.PlatformCredential; import hirs.attestationca.persist.entity.userdefined.report.DeviceInfoReport; import hirs.attestationca.persist.enums.AppraisalStatus; -import hirs.attestationca.persist.exceptions.CertificateProcessingException; import hirs.attestationca.persist.exceptions.IdentityProcessingException; import hirs.attestationca.persist.provision.helper.CredentialManagementHelper; +import hirs.attestationca.persist.provision.helper.ProvisionUtils; import hirs.attestationca.persist.service.SupplyChainValidationService; import hirs.structs.converters.SimpleStructBuilder; import hirs.structs.converters.StructConverter; @@ -27,29 +27,13 @@ import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.SerializationUtils; -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.OAEPParameterSpec; -import javax.crypto.spec.PSource; -import javax.crypto.spec.SecretKeySpec; import java.io.IOException; import java.math.BigInteger; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; -import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; -import java.security.spec.MGF1ParameterSpec; -import java.util.HashSet; import java.util.LinkedList; import java.util.List; -import java.util.Set; @Log4j2 public class IdentityRequestHandler extends AbstractRequestHandler { @@ -109,7 +93,8 @@ public class IdentityRequestHandler extends AbstractRequestHandler { IdentityRequestEnvelope challenge = structConverter.convert(identityRequest, IdentityRequestEnvelope.class); - byte[] identityProof = unwrapIdentityRequest(challenge.getRequest()); + byte[] identityProof = unwrapIdentityRequest(structConverter.convert(challenge.getRequest(), + IdentityRequest.class)); // the decrypted symmetric blob should be in the format of an IdentityProof. Use the // struct converter to generate it. IdentityProof proof = structConverter.convert(identityProof, IdentityProof.class); @@ -130,7 +115,7 @@ public class IdentityRequestHandler extends AbstractRequestHandler { BigInteger publicKeyModulus = Certificate.getPublicKeyModulus( endorsementCredential.getX509Certificate()); if (publicKeyModulus != null) { - ekPublicKey = assemblePublicKey(publicKeyModulus.toByteArray()); + ekPublicKey = ProvisionUtils.assemblePublicKey(publicKeyModulus.toByteArray()); } else { throw new IdentityProcessingException("TPM 1.2 Provisioning requires EK " + "Credentials to be created with RSA"); @@ -142,7 +127,7 @@ public class IdentityRequestHandler extends AbstractRequestHandler { log.warn("EKC was not in the identity proof from the client. Checking for uploads."); // Check if the EC was uploaded ekPublicKey = - assemblePublicKey(new String(challenge.getEndorsementCredentialModulus())); + ProvisionUtils.assemblePublicKey(new String(challenge.getEndorsementCredentialModulus())); endorsementCredential = getEndorsementCredential(ekPublicKey); } else { log.warn("Zero-length endorsement credential received in identity request."); @@ -222,7 +207,8 @@ public class IdentityRequestHandler extends AbstractRequestHandler { final List platformCredentials, final Device device) { // decrypt the asymmetric / symmetric blobs log.debug("unwrapping identity request"); - byte[] identityProof = unwrapIdentityRequest(challenge.getRequest()); + byte[] identityProof = unwrapIdentityRequest( + structConverter.convert(challenge.getRequest(), IdentityRequest.class)); // the decrypted symmetric blob should be in the format of an IdentityProof. Use the // struct converter to generate it. @@ -230,17 +216,19 @@ public class IdentityRequestHandler extends AbstractRequestHandler { // generate a session key and convert to byte array log.debug("generating symmetric key for response"); - SymmetricKey sessionKey = generateSymmetricKey(); + SymmetricKey sessionKey = ProvisionUtils.generateSymmetricKey(); // generate the asymmetric contents for the identity response log.debug("generating asymmetric contents for response"); - byte[] asymmetricContents = generateAsymmetricContents(proof, sessionKey, ekPublicKey); + byte[] asymmetricContents = ProvisionUtils.generateAsymmetricContents( + structConverter.convert(proof.getIdentityKey()), + structConverter.convert(sessionKey), ekPublicKey); // generate the identity credential log.debug("generating credential from identity proof"); // transform the public key struct into a public key - PublicKey publicKey = assemblePublicKey(proof.getIdentityKey().getStorePubKey().getKey()); + PublicKey publicKey = ProvisionUtils.assemblePublicKey(proof.getIdentityKey().getStorePubKey().getKey()); X509Certificate credential = generateCredential(publicKey, endorsementCredential, platformCredentials, device.getDeviceInfo() .getNetworkInfo() @@ -249,7 +237,7 @@ public class IdentityRequestHandler extends AbstractRequestHandler { // generate the attestation using the credential and the key for this session log.debug("generating symmetric response"); - SymmetricAttestation attestation = generateAttestation(credential, sessionKey); + SymmetricAttestation attestation = ProvisionUtils.generateAttestation(credential, sessionKey); // construct the response with the both the asymmetric contents and the CA attestation IdentityResponseEnvelope identityResponse = @@ -258,7 +246,7 @@ public class IdentityRequestHandler extends AbstractRequestHandler { .set("symmetricAttestation", attestation).build(); // save new attestation certificate - byte[] derEncodedAttestationCertificate = getDerEncodedCertificate(credential); + byte[] derEncodedAttestationCertificate = ProvisionUtils.getDerEncodedCertificate(credential); saveAttestationCertificate(this.certificateRepository, derEncodedAttestationCertificate, endorsementCredential, platformCredentials, device); @@ -269,13 +257,11 @@ public class IdentityRequestHandler extends AbstractRequestHandler { * Unwraps a given identityRequest. That is to say, decrypt the asymmetric portion of a data * structure to determine the method to decrypt the symmetric portion. * - * @param identityRequest + * @param request * to be decrypted * @return the decrypted symmetric portion of an identity request. */ - private byte[] unwrapIdentityRequest(final byte[] identityRequest) { - IdentityRequest request = structConverter.convert(identityRequest, IdentityRequest.class); - + private byte[] unwrapIdentityRequest(final IdentityRequest request) { // in case the TPM did not specify the IV, it must be extracted from the symmetric blob. // the IV will then be the the first block of the cipher text. final byte[] iv; @@ -283,7 +269,7 @@ public class IdentityRequestHandler extends AbstractRequestHandler { if (symmetricKeyParams != null && symmetricKeyParams.getParams() != null) { iv = symmetricKeyParams.getParams().getIv(); } else { - iv = extractInitialValue(request); + iv = ProvisionUtils.extractInitialValue(request); } // determine the encryption scheme from the algorithm @@ -292,14 +278,14 @@ public class IdentityRequestHandler extends AbstractRequestHandler { // decrypt the asymmetric blob byte[] decryptedAsymmetricBlob = - decryptAsymmetricBlob(request.getAsymmetricBlob(), asymmetricScheme); + ProvisionUtils.decryptAsymmetricBlob(request.getAsymmetricBlob(), asymmetricScheme, getPrivateKey()); // construct our symmetric key structure from the decrypted asymmetric blob SymmetricKey symmetricKey = structConverter.convert(decryptedAsymmetricBlob, SymmetricKey.class); byte[] decryptedSymmetricBlob = - decryptSymmetricBlob(request.getSymmetricBlob(), symmetricKey.getKey(), iv, + ProvisionUtils.decryptSymmetricBlob(request.getSymmetricBlob(), symmetricKey.getKey(), iv, "AES/CBC/PKCS5Padding"); // decrypt the symmetric blob @@ -356,219 +342,4 @@ public class IdentityRequestHandler extends AbstractRequestHandler { return credentials; } - /** - * Will attempt to decrypt the asymmetric blob that originated from an - * {@link hirs.structs.elements.tpm.IdentityRequest} using the cipher transformation. - * - * @param asymmetricBlob to be decrypted - * @param scheme to decrypt with - * @return decrypted blob - */ - private byte[] decryptAsymmetricBlob(final byte[] asymmetricBlob, final EncryptionScheme scheme) { - try { - // create a cipher from the specified transformation - Cipher cipher = Cipher.getInstance(scheme.toString()); - - switch (scheme) { - case OAEP: - OAEPParameterSpec spec = - new OAEPParameterSpec("Sha1", "MGF1", MGF1ParameterSpec.SHA1, - new PSource.PSpecified("".getBytes())); - - cipher.init(Cipher.PRIVATE_KEY, privateKey, spec); - break; - default: - // initialize the cipher to decrypt using the ACA private key. - cipher.init(Cipher.DECRYPT_MODE, privateKey); - } - - cipher.update(asymmetricBlob); - - return cipher.doFinal(); - } catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException - | BadPaddingException | IllegalBlockSizeException - | InvalidAlgorithmParameterException e) { - throw new IdentityProcessingException( - "Encountered error while decrypting asymmetric blob of an identity request: " - + e.getMessage(), e); - } - } - - /** - * Will attempt to decrypt the symmetric blob that originated from an - * {@link hirs.structs.elements.tpm.IdentityRequest} using the specified symmetric key - * and cipher transformation. - * - * @param symmetricBlob to be decrypted - * @param symmetricKey to use to decrypt - * @param iv to use with decryption cipher - * @param transformation of the cipher - * @return decrypted symmetric blob - */ - private byte[] decryptSymmetricBlob(final byte[] symmetricBlob, final byte[] symmetricKey, - final byte[] iv, final String transformation) { - try { - // create a cipher from the specified transformation - Cipher cipher = Cipher.getInstance(transformation); - - // generate a key specification to initialize the cipher - SecretKeySpec keySpec = new SecretKeySpec(symmetricKey, "AES"); - - // initialize the cipher to decrypt using the symmetric key - cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv)); - - // decrypt the symmetric blob - return cipher.doFinal(symmetricBlob); - } catch (IllegalBlockSizeException | InvalidKeyException | NoSuchAlgorithmException - | BadPaddingException | NoSuchPaddingException - | InvalidAlgorithmParameterException exception) { - log.error("Encountered error while decrypting symmetric blob of an identity request: " - + exception.getMessage(), exception); - } - - return new byte[0]; - } - - private SymmetricKey generateSymmetricKey() { - // create a session key for the CA contents - byte[] responseSymmetricKey = - generateRandomBytes(DEFAULT_IV_SIZE); - - // create a symmetric key struct for the CA contents - SymmetricKey sessionKey = - new SimpleStructBuilder<>(SymmetricKey.class) - .set("algorithmId", SymmetricKey.ALGORITHM_AES) - .set("encryptionScheme", SymmetricKey.SCHEME_CBC) - .set("key", responseSymmetricKey).build(); - return sessionKey; - } - - /** - * Generate asymmetric contents part of the identity response. - * - * @param proof identity requests symmetric contents, otherwise, the identity proof - * @param symmetricKey identity response session key - * @param publicKey of the EK certificate contained within the identity proof - * @return encrypted asymmetric contents - */ - byte[] generateAsymmetricContents(final IdentityProof proof, - final SymmetricKey symmetricKey, - final PublicKey publicKey) { - try { - // obtain the identity key from the identity proof - byte[] identityKey = structConverter.convert(proof.getIdentityKey()); - byte[] sessionKey = structConverter.convert(symmetricKey); - - // create a SHA1 digest of the identity key - MessageDigest md = MessageDigest.getInstance("SHA-1"); - md.update(identityKey); - - // generate the digest - byte[] identityDigest = md.digest(); - - // combine the session key with the digest of the identity key - byte[] asymmetricContents = ArrayUtils.addAll(sessionKey, identityDigest); - - // encrypt the asymmetric contents and return - OAEPParameterSpec oaepSpec = - new OAEPParameterSpec("Sha1", "MGF1", MGF1ParameterSpec.SHA1, - new PSource.PSpecified("TCPA".getBytes())); - - // initialize the asymmetric cipher using the default OAEP transformation - Cipher cipher = Cipher.getInstance(EncryptionScheme.OAEP.toString()); - - // initialize the cipher using the public spec with the additional OAEP specification - cipher.init(Cipher.PUBLIC_KEY, publicKey, oaepSpec); - - return cipher.doFinal(asymmetricContents); - } catch (NoSuchAlgorithmException | IllegalBlockSizeException | NoSuchPaddingException - | InvalidKeyException | BadPaddingException - | InvalidAlgorithmParameterException e) { - throw new CertificateProcessingException( - "Encountered error while generating ACA session key: " + e.getMessage(), e); - } - } - - /** - * Extracts the IV from the identity request. That is, take the first block of data from the - * symmetric blob and treat that as the IV. This modifies the original symmetric block. - * - * @param identityRequest to extract the IV from - * @return the IV from the identity request - */ - private byte[] extractInitialValue(final IdentityRequest identityRequest) { - - // make a reference to the symmetric blob - byte[] symmetricBlob = identityRequest.getSymmetricBlob(); - - // create the IV - byte[] iv = new byte[DEFAULT_IV_SIZE]; - - // initialize a new symmetric blob with the length of the original minus the IV - byte[] updatedBlob = new byte[symmetricBlob.length - iv.length]; - - // copy the IV out of the original symmetric blob - System.arraycopy(symmetricBlob, 0, iv, 0, iv.length); - - // copy everything but the IV out of the original blob into the new blob - System.arraycopy(symmetricBlob, iv.length, updatedBlob, 0, updatedBlob.length); - - // reassign the symmetric blob to the request. - identityRequest.setSymmetricBlob(updatedBlob); - - return iv; - } - - /** - * Generate the Identity Response using the identity credential and the session key. - * - * @param credential the identity credential - * @param symmetricKey generated session key for this request/response chain - * @return identity response for an identity request - */ - SymmetricAttestation generateAttestation(final X509Certificate credential, - final SymmetricKey symmetricKey) { - try { - // initialize the symmetric cipher - Cipher aesCipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); - - // generate a key specification to initialize the cipher - SecretKeySpec keySpec = new SecretKeySpec(symmetricKey.getKey(), "AES"); - - // fill IV with random bytes - byte[] credentialIV = generateRandomBytes(DEFAULT_IV_SIZE); - - // create IV encryption parameter specification - IvParameterSpec ivParameterSpec = new IvParameterSpec(credentialIV); - - // initialize the cipher to decrypt using the symmetric key - aesCipher.init(Cipher.ENCRYPT_MODE, keySpec, ivParameterSpec); - - // encrypt the credential - byte[] encryptedCredential = aesCipher.doFinal(credential.getEncoded()); - - // prepend the IV to the encrypted credential - byte[] credentialBytes = ArrayUtils.addAll(credentialIV, encryptedCredential); - - // create attestation for identity response that contains the credential - SymmetricAttestation attestation = - new SimpleStructBuilder<>(SymmetricAttestation.class) - .set("credential", credentialBytes) - .set("algorithm", - new SimpleStructBuilder<>(SymmetricKeyParams.class) - .set("algorithmId", SymmetricKeyParams.ALGORITHM_AES) - .set("encryptionScheme", - SymmetricKeyParams.SCHEME_CBC_PKCS5PADDING) - .set("signatureScheme", 0).build()).build(); - - return attestation; - - } catch (BadPaddingException | IllegalBlockSizeException | NoSuchAlgorithmException - | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException - | CertificateEncodingException exception) { - throw new CertificateProcessingException( - "Encountered error while generating Identity Response: " - + exception.getMessage(), exception); - } - } } diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/helper/ProvisionUtils.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/helper/ProvisionUtils.java new file mode 100644 index 00000000..a2fbfc3e --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/helper/ProvisionUtils.java @@ -0,0 +1,642 @@ +package hirs.attestationca.persist.provision.helper; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import hirs.attestationca.configuration.provisionerTpm2.ProvisionerTpm2; +import hirs.attestationca.persist.entity.userdefined.info.TPMInfo; +import hirs.attestationca.persist.exceptions.CertificateProcessingException; +import hirs.attestationca.persist.exceptions.IdentityProcessingException; +import hirs.attestationca.persist.exceptions.UnexpectedServerException; +import hirs.structs.converters.SimpleStructBuilder; +import hirs.structs.elements.aca.SymmetricAttestation; +import hirs.structs.elements.tpm.EncryptionScheme; +import hirs.structs.elements.tpm.IdentityRequest; +import hirs.structs.elements.tpm.SymmetricKey; +import hirs.structs.elements.tpm.SymmetricKeyParams; +import hirs.utils.HexUtils; +import hirs.utils.enums.DeviceInfoEnums; +import lombok.extern.log4j.Log4j2; +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.lang3.ArrayUtils; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.Mac; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.OAEPParameterSpec; +import javax.crypto.spec.PSource; +import javax.crypto.spec.SecretKeySpec; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.MGF1ParameterSpec; +import java.security.spec.RSAPublicKeySpec; +import java.util.Date; + +@Log4j2 +public final class ProvisionUtils { + + /** + * The default size for IV blocks. + */ + public final static int DEFAULT_IV_SIZE = 16; + /** + * Defines the well known exponent. + * https://en.wikipedia.org/wiki/65537_(number)#Applications + */ + private final static BigInteger EXPONENT = new BigInteger("010001", DEFAULT_IV_SIZE); + public static final int HMAC_SIZE_LENGTH_BYTES = 2; + public static final int HMAC_KEY_LENGTH_BYTES = 32; + public static final int SEED_LENGTH = 32; + public static final int MAX_SECRET_LENGTH = 32; + public static final int AES_KEY_LENGTH_BYTES = 16; + private static final int TPM2_CREDENTIAL_BLOB_SIZE = 392; + private static final int RSA_MODULUS_LENGTH = 256; + // Constants used to parse out the ak name from the ak public data. Used in generateAkName + private static final String AK_NAME_PREFIX = "000b"; + private static final String AK_NAME_HASH_PREFIX = + "0001000b00050072000000100014000b0800000000000100"; + + /** + * Helper method to parse a byte array into an {@link hirs.attestationca.configuration.provisionerTpm2.ProvisionerTpm2.IdentityClaim}. + * + * @param identityClaim byte array that should be converted to a Protobuf IdentityClaim + * object + * @throws {@link IdentityProcessingException} if byte array could not be parsed + * @return the Protobuf generated Identity Claim object + */ + public static ProvisionerTpm2.IdentityClaim parseIdentityClaim(final byte[] identityClaim) { + try { + return ProvisionerTpm2.IdentityClaim.parseFrom(identityClaim); + } catch (InvalidProtocolBufferException ipbe) { + throw new IdentityProcessingException( + "Could not deserialize Protobuf Identity Claim object.", ipbe); + } + } + + /** + * Helper method to extract a DER encoded ASN.1 certificate from an X509 certificate. + * + * @param certificate the X509 certificate to be converted to DER encoding + * @throws {@link UnexpectedServerException} if error occurs during encoding retrieval + * @return the byte array representing the DER encoded certificate + */ + public static byte[] getDerEncodedCertificate(final X509Certificate certificate) { + try { + return certificate.getEncoded(); + } catch (CertificateEncodingException ceEx) { + log.error("Error converting certificate to ASN.1 DER Encoding.", ceEx); + throw new UnexpectedServerException( + "Encountered error while converting X509 Certificate to ASN.1 DER Encoding: " + + ceEx.getMessage(), ceEx); + } + } + + /** + * Parse public key from public data segment generated by TPM 2.0. + * @param publicArea the public area segment to parse + * @return the RSA public key of the supplied public data + */ + public static RSAPublicKey parsePublicKey(final byte[] publicArea) { + int pubLen = publicArea.length; + if (pubLen < RSA_MODULUS_LENGTH) { + throw new IllegalArgumentException( + "EK or AK public data segment is not long enough"); + } + // public data ends with 256 byte modulus + byte[] modulus = HexUtils.subarray(publicArea, + pubLen - RSA_MODULUS_LENGTH, + pubLen - 1); + return (RSAPublicKey) assemblePublicKey(modulus); + } + + /** + * Constructs a public key where the modulus is in raw form. + * + * @param modulus + * in byte array form + * @return public key using specific modulus and the well known exponent + */ + public static PublicKey assemblePublicKey(final byte[] modulus) { + return assemblePublicKey(Hex.encodeHexString(modulus)); + } + + /** + * Constructs a public key where the modulus is Hex encoded. + * + * @param modulus + * hex encoded modulus + * @return public key using specific modulus and the well known exponent + */ + public static PublicKey assemblePublicKey(final String modulus) { + return assemblePublicKey(new BigInteger(modulus, DEFAULT_IV_SIZE)); + } + + /** + * Assembles a public key using a defined big int modulus and the well known exponent. + */ + public static PublicKey assemblePublicKey(final BigInteger modulus) { + // generate a key spec using mod and exp + RSAPublicKeySpec keySpec = new RSAPublicKeySpec(modulus, EXPONENT); + + // create the public key + try { + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + return keyFactory.generatePublic(keySpec); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + throw new UnexpectedServerException( + "Encountered unexpected error creating public key: " + e.getMessage(), e); + } + } + + /** + * Will attempt to decrypt the asymmetric blob that originated from an + * {@link hirs.structs.elements.tpm.IdentityRequest} using the cipher transformation. + * + * @param asymmetricBlob to be decrypted + * @param scheme to decrypt with + * @param privateKey cipher private key + * @return decrypted blob + */ + public static byte[] decryptAsymmetricBlob(final byte[] asymmetricBlob, + final EncryptionScheme scheme, + final PrivateKey privateKey) { + try { + // create a cipher from the specified transformation + Cipher cipher = Cipher.getInstance(scheme.toString()); + + switch (scheme) { + case OAEP: + OAEPParameterSpec spec = + new OAEPParameterSpec("Sha1", "MGF1", MGF1ParameterSpec.SHA1, + new PSource.PSpecified("".getBytes())); + + cipher.init(Cipher.PRIVATE_KEY, privateKey, spec); + break; + default: + // initialize the cipher to decrypt using the ACA private key. + cipher.init(Cipher.DECRYPT_MODE, privateKey); + } + + cipher.update(asymmetricBlob); + + return cipher.doFinal(); + } catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException + | BadPaddingException | IllegalBlockSizeException + | InvalidAlgorithmParameterException e) { + throw new IdentityProcessingException( + "Encountered error while decrypting asymmetric blob of an identity request: " + + e.getMessage(), e); + } + } + + /** + * Will attempt to decrypt the symmetric blob that originated from an + * {@link hirs.structs.elements.tpm.IdentityRequest} using the specified symmetric key + * and cipher transformation. + * + * @param symmetricBlob to be decrypted + * @param symmetricKey to use to decrypt + * @param iv to use with decryption cipher + * @param transformation of the cipher + * @return decrypted symmetric blob + */ + public static byte[] decryptSymmetricBlob(final byte[] symmetricBlob, final byte[] symmetricKey, + final byte[] iv, final String transformation) { + try { + // create a cipher from the specified transformation + Cipher cipher = Cipher.getInstance(transformation); + + // generate a key specification to initialize the cipher + SecretKeySpec keySpec = new SecretKeySpec(symmetricKey, "AES"); + + // initialize the cipher to decrypt using the symmetric key + cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv)); + + // decrypt the symmetric blob + return cipher.doFinal(symmetricBlob); + } catch (IllegalBlockSizeException | InvalidKeyException | NoSuchAlgorithmException + | BadPaddingException | NoSuchPaddingException + | InvalidAlgorithmParameterException exception) { + log.error("Encountered error while decrypting symmetric blob of an identity request: " + + exception.getMessage(), exception); + } + + return new byte[0]; + } + + public static SymmetricKey generateSymmetricKey() { + // create a session key for the CA contents + byte[] responseSymmetricKey = + generateRandomBytes(DEFAULT_IV_SIZE); + + // create a symmetric key struct for the CA contents + SymmetricKey sessionKey = + new SimpleStructBuilder<>(SymmetricKey.class) + .set("algorithmId", SymmetricKey.ALGORITHM_AES) + .set("encryptionScheme", SymmetricKey.SCHEME_CBC) + .set("key", responseSymmetricKey).build(); + return sessionKey; + } + + /** + * Performs the first step of the TPM 2.0 identity claim process. Takes an ek, ak, and secret + * and then generates a seed that is used to generate AES and HMAC keys. Parses the ak name. + * Encrypts the seed with the public ek. Uses the AES key to encrypt the secret. Uses the HMAC + * key to generate an HMAC to cover the encrypted secret and the ak name. The output is an + * encrypted blob that acts as the first part of a challenge-response authentication mechanism + * to validate an identity claim. + * + * Equivalent to calling tpm2_makecredential using tpm2_tools. + * + * @param ek endorsement key in the identity claim + * @param ak attestation key in the identity claim + * @param secret a nonce + * @return the encrypted blob forming the identity claim challenge + */ + public static ByteString tpm20MakeCredential(final RSAPublicKey ek, final RSAPublicKey ak, + final byte[] secret) { + // check size of the secret + if (secret.length > MAX_SECRET_LENGTH) { + throw new IllegalArgumentException("Secret must be " + MAX_SECRET_LENGTH + + " bytes or smaller."); + } + + // generate a random 32 byte seed + byte[] seed = generateRandomBytes(SEED_LENGTH); + + try { + // encrypt seed with pubEk + Cipher asymCipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding"); + OAEPParameterSpec oaepSpec = new OAEPParameterSpec("SHA-256", "MGF1", + MGF1ParameterSpec.SHA256, new PSource.PSpecified("IDENTITY\0".getBytes())); + asymCipher.init(Cipher.PUBLIC_KEY, ek, oaepSpec); + asymCipher.update(seed); + byte[] encSeed = asymCipher.doFinal(); + + // generate ak name from akMod + byte[] akModTemp = ak.getModulus().toByteArray(); + byte[] akMod = new byte[RSA_MODULUS_LENGTH]; + int startpos = 0; + // BigIntegers are signed, so a modulus that has a first bit of 1 + // will be padded with a zero byte that must be removed + if (akModTemp[0] == 0x00) { + startpos = 1; + } + System.arraycopy(akModTemp, startpos, akMod, 0, RSA_MODULUS_LENGTH); + byte[] akName = ProvisionUtils.generateAkName(akMod); + + // generate AES and HMAC keys from seed + byte[] aesKey = ProvisionUtils.cryptKDFa(seed, "STORAGE", akName, AES_KEY_LENGTH_BYTES); + byte[] hmacKey = ProvisionUtils.cryptKDFa(seed, "INTEGRITY", null, HMAC_KEY_LENGTH_BYTES); + + // use two bytes to add a size prefix on secret + ByteBuffer b; + b = ByteBuffer.allocate(2); + b.putShort((short) (secret.length)); + byte[] secretLength = b.array(); + byte[] secretBytes = new byte[secret.length + 2]; + System.arraycopy(secretLength, 0, secretBytes, 0, 2); + System.arraycopy(secret, 0, secretBytes, 2, secret.length); + + // encrypt size prefix + secret with AES key + Cipher symCipher = Cipher.getInstance("AES/CFB/NoPadding"); + byte[] defaultIv = HexUtils.hexStringToByteArray("00000000000000000000000000000000"); + IvParameterSpec ivSpec = new IvParameterSpec(defaultIv); + symCipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(aesKey, "AES"), ivSpec); + byte[] encSecret = symCipher.doFinal(secretBytes); + + // generate HMAC covering encrypted secret and ak name + Mac integrityHmac = Mac.getInstance("HmacSHA256"); + SecretKeySpec integrityKey = new SecretKeySpec(hmacKey, integrityHmac.getAlgorithm()); + integrityHmac.init(integrityKey); + byte[] message = new byte[encSecret.length + akName.length]; + System.arraycopy(encSecret, 0, message, 0, encSecret.length); + System.arraycopy(akName, 0, message, encSecret.length, akName.length); + integrityHmac.update(message); + byte[] integrity = integrityHmac.doFinal(); + b = ByteBuffer.allocate(2); + b.putShort((short) (HMAC_SIZE_LENGTH_BYTES + HMAC_KEY_LENGTH_BYTES + encSecret.length)); + byte[] topSize = b.array(); + + // return ordered blob of assembled credentials + byte[] bytesToReturn = ProvisionUtils.assembleCredential(topSize, integrity, encSecret, encSeed); + return ByteString.copyFrom(bytesToReturn); + + } catch (BadPaddingException | IllegalBlockSizeException | NoSuchAlgorithmException + | InvalidKeyException | InvalidAlgorithmParameterException + | NoSuchPaddingException e) { + throw new IdentityProcessingException( + "Encountered error while making the identity claim challenge: " + + e.getMessage(), e); + } + } + + /** + * Generate asymmetric contents part of the identity response. + * + * @param identityKey identity requests symmetric contents, otherwise, the identity proof + * @param sessionKey identity response session key + * @param publicKey of the EK certificate contained within the identity proof + * @return encrypted asymmetric contents + */ + public static byte[] generateAsymmetricContents(final byte[] identityKey, + final byte[] sessionKey, + final PublicKey publicKey) { + try { + // create a SHA1 digest of the identity key + MessageDigest md = MessageDigest.getInstance("SHA-1"); + md.update(identityKey); + + // generate the digest + byte[] identityDigest = md.digest(); + + // combine the session key with the digest of the identity key + byte[] asymmetricContents = ArrayUtils.addAll(sessionKey, identityDigest); + + // encrypt the asymmetric contents and return + OAEPParameterSpec oaepSpec = + new OAEPParameterSpec("Sha1", "MGF1", MGF1ParameterSpec.SHA1, + new PSource.PSpecified("TCPA".getBytes())); + + // initialize the asymmetric cipher using the default OAEP transformation + Cipher cipher = Cipher.getInstance(EncryptionScheme.OAEP.toString()); + + // initialize the cipher using the public spec with the additional OAEP specification + cipher.init(Cipher.PUBLIC_KEY, publicKey, oaepSpec); + + return cipher.doFinal(asymmetricContents); + } catch (NoSuchAlgorithmException | IllegalBlockSizeException | NoSuchPaddingException + | InvalidKeyException | BadPaddingException + | InvalidAlgorithmParameterException e) { + throw new CertificateProcessingException( + "Encountered error while generating ACA session key: " + e.getMessage(), e); + } + } + + /** + * Extracts the IV from the identity request. That is, take the first block of data from the + * symmetric blob and treat that as the IV. This modifies the original symmetric block. + * + * @param identityRequest to extract the IV from + * @return the IV from the identity request + */ + public static byte[] extractInitialValue(final IdentityRequest identityRequest) { + // make a reference to the symmetric blob + byte[] symmetricBlob = identityRequest.getSymmetricBlob(); + + // create the IV + byte[] iv = new byte[DEFAULT_IV_SIZE]; + + // initialize a new symmetric blob with the length of the original minus the IV + byte[] updatedBlob = new byte[symmetricBlob.length - iv.length]; + + // copy the IV out of the original symmetric blob + System.arraycopy(symmetricBlob, 0, iv, 0, iv.length); + + // copy everything but the IV out of the original blob into the new blob + System.arraycopy(symmetricBlob, iv.length, updatedBlob, 0, updatedBlob.length); + + // reassign the symmetric blob to the request. + identityRequest.setSymmetricBlob(updatedBlob); + + return iv; + } + + /** + * Generate the Identity Response using the identity credential and the session key. + * + * @param credential the identity credential + * @param symmetricKey generated session key for this request/response chain + * @return identity response for an identity request + */ + public static SymmetricAttestation generateAttestation(final X509Certificate credential, + final SymmetricKey symmetricKey) { + try { + // initialize the symmetric cipher + Cipher aesCipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + + // generate a key specification to initialize the cipher + SecretKeySpec keySpec = new SecretKeySpec(symmetricKey.getKey(), "AES"); + + // fill IV with random bytes + byte[] credentialIV = generateRandomBytes(DEFAULT_IV_SIZE); + + // create IV encryption parameter specification + IvParameterSpec ivParameterSpec = new IvParameterSpec(credentialIV); + + // initialize the cipher to decrypt using the symmetric key + aesCipher.init(Cipher.ENCRYPT_MODE, keySpec, ivParameterSpec); + + // encrypt the credential + byte[] encryptedCredential = aesCipher.doFinal(credential.getEncoded()); + + // prepend the IV to the encrypted credential + byte[] credentialBytes = ArrayUtils.addAll(credentialIV, encryptedCredential); + + // create attestation for identity response that contains the credential + SymmetricAttestation attestation = + new SimpleStructBuilder<>(SymmetricAttestation.class) + .set("credential", credentialBytes) + .set("algorithm", + new SimpleStructBuilder<>(SymmetricKeyParams.class) + .set("algorithmId", SymmetricKeyParams.ALGORITHM_AES) + .set("encryptionScheme", + SymmetricKeyParams.SCHEME_CBC_PKCS5PADDING) + .set("signatureScheme", 0).build()).build(); + + return attestation; + + } catch (BadPaddingException | IllegalBlockSizeException | NoSuchAlgorithmException + | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException + | CertificateEncodingException exception) { + throw new CertificateProcessingException( + "Encountered error while generating Identity Response: " + + exception.getMessage(), exception); + } + } + + @SuppressWarnings("magicnumber") + public static byte[] assembleCredential(final byte[] topSize, final byte[] integrityHmac, + final byte[] encryptedSecret, + final byte[] encryptedSeed) { + /* + * Credential structure breakdown with endianness: + * 0-1 topSize (2), LE + * 2-3 hashsize (2), BE always 0x0020 + * 4-35 integrity HMac (32) + * 36-133 (98 = 32*3 +2) of zeros, copy over from encSecret starting at [36] + * 134-135 (2) LE size, always 0x0001 + * 136-391 (256) copy over with encSeed + * */ + byte[] credentialBlob = new byte[TPM2_CREDENTIAL_BLOB_SIZE]; + credentialBlob[0] = topSize[1]; + credentialBlob[1] = topSize[0]; + credentialBlob[2] = 0x00; + credentialBlob[3] = 0x20; + System.arraycopy(integrityHmac, 0, credentialBlob, 4, 32); + for (int i = 0; i < 98; i++) { + credentialBlob[36 + i] = 0x00; + } + System.arraycopy(encryptedSecret, 0, credentialBlob, 36, encryptedSecret.length); + credentialBlob[134] = 0x00; + credentialBlob[135] = 0x01; + System.arraycopy(encryptedSeed, 0, credentialBlob, 136, 256); + // return the result + return credentialBlob; + } + + /** + * Determines the AK name from the AK Modulus. + * @param akModulus modulus of an attestation key + * @return the ak name byte array + * @throws java.security.NoSuchAlgorithmException Underlying SHA256 method used a bad algorithm + */ + public static byte[] generateAkName(final byte[] akModulus) throws NoSuchAlgorithmException { + byte[] namePrefix = HexUtils.hexStringToByteArray(AK_NAME_PREFIX); + byte[] hashPrefix = HexUtils.hexStringToByteArray(AK_NAME_HASH_PREFIX); + byte[] toHash = new byte[hashPrefix.length + akModulus.length]; + System.arraycopy(hashPrefix, 0, toHash, 0, hashPrefix.length); + System.arraycopy(akModulus, 0, toHash, hashPrefix.length, akModulus.length); + byte[] nameHash = sha256hash(toHash); + byte[] toReturn = new byte[namePrefix.length + nameHash.length]; + System.arraycopy(namePrefix, 0, toReturn, 0, namePrefix.length); + System.arraycopy(nameHash, 0, toReturn, namePrefix.length, nameHash.length); + return toReturn; + } + + /** + * This replicates the TPM 2.0 CryptKDFa function to an extent. It will only work for generation + * that uses SHA-256, and will only generate values of 32 B or less. Counters above zero and + * multiple contexts are not supported in this implementation. This should work for all uses of + * the KDF for TPM2_MakeCredential. + * + * @param seed random value used to generate the key + * @param label first portion of message used to generate key + * @param context second portion of message used to generate key + * @param sizeInBytes size of key to generate in bytes + * @return the derived key + * @throws NoSuchAlgorithmException Wrong crypto algorithm selected + * @throws java.security.InvalidKeyException Invalid key used + */ + @SuppressWarnings("magicnumber") + public static byte[] cryptKDFa(final byte[] seed, final String label, final byte[] context, + final int sizeInBytes) + throws NoSuchAlgorithmException, InvalidKeyException { + ByteBuffer b = ByteBuffer.allocate(4); + b.putInt(1); + byte[] counter = b.array(); + // get the label + String labelWithEnding = label; + if (label.charAt(label.length() - 1) != "\0".charAt(0)) { + labelWithEnding = label + "\0"; + } + byte[] labelBytes = labelWithEnding.getBytes(); + b = ByteBuffer.allocate(4); + b.putInt(sizeInBytes * 8); + byte[] desiredSizeInBits = b.array(); + int sizeOfMessage = 8 + labelBytes.length; + if (context != null) { + sizeOfMessage += context.length; + } + byte[] message = new byte[sizeOfMessage]; + int marker = 0; + System.arraycopy(counter, 0, message, marker, 4); + marker += 4; + System.arraycopy(labelBytes, 0, message, marker, labelBytes.length); + marker += labelBytes.length; + if (context != null) { + System.arraycopy(context, 0, message, marker, context.length); + marker += context.length; + } + System.arraycopy(desiredSizeInBits, 0, message, marker, 4); + Mac hmac; + byte[] toReturn = new byte[sizeInBytes]; + + hmac = Mac.getInstance("HmacSHA256"); + SecretKeySpec hmacKey = new SecretKeySpec(seed, hmac.getAlgorithm()); + hmac.init(hmacKey); + hmac.update(message); + byte[] hmacResult = hmac.doFinal(); + System.arraycopy(hmacResult, 0, toReturn, 0, sizeInBytes); + return toReturn; + } + + /** + * This method takes the provided TPM Quote and splits it between the PCR + * quote and the signature hash. + * @param tpmQuote contains hash values for the quote and the signature + */ + public static String parseTPMQuoteHash(final String tpmQuote) { + if (tpmQuote != null) { + String[] lines = tpmQuote.split(":"); + if (lines[1].contains("signature")) { + return lines[1].replace("signature", "").trim(); + } else { + return lines[1].trim(); + } + } + + return "null"; + } + + /** + * This method takes the provided TPM Quote and splits it between the PCR + * quote and the signature hash. + * @param tpmQuote contains hash values for the quote and the signature + */ + public static String parseTPMQuoteSignature(final String tpmQuote) { + if (tpmQuote != null) { + String[] lines = tpmQuote.split(":"); + + return lines[2].trim(); + } + + return "null"; + } + + /** + * Computes the sha256 hash of the given blob. + * @param blob byte array to take the hash of + * @return sha256 hash of blob + * @throws NoSuchAlgorithmException improper algorithm selected + */ + public static byte[] sha256hash(final byte[] blob) throws NoSuchAlgorithmException { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + md.update(blob); + return md.digest(); + } + + /** + * Generates a array of random bytes. + * + * @param numberOfBytes + * to be generated + * @return byte array filled with the specified number of bytes. + */ + public static byte[] generateRandomBytes(final int numberOfBytes) { + byte[] bytes = new byte[numberOfBytes]; + SecureRandom random = new SecureRandom(); + random.nextBytes(bytes); + return bytes; + } + + @SuppressWarnings("magicnumber") + public static int daysBetween(final Date date1, final Date date2) { + return (int) ((date2.getTime() - date1.getTime()) / (1000 * 60 * 60 * 24)); + } +} From b7357fac9bce5c81c7c16794240c9d55d3e1cff6 Mon Sep 17 00:00:00 2001 From: Cyrus <24922493+cyrus-dev@users.noreply.github.com> Date: Fri, 1 Sep 2023 07:26:22 -0400 Subject: [PATCH 03/16] Added in the configuration mapping to find the new page mapping --- .../java/hirs/attestationca/portal/PersistenceJPAConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/PersistenceJPAConfig.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/PersistenceJPAConfig.java index 285f2f7e..65c28dc3 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/PersistenceJPAConfig.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/PersistenceJPAConfig.java @@ -58,7 +58,7 @@ import java.util.Properties; @PropertySource(value = "file:/etc/hirs/aca/application.properties", ignoreResourceNotFound = true) }) -@ComponentScan({"hirs.attestationca.portal", "hirs.attestationca.portal.page.controllers", "hirs.attestationca.persist.entity", "hirs.attestationca.persist.service"}) +@ComponentScan({"hirs.attestationca.portal", "hirs.attestationca.portal.page.controllers", "hirs.attestationca.persist", "hirs.attestationca.persist.entity", "hirs.attestationca.persist.service"}) @EnableJpaRepositories(basePackages = "hirs.attestationca.persist.entity.manager") public class PersistenceJPAConfig implements WebMvcConfigurer { From 6b063d44e06408e0e5bde5fe28f8494be7f036be Mon Sep 17 00:00:00 2001 From: Cyrus <24922493+cyrus-dev@users.noreply.github.com> Date: Wed, 6 Sep 2023 11:10:57 -0400 Subject: [PATCH 04/16] Updated a couple of files to resolve aca load issues --- .../persist/PersistenceConfiguration.java | 16 ++++++++++++++++ .../entity/userdefined/ReferenceManifest.java | 12 ++++++++++++ .../userdefined/rim/BaseReferenceManifest.java | 17 ----------------- .../portal/PersistenceJPAConfig.java | 8 -------- 4 files changed, 28 insertions(+), 25 deletions(-) diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/PersistenceConfiguration.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/PersistenceConfiguration.java index e9974c26..0526a4a7 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/PersistenceConfiguration.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/PersistenceConfiguration.java @@ -1,6 +1,10 @@ package hirs.attestationca.persist; +import hirs.structs.converters.SimpleStructConverter; +import hirs.structs.converters.StructConverter; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; /** * Persistence Configuration for Spring enabled applications. Constructs a Hibernate SessionFactory @@ -12,6 +16,18 @@ import org.springframework.context.annotation.Configuration; @Configuration public class PersistenceConfiguration { + /** + * Prototyped {@link StructConverter}. In other words, all instances + * returned by this method will be configured identically, but subsequent + * invocations will return a new instance. + * + * @return ready to use {@link StructConverter}. + */ + @Bean + @Scope("prototype") + public static StructConverter structConverter() { + return new SimpleStructConverter(); + } // @Bean // public FilesStorageService filesStorageService() { // FilesStorageServiceImpl filesStorageService = new FilesStorageServiceImpl(new StorageProperties()); diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/ReferenceManifest.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/ReferenceManifest.java index 3be14b56..ea496a00 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/ReferenceManifest.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/ReferenceManifest.java @@ -21,6 +21,7 @@ import org.hibernate.annotations.JdbcTypeCode; import javax.xml.XMLConstants; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.Base64; import java.util.UUID; /** @@ -104,6 +105,9 @@ public class ReferenceManifest extends ArchivableEntity { private String hexDecHash = ""; @Column private String eventLogHash = ""; + @Column + @JsonIgnore + private String base64Hash = ""; /** * Default constructor necessary for Hibernate. @@ -141,6 +145,14 @@ public class ReferenceManifest extends ArchivableEntity { } catch (NoSuchAlgorithmException noSaEx) { log.error(noSaEx); } + this.base64Hash = ""; + try { + digest = MessageDigest.getInstance("SHA-256"); + this.base64Hash = Base64.getEncoder().encodeToString( + digest.digest(rimBytes)); + } catch (NoSuchAlgorithmException noSaEx) { + log.error(noSaEx); + } } /** diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/rim/BaseReferenceManifest.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/rim/BaseReferenceManifest.java index 391fe8ae..74493148 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/rim/BaseReferenceManifest.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/rim/BaseReferenceManifest.java @@ -1,6 +1,5 @@ package hirs.attestationca.persist.entity.userdefined.rim; -import com.fasterxml.jackson.annotation.JsonIgnore; import hirs.attestationca.persist.entity.userdefined.ReferenceManifest; import hirs.utils.SwidResource; import hirs.utils.swid.SwidTagConstants; @@ -32,10 +31,7 @@ import javax.xml.validation.SchemaFactory; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.util.ArrayList; -import java.util.Base64; import java.util.List; /** @@ -54,9 +50,6 @@ public class BaseReferenceManifest extends ReferenceManifest { private static JAXBContext jaxbContext; - @Column - @JsonIgnore - private String base64Hash = ""; @Column private String swidName = null; @Column @@ -120,16 +113,6 @@ public class BaseReferenceManifest extends ReferenceManifest { Element entity; Element link; - MessageDigest digest = null; - this.base64Hash = ""; - try { - digest = MessageDigest.getInstance("SHA-256"); - this.base64Hash = Base64.getEncoder().encodeToString( - digest.digest(rimBytes)); - } catch (NoSuchAlgorithmException noSaEx) { - log.error(noSaEx); - } - // begin parsing valid swid tag if (document != null) { softwareIdentity = (Element) document.getElementsByTagName(SwidTagConstants.SOFTWARE_IDENTITY).item(0); diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/PersistenceJPAConfig.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/PersistenceJPAConfig.java index 65c28dc3..8168d623 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/PersistenceJPAConfig.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/PersistenceJPAConfig.java @@ -1,7 +1,5 @@ package hirs.attestationca.portal; -import hirs.attestationca.persist.service.SupplyChainValidationService; -import hirs.attestationca.persist.service.SupplyChainValidationServiceImpl; import jakarta.annotation.PostConstruct; import lombok.extern.log4j.Log4j2; import org.bouncycastle.jce.provider.BouncyCastleProvider; @@ -28,7 +26,6 @@ import org.springframework.web.servlet.config.annotation.DefaultServletHandlerCo import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -//import javax.sql.DataSource; import javax.sql.DataSource; import java.nio.file.Files; import java.nio.file.Path; @@ -90,11 +87,6 @@ public class PersistenceJPAConfig implements WebMvcConfigurer { return entityManagerBean; } -// @Bean -// public SupplyChainValidationService supplyChainValidationService() { -// return new SupplyChainValidationServiceImpl(); -// } - @Bean public DataSource dataSource() { final DriverManagerDataSource dataSource = new DriverManagerDataSource(); From 6629a87d567d061c396a33133408bd6684efe7f9 Mon Sep 17 00:00:00 2001 From: Cyrus <24922493+cyrus-dev@users.noreply.github.com> Date: Thu, 7 Sep 2023 15:24:16 -0400 Subject: [PATCH 05/16] Looking into the DN missing from the certificate details page I recognized that the setting of the variable for the portal page was using the wrong getter method. --- .../persist/entity/userdefined/Certificate.java | 14 ++++++++++++-- .../certificate/CertificateVariables.java | 16 ++++++++++++---- .../page/utils/CertificateStringMapBuilder.java | 8 ++++---- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/Certificate.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/Certificate.java index 5844bb35..e5d00fe9 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/Certificate.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/Certificate.java @@ -415,12 +415,22 @@ public abstract class Certificate extends ArchivableEntity { case CertificateVariables.RSA512_256_OID: this.signatureAlgorithm = CertificateVariables.RSA512_256_STRING; break; - case CertificateVariables.ECDSA_OID: - this.signatureAlgorithm = CertificateVariables.ECDSA_STRING; + case CertificateVariables.ECDSA_SHA1_OID: + this.signatureAlgorithm = CertificateVariables.ECDSA_SHA1_STRING; break; case CertificateVariables.ECDSA_SHA224_OID: this.signatureAlgorithm = CertificateVariables.ECDSA_SHA224_STRING; break; + case CertificateVariables.ECDSA_SHA256_OID: + this.signatureAlgorithm = CertificateVariables.ECDSA_SHA256_STRING; + break; + case CertificateVariables.ECDSA_SHA384_OID: + this.signatureAlgorithm = CertificateVariables.ECDSA_SHA384_STRING; + break; + case CertificateVariables.ECDSA_SHA512_OID: + this.signatureAlgorithm = CertificateVariables.ECDSA_SHA512_STRING; + break; + default: break; } diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/CertificateVariables.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/CertificateVariables.java index 621ef771..20b89fc7 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/CertificateVariables.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/CertificateVariables.java @@ -28,20 +28,28 @@ public class CertificateVariables { public static final String KEY_USAGE_CS = "CRL SIGN"; public static final String KEY_USAGE_EO = "ENCIPHER ONLY"; public static final String KEY_USAGE_DO = "DECIPHER ONLY"; - public static final String ECDSA_OID = "1.2.840.10045.4.3.2"; - public static final String ECDSA_SHA224_OID = "1.2.840.10045.4.1"; + public static final String RSA1_OID = "1.2.840.113549.1.1.5"; public static final String RSA256_OID = "1.2.840.113549.1.1.11"; public static final String RSA384_OID = "1.2.840.113549.1.1.12"; public static final String RSA512_OID = "1.2.840.113549.1.1.13"; public static final String RSA224_OID = "1.2.840.113549.1.1.14"; public static final String RSA512_224_OID = "1.2.840.113549.1.1.15"; public static final String RSA512_256_OID = "1.2.840.113549.1.1.16"; + public static final String RSA1_STRING = "SHA1WithRSA"; + public static final String RSA224_STRING = "SHA224WithRSA"; public static final String RSA256_STRING = "SHA256WithRSA"; public static final String RSA384_STRING = "SHA384WithRSA"; - public static final String RSA224_STRING = "SHA224WithRSA"; public static final String RSA512_STRING = "SHA512WithRSA"; public static final String RSA512_224_STRING = "SHA512-224WithRSA"; public static final String RSA512_256_STRING = "SHA512-256WithRSA"; - public static final String ECDSA_STRING = "SHA256WithECDSA"; + public static final String ECDSA_SHA1_OID = "1.2.840.10045.4.1"; + public static final String ECDSA_SHA224_OID = "1.2.840.10045.4.3.1"; + public static final String ECDSA_SHA256_OID = "1.2.840.10045.4.3.2"; + public static final String ECDSA_SHA384_OID = "1.2.840.10045.4.3.3"; + public static final String ECDSA_SHA512_OID = "1.2.840.10045.4.3.4"; + public static final String ECDSA_SHA1_STRING = "SHA1WithECDSA"; public static final String ECDSA_SHA224_STRING = "SHA224WithECDSA"; + public static final String ECDSA_SHA256_STRING = "SHA256WithECDSA"; + public static final String ECDSA_SHA384_STRING = "SHA384WithECDSA"; + public static final String ECDSA_SHA512_STRING = "SHA512WithECDSA"; } diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/utils/CertificateStringMapBuilder.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/utils/CertificateStringMapBuilder.java index e5825507..c084c8be 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/utils/CertificateStringMapBuilder.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/utils/CertificateStringMapBuilder.java @@ -47,7 +47,7 @@ public final class CertificateStringMapBuilder { HashMap data = new HashMap<>(); if (certificate != null) { - data.put("issuer", certificate.getHolderIssuer()); + data.put("issuer", certificate.getIssuer()); //Serial number in hex value data.put("serialNumber", Hex.toHexString(certificate.getSerialNumber().toByteArray())); if (!certificate.getAuthoritySerialNumber().equals(BigInteger.ZERO)) { @@ -66,9 +66,9 @@ public final class CertificateStringMapBuilder { if (certificate.getSubject() != null) { data.put("subject", certificate.getSubject()); - if (certificate.getHolderIssuer() != null) { + if (certificate.getIssuer() != null) { data.put("isSelfSigned", - String.valueOf(certificate.getHolderIssuer().equals(certificate.getSubject()))); + String.valueOf(certificate.getIssuer().equals(certificate.getSubject()))); } else { data.put("isSelfSigned", "false"); } @@ -107,7 +107,7 @@ public final class CertificateStringMapBuilder { if (missingCert != null) { data.put("missingChainIssuer", String.format("Missing %s from the chain.", - missingCert.getHolderIssuer())); + missingCert.getIssuer())); } List certificates = certificateRepository.findBySubjectSorted( certificate.getIssuerSorted(), "CertificateAuthorityCredential"); From 7337c115cac8e1cfc46cb3f8d3d1ee3b4b219f6a Mon Sep 17 00:00:00 2001 From: Cyrus <24922493+cyrus-dev@users.noreply.github.com> Date: Fri, 8 Sep 2023 07:59:58 -0400 Subject: [PATCH 06/16] Copying the json files for component class and vendor table were taken out of the set up script. They are now causing errors because the files can't be found. --- .../certificate/attributes/ComponentClass.java | 2 +- .../main/java/hirs/utils/tpm/eventlog/uefi/UefiGuid.java | 2 +- package/scripts/aca/aca_setup.sh | 8 +++++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/attributes/ComponentClass.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/attributes/ComponentClass.java index 40d9a5cc..a666f4e3 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/attributes/ComponentClass.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/attributes/ComponentClass.java @@ -30,7 +30,7 @@ public class ComponentClass { private static final String TCG_COMPONENT_REGISTRY = "2.23.133.18.3.1"; private static final String SMBIOS_COMPONENT_REGISTRY = "2.23.133.18.3.3"; private static final Path JSON_PATH = FileSystems.getDefault() - .getPath("/opt", "hirs", "default-properties", "component-class.json"); + .getPath("/etc", "hirs/aca", "default-properties", "component-class.json"); private static final String OTHER_STRING = "Other"; private static final String UNKNOWN_STRING = "Unknown"; diff --git a/HIRS_Utils/src/main/java/hirs/utils/tpm/eventlog/uefi/UefiGuid.java b/HIRS_Utils/src/main/java/hirs/utils/tpm/eventlog/uefi/UefiGuid.java index 5530258c..7ceecdc0 100644 --- a/HIRS_Utils/src/main/java/hirs/utils/tpm/eventlog/uefi/UefiGuid.java +++ b/HIRS_Utils/src/main/java/hirs/utils/tpm/eventlog/uefi/UefiGuid.java @@ -24,7 +24,7 @@ public class UefiGuid { private static final int UUID_EPOCH_DIVISOR = 10000; private static final Path JSON_PATH = FileSystems.getDefault().getPath("/opt", - "hirs", "default-properties", "vendor-table.json"); + "hirs/aca", "default-properties", "vendor-table.json"); private JsonObject uefiVendorRef; /** * guid byte array. diff --git a/package/scripts/aca/aca_setup.sh b/package/scripts/aca/aca_setup.sh index 9cb7483b..bf685637 100755 --- a/package/scripts/aca/aca_setup.sh +++ b/package/scripts/aca/aca_setup.sh @@ -6,6 +6,9 @@ LOG_FILE_NAME="hirs_aca_install_"$(date +%Y-%m-%d).log LOG_DIR="/var/log/hirs/" LOG_FILE="$LOG_DIR$LOG_FILE_NAME" HIRS_PROP_DIR="/opt/hirs/default-properties" +HIRS_JSON_DIR="/etc/hirs/aca/default-properties" +COMP_JSON='../../../HIRS_AttestationCA/src/main/resources/component-class.json' +VENDOR_TABLE='../../../HIRS_AttestationCA/src/main/resources/vendor-table.json' help () { echo " Setup script for the HIRS ACA" @@ -55,7 +58,10 @@ done set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters -mkdir -p $HIRS_CONF_DIR $LOG_DIR $HIRS_PROP_DIR +mkdir -p $HIRS_CONF_DIR $LOG_DIR $HIRS_PROP_DIR $HIRS_JSON_DIR + +cp -n $COMP_JSON $HIRS_JSON_DIR/ +cp -n $VENDOR_TABLE $HIRS_JSON_DIR/ echo "ACA setup log file is $LOG_FILE" From 6bc6fa1cf11c9e20c3b962503d93570a05a341a1 Mon Sep 17 00:00:00 2001 From: Cyrus <24922493+cyrus-dev@users.noreply.github.com> Date: Mon, 11 Sep 2023 16:03:01 -0400 Subject: [PATCH 07/16] Added code to check for multi pem single files --- .../entity/userdefined/Certificate.java | 157 ++---------------- .../CertificatePageController.java | 25 +++ 2 files changed, 35 insertions(+), 147 deletions(-) diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/Certificate.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/Certificate.java index e5d00fe9..fda9d4c8 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/Certificate.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/Certificate.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.google.common.base.Preconditions; import hirs.attestationca.persist.entity.ArchivableEntity; import hirs.attestationca.persist.entity.userdefined.certificate.CertificateVariables; +import hirs.attestationca.persist.util.CredentialHelper; import hirs.utils.HexUtils; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -72,7 +73,6 @@ import java.util.List; import java.util.ListIterator; import java.util.Objects; - /** * This class enables the persistence of a single X509 certificates or X509 attribute certificate. * It stores certain attributes separately from the serialized certificate to enable querying on @@ -312,16 +312,12 @@ public abstract class Certificate extends ArchivableEntity { // check for and handle possible PEM base 64 encoding String possiblePem = new String(certificateBytes, StandardCharsets.UTF_8); - if (isPEM(possiblePem)) { - possiblePem = possiblePem.replace(CertificateVariables.PEM_HEADER, ""); - possiblePem = possiblePem.replace(CertificateVariables.PEM_FOOTER, ""); - possiblePem = possiblePem.replace(CertificateVariables.PEM_ATTRIBUTE_HEADER, ""); - possiblePem = possiblePem.replace(CertificateVariables.PEM_ATTRIBUTE_FOOTER, ""); - this.certificateBytes = Base64.decode(possiblePem); + if (CredentialHelper.isPEM(possiblePem)) { + this.certificateBytes = CredentialHelper.stripPemHeaderFooter(possiblePem); } AuthorityKeyIdentifier authKeyIdentifier; - this.certificateBytes = trimCertificate(this.certificateBytes); + this.certificateBytes = CredentialHelper.trimCertificate(this.certificateBytes); // Extract certificate data switch (getCertificateType()) { @@ -345,8 +341,8 @@ public abstract class Certificate extends ArchivableEntity { this.beginValidity = x509Certificate.getNotBefore(); this.endValidity = x509Certificate.getNotAfter(); this.holderSerialNumber = BigInteger.ZERO; - this.issuerSorted = parseSortDNs(this.issuer); - this.subjectSorted = parseSortDNs(this.subject); + this.issuerSorted = CredentialHelper.parseSortDNs(this.issuer); + this.subjectSorted = CredentialHelper.parseSortDNs(this.subject); this.policyConstraints = x509Certificate .getExtensionValue(POLICY_CONSTRAINTS); authKeyIdentifier = AuthorityKeyIdentifier @@ -415,22 +411,12 @@ public abstract class Certificate extends ArchivableEntity { case CertificateVariables.RSA512_256_OID: this.signatureAlgorithm = CertificateVariables.RSA512_256_STRING; break; - case CertificateVariables.ECDSA_SHA1_OID: - this.signatureAlgorithm = CertificateVariables.ECDSA_SHA1_STRING; + case CertificateVariables.ECDSA_OID: + this.signatureAlgorithm = CertificateVariables.ECDSA_STRING; break; case CertificateVariables.ECDSA_SHA224_OID: this.signatureAlgorithm = CertificateVariables.ECDSA_SHA224_STRING; break; - case CertificateVariables.ECDSA_SHA256_OID: - this.signatureAlgorithm = CertificateVariables.ECDSA_SHA256_STRING; - break; - case CertificateVariables.ECDSA_SHA384_OID: - this.signatureAlgorithm = CertificateVariables.ECDSA_SHA384_STRING; - break; - case CertificateVariables.ECDSA_SHA512_OID: - this.signatureAlgorithm = CertificateVariables.ECDSA_SHA512_STRING; - break; - default: break; } @@ -448,7 +434,7 @@ public abstract class Certificate extends ArchivableEntity { this.signature = attCert.getSignatureValue().getBytes(); this.issuer = getAttributeCertificateIssuerNames( attCertInfo.getIssuer())[0].toString(); - this.issuerSorted = parseSortDNs(this.issuer); + this.issuerSorted = CredentialHelper.parseSortDNs(this.issuer); // Parse notBefore and notAfter dates this.beginValidity = recoverDate(attCertInfo @@ -478,53 +464,6 @@ public abstract class Certificate extends ArchivableEntity { this.certAndTypeHash = Objects.hash(certificateHash, getClass().getSimpleName()); } - @SuppressWarnings("magicnumber") - private byte[] trimCertificate(final byte[] certificateBytes) { - int certificateStart = 0; - int certificateLength = 0; - ByteBuffer certificateByteBuffer = ByteBuffer.wrap(certificateBytes); - - StringBuilder malformedCertStringBuilder = new StringBuilder(CertificateVariables.MALFORMED_CERT_MESSAGE); - while (certificateByteBuffer.hasRemaining()) { - // Check if there isn't an ASN.1 structure in the provided bytes - if (certificateByteBuffer.remaining() <= 2) { - throw new IllegalArgumentException(malformedCertStringBuilder - .append(" No certificate length field could be found.").toString()); - } - - // Look for first ASN.1 Sequence marked by the two bytes (0x30) and (0x82) - // The check advances our position in the ByteBuffer by one byte - int currentPosition = certificateByteBuffer.position(); - if (certificateByteBuffer.get() == (byte) 0x30 - && certificateByteBuffer.get(currentPosition + 1) == (byte) 0x82) { - // Check if we have anything more in the buffer than an ASN.1 Sequence header - if (certificateByteBuffer.remaining() <= 3) { - throw new IllegalArgumentException(malformedCertStringBuilder - .append(" Certificate is nothing more than ASN.1 Sequence.") - .toString()); - } - // Mark the start of the first ASN.1 Sequence / Certificate Body - certificateStart = currentPosition; - - // Parse the length as the 2-bytes following the start of the ASN.1 Sequence - certificateLength = Short.toUnsignedInt( - certificateByteBuffer.getShort(currentPosition + 2)); - // Add the 4 bytes that comprise the start of the ASN.1 Sequence and the length - certificateLength += 4; - break; - } - } - - if (certificateStart + certificateLength > certificateBytes.length) { - throw new IllegalArgumentException(malformedCertStringBuilder - .append(" Value of certificate length field extends beyond length") - .append(" of provided certificate.").toString()); - } - // Return bytes representing the main certificate body - return Arrays.copyOfRange(certificateBytes, certificateStart, - certificateStart + certificateLength); - } - /** * Getter for the CRL Distribution that is reference by the Revocation Locator * on the portal. @@ -684,10 +623,6 @@ public abstract class Certificate extends ArchivableEntity { return CertificateType.INVALID_CERTIFICATE; } - private boolean isPEM(final String possiblePEM) { - return possiblePEM.contains(CertificateVariables.PEM_HEADER) - || possiblePEM.contains(CertificateVariables.PEM_ATTRIBUTE_HEADER); - } private String parseKeyUsage(final boolean[] bools) { StringBuilder sb = new StringBuilder(); @@ -695,7 +630,7 @@ public abstract class Certificate extends ArchivableEntity { if (bools != null) { for (int i = 0; i < bools.length; i++) { if (bools[i]) { - sb.append(getKeyUsageString(i)); + sb.append(CredentialHelper.getKeyUsageString(i)); } } } @@ -703,49 +638,6 @@ public abstract class Certificate extends ArchivableEntity { return sb.toString(); } - /** - * Return the string associated with the boolean slot. - * @param bit associated with the location in the array. - * @return string value of the bit set. - */ - private String getKeyUsageString(final int bit) { - String tempStr = ""; - - switch (bit) { - case CertificateVariables.KEY_USAGE_BIT0: - tempStr = String.format("%s%n", CertificateVariables.KEY_USAGE_DS); - break; - case CertificateVariables.KEY_USAGE_BIT1: - tempStr = String.format("%s%n", CertificateVariables.KEY_USAGE_NR); - break; - case CertificateVariables.KEY_USAGE_BIT2: - tempStr = String.format("%s%n", CertificateVariables.KEY_USAGE_KE); - break; - case CertificateVariables.KEY_USAGE_BIT3: - tempStr = String.format("%s%n", CertificateVariables.KEY_USAGE_DE); - break; - case CertificateVariables.KEY_USAGE_BIT4: - tempStr = String.format("%s%n", CertificateVariables.KEY_USAGE_KA); - break; - case CertificateVariables.KEY_USAGE_BIT5: - tempStr = String.format("%s%n", CertificateVariables.KEY_USAGE_KC); - break; - case CertificateVariables.KEY_USAGE_BIT6: - tempStr = String.format("%s%n", CertificateVariables.KEY_USAGE_CS); - break; - case CertificateVariables.KEY_USAGE_BIT7: - tempStr = String.format("%s%n", CertificateVariables.KEY_USAGE_EO); - break; - case CertificateVariables.KEY_USAGE_BIT8: - tempStr = String.format("%s%n", CertificateVariables.KEY_USAGE_DO); - break; - default: - break; - } - - return tempStr; - } - /** * Getter for the authorityKeyIdentifier. * @return the ID's byte representation @@ -836,35 +728,6 @@ public abstract class Certificate extends ArchivableEntity { return sb.toString(); } - /** - * This method is to take the DNs from certificates and sort them in an order - * that will be used to lookup issuer certificates. This will not be stored in - * the certificate, just the DB for lookup. - * @param distinguishedName the original DN string. - * @return a modified string of sorted DNs - */ - public static String parseSortDNs(final String distinguishedName) { - StringBuilder sb = new StringBuilder(); - String dnsString; - - if (distinguishedName == null || distinguishedName.isEmpty()) { - sb.append("BLANK"); - } else { - dnsString = distinguishedName.trim(); - dnsString = dnsString.toLowerCase(); - List dnValArray = Arrays.asList(dnsString.split(",")); - Collections.sort(dnValArray); - ListIterator dnListIter = dnValArray.listIterator(); - while (dnListIter.hasNext()) { - sb.append(dnListIter.next()); - if (dnListIter.hasNext()) { - sb.append(","); - } - } - } - - return sb.toString(); - } /** * Retrieve the original X509 attribute certificate. diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificatePageController.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificatePageController.java index 674d6c7f..82af7b67 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificatePageController.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificatePageController.java @@ -14,6 +14,7 @@ import hirs.attestationca.persist.entity.userdefined.certificate.CertificateAuth import hirs.attestationca.persist.entity.userdefined.certificate.EndorsementCredential; import hirs.attestationca.persist.entity.userdefined.certificate.IssuedAttestationCertificate; import hirs.attestationca.persist.entity.userdefined.certificate.PlatformCredential; +import hirs.attestationca.persist.util.CredentialHelper; import hirs.attestationca.portal.datatables.DataTableInput; import hirs.attestationca.portal.datatables.DataTableResponse; import hirs.attestationca.portal.datatables.OrderedListQueryDataTableAdapter; @@ -48,12 +49,18 @@ import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.support.RedirectAttributes; import org.springframework.web.servlet.view.RedirectView; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.lang.ref.Reference; import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; +import java.util.Collection; import java.util.HashMap; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -787,6 +794,24 @@ public class CertificatePageController extends PageController { case ENDORSEMENTCREDENTIAL: return new EndorsementCredential(fileBytes); case TRUSTCHAIN: + if (CredentialHelper.isMultiPEM(new String(fileBytes, StandardCharsets.UTF_8))) { + try (ByteArrayInputStream certInputStream = new ByteArrayInputStream(fileBytes)) { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + Collection c = cf.generateCertificates(certInputStream); + Iterator i = c.iterator(); + while (i.hasNext()) { + storeCertificate( + certificateType, + file.getOriginalFilename(), + messages, (Certificate)i.next()); + } + + // stop the main thread from saving/storing + return null; + } catch (CertificateException e) { + throw new IOException("Cannot construct X509Certificate from the input stream", e); + } + } return new CertificateAuthorityCredential(fileBytes); default: final String failMessage = String.format("Failed to parse uploaded file " From a685e987ef6f978f9f7199ebae0568fbc9aa7ea3 Mon Sep 17 00:00:00 2001 From: Cyrus <24922493+cyrus-dev@users.noreply.github.com> Date: Mon, 11 Sep 2023 16:08:30 -0400 Subject: [PATCH 08/16] Forgot to add updated new java --- .../persist/util/CredentialHelper.java | 165 ++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/util/CredentialHelper.java diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/util/CredentialHelper.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/util/CredentialHelper.java new file mode 100644 index 00000000..f6224504 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/util/CredentialHelper.java @@ -0,0 +1,165 @@ +package hirs.attestationca.persist.util; + +import hirs.attestationca.persist.entity.userdefined.certificate.CertificateVariables; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.bouncycastle.util.encoders.Base64; + +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.ListIterator; + +@Log4j2 +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class CredentialHelper { + + public static boolean isPEM(final String possiblePEM) { + return possiblePEM.contains(CertificateVariables.PEM_HEADER) + || possiblePEM.contains(CertificateVariables.PEM_ATTRIBUTE_HEADER); + } + + public static boolean isMultiPEM(final String possiblePEM) { + boolean multiPem = false; + int iniIndex = possiblePEM.indexOf(CertificateVariables.PEM_HEADER); + if (iniIndex >= 0) { + iniIndex = possiblePEM.indexOf(CertificateVariables.PEM_HEADER, + iniIndex + CertificateVariables.PEM_HEADER.length()); + if (iniIndex > 1) { + multiPem = true; + } + } + return multiPem; + } + + public static byte[] stripPemHeaderFooter(final String pemFile) { + String strippedFile; + strippedFile = pemFile.replace(CertificateVariables.PEM_HEADER, ""); + strippedFile = strippedFile.replace(CertificateVariables.PEM_FOOTER, ""); + strippedFile = strippedFile.replace(CertificateVariables.PEM_ATTRIBUTE_HEADER, ""); + strippedFile = strippedFile.replace(CertificateVariables.PEM_ATTRIBUTE_FOOTER, ""); + return Base64.decode(strippedFile); + } + + @SuppressWarnings("magicnumber") + public static byte[] trimCertificate(final byte[] certificateBytes) { + int certificateStart = 0; + int certificateLength = 0; + ByteBuffer certificateByteBuffer = ByteBuffer.wrap(certificateBytes); + + StringBuilder malformedCertStringBuilder = new StringBuilder(CertificateVariables.MALFORMED_CERT_MESSAGE); + while (certificateByteBuffer.hasRemaining()) { + // Check if there isn't an ASN.1 structure in the provided bytes + if (certificateByteBuffer.remaining() <= 2) { + throw new IllegalArgumentException(malformedCertStringBuilder + .append(" No certificate length field could be found.").toString()); + } + + // Look for first ASN.1 Sequence marked by the two bytes (0x30) and (0x82) + // The check advances our position in the ByteBuffer by one byte + int currentPosition = certificateByteBuffer.position(); + if (certificateByteBuffer.get() == (byte) 0x30 + && certificateByteBuffer.get(currentPosition + 1) == (byte) 0x82) { + // Check if we have anything more in the buffer than an ASN.1 Sequence header + if (certificateByteBuffer.remaining() <= 3) { + throw new IllegalArgumentException(malformedCertStringBuilder + .append(" Certificate is nothing more than ASN.1 Sequence.") + .toString()); + } + // Mark the start of the first ASN.1 Sequence / Certificate Body + certificateStart = currentPosition; + + // Parse the length as the 2-bytes following the start of the ASN.1 Sequence + certificateLength = Short.toUnsignedInt( + certificateByteBuffer.getShort(currentPosition + 2)); + // Add the 4 bytes that comprise the start of the ASN.1 Sequence and the length + certificateLength += 4; + break; + } + } + + if (certificateStart + certificateLength > certificateBytes.length) { + throw new IllegalArgumentException(malformedCertStringBuilder + .append(" Value of certificate length field extends beyond length") + .append(" of provided certificate.").toString()); + } + // Return bytes representing the main certificate body + return Arrays.copyOfRange(certificateBytes, certificateStart, + certificateStart + certificateLength); + } + + /** + * Return the string associated with the boolean slot. + * @param bit associated with the location in the array. + * @return string value of the bit set. + */ + public static String getKeyUsageString(final int bit) { + String tempStr = ""; + + switch (bit) { + case CertificateVariables.KEY_USAGE_BIT0: + tempStr = String.format("%s%n", CertificateVariables.KEY_USAGE_DS); + break; + case CertificateVariables.KEY_USAGE_BIT1: + tempStr = String.format("%s%n", CertificateVariables.KEY_USAGE_NR); + break; + case CertificateVariables.KEY_USAGE_BIT2: + tempStr = String.format("%s%n", CertificateVariables.KEY_USAGE_KE); + break; + case CertificateVariables.KEY_USAGE_BIT3: + tempStr = String.format("%s%n", CertificateVariables.KEY_USAGE_DE); + break; + case CertificateVariables.KEY_USAGE_BIT4: + tempStr = String.format("%s%n", CertificateVariables.KEY_USAGE_KA); + break; + case CertificateVariables.KEY_USAGE_BIT5: + tempStr = String.format("%s%n", CertificateVariables.KEY_USAGE_KC); + break; + case CertificateVariables.KEY_USAGE_BIT6: + tempStr = String.format("%s%n", CertificateVariables.KEY_USAGE_CS); + break; + case CertificateVariables.KEY_USAGE_BIT7: + tempStr = String.format("%s%n", CertificateVariables.KEY_USAGE_EO); + break; + case CertificateVariables.KEY_USAGE_BIT8: + tempStr = String.format("%s%n", CertificateVariables.KEY_USAGE_DO); + break; + default: + break; + } + + return tempStr; + } + + /** + * This method is to take the DNs from certificates and sort them in an order + * that will be used to lookup issuer certificates. This will not be stored in + * the certificate, just the DB for lookup. + * @param distinguishedName the original DN string. + * @return a modified string of sorted DNs + */ + public static String parseSortDNs(final String distinguishedName) { + StringBuilder sb = new StringBuilder(); + String dnsString; + + if (distinguishedName == null || distinguishedName.isEmpty()) { + sb.append("BLANK"); + } else { + dnsString = distinguishedName.trim(); + dnsString = dnsString.toLowerCase(); + List dnValArray = Arrays.asList(dnsString.split(",")); + Collections.sort(dnValArray); + ListIterator dnListIter = dnValArray.listIterator(); + while (dnListIter.hasNext()) { + sb.append(dnListIter.next()); + if (dnListIter.hasNext()) { + sb.append(","); + } + } + } + + return sb.toString(); + } +} From 1b7ba564d81b2a0f6c0843faddc2e87cbe3f3bbf Mon Sep 17 00:00:00 2001 From: Cyrus <24922493+cyrus-dev@users.noreply.github.com> Date: Mon, 11 Sep 2023 16:10:34 -0400 Subject: [PATCH 09/16] Messed up merging in versus other changes --- .../certificate/CertificateVariables.java | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/CertificateVariables.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/CertificateVariables.java index 20b89fc7..621ef771 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/CertificateVariables.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/CertificateVariables.java @@ -28,28 +28,20 @@ public class CertificateVariables { public static final String KEY_USAGE_CS = "CRL SIGN"; public static final String KEY_USAGE_EO = "ENCIPHER ONLY"; public static final String KEY_USAGE_DO = "DECIPHER ONLY"; - public static final String RSA1_OID = "1.2.840.113549.1.1.5"; + public static final String ECDSA_OID = "1.2.840.10045.4.3.2"; + public static final String ECDSA_SHA224_OID = "1.2.840.10045.4.1"; public static final String RSA256_OID = "1.2.840.113549.1.1.11"; public static final String RSA384_OID = "1.2.840.113549.1.1.12"; public static final String RSA512_OID = "1.2.840.113549.1.1.13"; public static final String RSA224_OID = "1.2.840.113549.1.1.14"; public static final String RSA512_224_OID = "1.2.840.113549.1.1.15"; public static final String RSA512_256_OID = "1.2.840.113549.1.1.16"; - public static final String RSA1_STRING = "SHA1WithRSA"; - public static final String RSA224_STRING = "SHA224WithRSA"; public static final String RSA256_STRING = "SHA256WithRSA"; public static final String RSA384_STRING = "SHA384WithRSA"; + public static final String RSA224_STRING = "SHA224WithRSA"; public static final String RSA512_STRING = "SHA512WithRSA"; public static final String RSA512_224_STRING = "SHA512-224WithRSA"; public static final String RSA512_256_STRING = "SHA512-256WithRSA"; - public static final String ECDSA_SHA1_OID = "1.2.840.10045.4.1"; - public static final String ECDSA_SHA224_OID = "1.2.840.10045.4.3.1"; - public static final String ECDSA_SHA256_OID = "1.2.840.10045.4.3.2"; - public static final String ECDSA_SHA384_OID = "1.2.840.10045.4.3.3"; - public static final String ECDSA_SHA512_OID = "1.2.840.10045.4.3.4"; - public static final String ECDSA_SHA1_STRING = "SHA1WithECDSA"; + public static final String ECDSA_STRING = "SHA256WithECDSA"; public static final String ECDSA_SHA224_STRING = "SHA224WithECDSA"; - public static final String ECDSA_SHA256_STRING = "SHA256WithECDSA"; - public static final String ECDSA_SHA384_STRING = "SHA384WithECDSA"; - public static final String ECDSA_SHA512_STRING = "SHA512WithECDSA"; } From a61488cbc3ea0c0ac9878f0831c90a5ecf40af3d Mon Sep 17 00:00:00 2001 From: Cyrus <24922493+cyrus-dev@users.noreply.github.com> Date: Mon, 11 Sep 2023 16:37:57 -0400 Subject: [PATCH 10/16] The wrong 'certificate' was being used --- .../portal/page/controllers/CertificatePageController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificatePageController.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificatePageController.java index 82af7b67..00752c13 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificatePageController.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificatePageController.java @@ -803,7 +803,7 @@ public class CertificatePageController extends PageController { storeCertificate( certificateType, file.getOriginalFilename(), - messages, (Certificate)i.next()); + messages, new CertificateAuthorityCredential(((java.security.cert.Certificate)i.next()).getEncoded())); } // stop the main thread from saving/storing From 38894e7ce5ab4ba6aec0f877e1c20bb3202e4752 Mon Sep 17 00:00:00 2001 From: chubtub <43381989+chubtub@users.noreply.github.com> Date: Tue, 12 Sep 2023 11:30:05 -0400 Subject: [PATCH 11/16] WIP: migrate RIM classes from ACA to rimtool --- tools/tcg_rim_tool/build.gradle | 11 + .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../java/hirs/swid/BaseReferenceManifest.java | 360 ++++++++++++++++++ .../main/java/hirs/swid/DigestAlgorithm.java | 66 ++++ .../java/hirs/swid/ReferenceManifest.java | 165 ++++++++ .../hirs/swid/SupportReferenceManifest.java | 207 ++++++++++ .../src/main/java/hirs/swid/SwidResource.java | 83 ++++ .../main/java/hirs/swid/SwidTagValidator.java | 16 + 8 files changed, 909 insertions(+), 1 deletion(-) create mode 100644 tools/tcg_rim_tool/src/main/java/hirs/swid/BaseReferenceManifest.java create mode 100644 tools/tcg_rim_tool/src/main/java/hirs/swid/DigestAlgorithm.java create mode 100644 tools/tcg_rim_tool/src/main/java/hirs/swid/ReferenceManifest.java create mode 100644 tools/tcg_rim_tool/src/main/java/hirs/swid/SupportReferenceManifest.java create mode 100644 tools/tcg_rim_tool/src/main/java/hirs/swid/SwidResource.java diff --git a/tools/tcg_rim_tool/build.gradle b/tools/tcg_rim_tool/build.gradle index cd7aefbe..5111a0a3 100644 --- a/tools/tcg_rim_tool/build.gradle +++ b/tools/tcg_rim_tool/build.gradle @@ -25,10 +25,21 @@ dependencies { implementation libs.glassfish.json implementation libs.glassfish.jaxb.runtime implementation libs.jcommander + implementation libs.jakarta.api + implementation libs.jakarta.xml + implementation libs.commons.codec + implementation libs.hibernate.core + implementation libs.jackson.databind + implementation 'org.apache.logging.log4j:log4j-core:2.19.0' + implementation libs.guava // implementation libs.javax.json // implementation libs.javax.jaxb // implementation libs.javax.annotation + compileOnly libs.lombok + implementation libs.lombok + annotationProcessor libs.lombok + testImplementation libs.testng } diff --git a/tools/tcg_rim_tool/gradle/wrapper/gradle-wrapper.properties b/tools/tcg_rim_tool/gradle/wrapper/gradle-wrapper.properties index 52843533..7dee5e87 100644 --- a/tools/tcg_rim_tool/gradle/wrapper/gradle-wrapper.properties +++ b/tools/tcg_rim_tool/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=gradle-4.5.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip diff --git a/tools/tcg_rim_tool/src/main/java/hirs/swid/BaseReferenceManifest.java b/tools/tcg_rim_tool/src/main/java/hirs/swid/BaseReferenceManifest.java new file mode 100644 index 00000000..d9781c5e --- /dev/null +++ b/tools/tcg_rim_tool/src/main/java/hirs/swid/BaseReferenceManifest.java @@ -0,0 +1,360 @@ +package hirs.swid; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import hirs.swid.ReferenceManifest; +import hirs.swid.SwidResource; +import hirs.swid.SwidTagConstants; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.xml.bind.JAXBContext; +import jakarta.xml.bind.JAXBException; +import jakarta.xml.bind.UnmarshalException; +import jakarta.xml.bind.Unmarshaller; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.extern.log4j.Log4j2; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +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.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; + +/** + * + */ +@Log4j2 +@Getter +@Setter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +public class BaseReferenceManifest extends ReferenceManifest { + /** + * Holds the name of the 'base64Hash' field. + */ + public static final String BASE_64_HASH_FIELD = "base64Hash"; + + private static JAXBContext jaxbContext; + + @Column + @JsonIgnore + private String base64Hash = ""; + @Column + private String swidName = null; + @Column + private int swidCorpus = 0; + @Column + private String colloquialVersion = null; + @Column + private String product = null; + @Column + private String revision = null; + @Column + private String edition = null; + @Column + private String rimLinkHash = null; + @Column + private String bindingSpec = null; + @Column + private String bindingSpecVersion = null; + @Column + private String platformVersion = null; + @Column + private String payloadType = null; + @Column + private String pcURIGlobal = null; + @Column + private String pcURILocal = null; + + private String entityName = null; + private String entityRegId = null; + private String entityRole = null; + private String entityThumbprint = null; + private String linkHref = null; + private String linkRel = null; + + /** + * Support constructor for the RIM object. + * + * @param rimBytes - the file content of the uploaded file. + * @throws IOException - thrown if the file is invalid. + */ + public BaseReferenceManifest(final byte[] rimBytes) throws IOException { + this("", rimBytes); + } + + /** + * Main constructor for the RIM object. This takes in a byte array of a + * valid swidtag file and parses the information. + * + * @param fileName - string representation of the uploaded file. + * @param rimBytes byte array representation of the RIM + * @throws IOException if unable to unmarshal the string + */ + @SuppressWarnings("checkstyle:AvoidInlineConditionals") + public BaseReferenceManifest(final String fileName, final byte[] rimBytes) throws IOException { + super(rimBytes); + this.setRimType(BASE_RIM); + this.setFileName(fileName); + Document document = unmarshallSwidTag(new ByteArrayInputStream(rimBytes)); + Element softwareIdentity; + Element meta; + Element entity; + Element link; + + MessageDigest digest = null; + this.base64Hash = ""; + try { + digest = MessageDigest.getInstance("SHA-256"); + this.base64Hash = Base64.getEncoder().encodeToString( + digest.digest(rimBytes)); + } catch (NoSuchAlgorithmException noSaEx) { + log.error(noSaEx); + } + + // begin parsing valid swid tag + if (document != null) { + softwareIdentity = (Element) document.getElementsByTagName(SwidTagConstants.SOFTWARE_IDENTITY).item(0); + entity = (Element) document.getElementsByTagName(SwidTagConstants.ENTITY).item(0); + link = (Element) document.getElementsByTagName(SwidTagConstants.LINK).item(0); + meta = (Element) document.getElementsByTagName(SwidTagConstants.META).item(0); + setTagId(softwareIdentity.getAttribute(SwidTagConstants.TAGID)); + this.swidName = softwareIdentity.getAttribute(SwidTagConstants.NAME); + this.swidCorpus = Boolean.parseBoolean(softwareIdentity.getAttribute(SwidTagConstants.CORPUS)) ? 1 : 0; + this.setSwidPatch(Boolean.parseBoolean(softwareIdentity.getAttribute(SwidTagConstants.PATCH))); + this.setSwidSupplemental(Boolean.parseBoolean(softwareIdentity.getAttribute(SwidTagConstants.SUPPLEMENTAL))); + this.setSwidVersion(softwareIdentity.getAttribute(SwidTagConstants.VERSION)); + this.setSwidTagVersion(softwareIdentity.getAttribute(SwidTagConstants.TAGVERSION)); + + parseSoftwareMeta(meta); + parseEntity(entity); + parseLink(link); + } + } + + /** + * This is a helper method that parses the SoftwareMeta tag and stores the + * information in the class fields. + * + * @param softwareMeta The object to parse. + */ + private void parseSoftwareMeta(final Element softwareMeta) { + if (softwareMeta != null) { + this.colloquialVersion = softwareMeta.getAttribute(SwidTagConstants.COLLOQUIAL_VERSION); + this.product = softwareMeta.getAttribute(SwidTagConstants.PRODUCT); + this.revision = softwareMeta.getAttribute(SwidTagConstants.REVISION); + this.edition = softwareMeta.getAttribute(SwidTagConstants.EDITION); + this.rimLinkHash = softwareMeta.getAttribute(SwidTagConstants.RIM_LINK_HASH); + this.bindingSpec = softwareMeta.getAttribute(SwidTagConstants.BINDING_SPEC); + this.bindingSpecVersion = softwareMeta.getAttribute(SwidTagConstants.BINDING_SPEC_VERSION); + this.setPlatformManufacturerId(softwareMeta.getAttribute(SwidTagConstants.PLATFORM_MANUFACTURER_ID)); + this.setPlatformManufacturer(softwareMeta.getAttribute(SwidTagConstants.PLATFORM_MANUFACTURER_STR)); + this.setPlatformModel(softwareMeta.getAttribute(SwidTagConstants.PLATFORM_MODEL)); + this.platformVersion = softwareMeta.getAttribute(SwidTagConstants.PLATFORM_VERSION); + this.payloadType = softwareMeta.getAttribute(SwidTagConstants.PAYLOAD_TYPE); + this.pcURIGlobal = softwareMeta.getAttribute(SwidTagConstants.PC_URI_GLOBAL); + this.pcURILocal = softwareMeta.getAttribute(SwidTagConstants.PC_URI_LOCAL); + } else { + log.warn("SoftwareMeta Tag not found."); + } + } + + /** + * This is a helper method that parses the Entity tag and stores the + * information in the class fields. + * + * @param entity The object to parse. + */ + private void parseEntity(final Element entity) { + if (entity != null) { + this.entityName = entity.getAttribute(SwidTagConstants.NAME); + this.entityRegId = entity.getAttribute(SwidTagConstants.REGID); + this.entityRole = entity.getAttribute(SwidTagConstants.ROLE); + this.entityThumbprint = entity.getAttribute(SwidTagConstants.THUMBPRINT); + } else { + log.warn("Entity Tag not found."); + } + } + + /** + * This is a helper method that parses the Link tag and stores the + * information in the class fields. + * + * @param link The object to parse. + */ + private void parseLink(final Element link) { + if (link != null) { + this.linkHref = link.getAttribute(SwidTagConstants.HREF); + this.linkRel = link.getAttribute(SwidTagConstants.REL); + } else { + log.warn("Link Tag not found."); + } + } + + /** + * This method validates the .swidtag file at the given filepath against the + * schema. A successful validation results in the output of the tag's name + * and tagId attributes, otherwise a generic error message is printed. + * + */ + private Element getDirectoryTag() { + return getDirectoryTag(new ByteArrayInputStream(getRimBytes())); + } + + /** + * This method validates the .swidtag file at the given filepath against the + * schema. A successful validation results in the output of the tag's name + * and tagId attributes, otherwise a generic error message is printed. + * + * @param byteArrayInputStream the location of the file to be validated + */ + private Element getDirectoryTag(final ByteArrayInputStream byteArrayInputStream) { + Document document = unmarshallSwidTag(byteArrayInputStream); + Element softwareIdentity = + (Element) document.getElementsByTagName("SoftwareIdentity").item(0); + if (softwareIdentity != null) { + Element directory = (Element) document.getElementsByTagName("Directory").item(0); + + return directory; + } else { + log.error("Invalid xml for validation, please verify "); + } + + return null; + } + + /** + * This method iterates over the list of File elements under the directory. * + */ + public List getFileResources() { + return getFileResources(getRimBytes()); + } + + /** + * This method iterates over the list of File elements under the directory. + * + * @param rimBytes the bytes to find the files + * + */ + public List getFileResources(final byte[] rimBytes) { + Element directoryTag = getDirectoryTag(new ByteArrayInputStream(rimBytes)); + List validHashes = new ArrayList<>(); + NodeList fileNodeList = directoryTag.getChildNodes(); + Element file = null; + SwidResource swidResource = null; + for (int i = 0; i < fileNodeList.getLength(); i++) { + file = (Element) fileNodeList.item(i); + swidResource = new SwidResource(); + swidResource.setName(file.getAttribute(SwidTagConstants.NAME)); + swidResource.setSize(file.getAttribute(SwidTagConstants.SIZE)); + swidResource.setHashValue(file.getAttribute(SwidTagConstants._SHA256_HASH.getPrefix() + ":" + + SwidTagConstants._SHA256_HASH.getLocalPart())); + validHashes.add(swidResource); + } + + return validHashes; + } + + /** + * This method unmarshalls the swidtag found at [path] into a Document object + * and validates it according to the schema. + * + * @param byteArrayInputStream to the input swidtag + * @return the Document element at the root of the swidtag + */ + private Document unmarshallSwidTag(final ByteArrayInputStream byteArrayInputStream) { + InputStream is = null; + Document document = null; + Unmarshaller unmarshaller = null; + try { + document = removeXMLWhitespace(byteArrayInputStream); + SchemaFactory schemaFactory = SchemaFactory.newInstance(SCHEMA_LANGUAGE); + is = getClass().getClassLoader().getResourceAsStream(SwidTagConstants.SCHEMA_URL); + Schema schema = schemaFactory.newSchema(new StreamSource(is)); + if (jaxbContext == null) { + jaxbContext = JAXBContext.newInstance(SCHEMA_PACKAGE); + } + unmarshaller = jaxbContext.createUnmarshaller(); + unmarshaller.setSchema(schema); + unmarshaller.unmarshal(document); + } catch (IOException e) { + log.error(e.getMessage()); + } catch (SAXException e) { + log.error("Error setting schema for validation!"); + } catch (UnmarshalException e) { + log.error("Error validating swidtag file!"); + } catch (IllegalArgumentException e) { + log.error("Input file empty."); + } catch (JAXBException e) { + e.printStackTrace(); + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + System.out.println("Error closing input stream"); + } + } + } + + return document; + } + + /** + * This method strips all whitespace from an xml file, including indents and spaces + * added for human-readability. + * + * @param byteArrayInputStream to the xml file + * @return Document object without whitespace + */ + private Document removeXMLWhitespace(final ByteArrayInputStream byteArrayInputStream) throws IOException { + TransformerFactory tf = TransformerFactory.newInstance(); + Source source = new StreamSource( + getClass().getClassLoader().getResourceAsStream("identity_transform.xslt")); + Document document = null; + if (byteArrayInputStream.available() > 0) { + try { + Transformer transformer = tf.newTransformer(source); + DOMResult result = new DOMResult(); + transformer.transform(new StreamSource(byteArrayInputStream), result); + document = (Document) result.getNode(); + } catch (TransformerConfigurationException tcEx) { + log.error("Error configuring transformer!"); + } catch (TransformerException tEx) { + log.error("Error transforming input!"); + } + } else { + throw new IOException("Input file is empty!"); + } + + return document; + } + + @Override + public String toString() { + return String.format("ReferenceManifest{swidName=%s," + + "platformManufacturer=%s," + + " platformModel=%s," + + "tagId=%s, base64Hash=%s}", + swidName, this.getPlatformManufacturer(), + this.getPlatformModel(), getTagId(), this.getBase64Hash()); + } +} diff --git a/tools/tcg_rim_tool/src/main/java/hirs/swid/DigestAlgorithm.java b/tools/tcg_rim_tool/src/main/java/hirs/swid/DigestAlgorithm.java new file mode 100644 index 00000000..35111dc3 --- /dev/null +++ b/tools/tcg_rim_tool/src/main/java/hirs/swid/DigestAlgorithm.java @@ -0,0 +1,66 @@ +package hirs.swid; + + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * Enum of digest algorithms. The enum values also provide a standardized + * algorithm name. The standardized algorithm name is a String of the algorithm + * name as defined by Java. + */ +@Getter +@AllArgsConstructor +public enum DigestAlgorithm { + /** + * MD2 digest algorithm. + */ + MD2("MD2", 16), + /** + * MD5 digest algorithm. + */ + MD5("MD5", 16), + /** + * SHA-1 digest algorithm. + */ + SHA1("SHA-1", 20), + /** + * SHA-256 digest algorithm. + */ + SHA256("SHA-256", 32), + /** + * SHA-384 digest algorithm. + */ + SHA384("SHA-384", 48), + /** + * SHA-512 digest algorithm. + */ + SHA512("SHA-512", 64), + /** + * Condition used when an algorithm is not specified and + * the size doesn't match known digests. + */ + UNSPECIFIED("NOT SPECIFIED", Integer.BYTES); + + private final String standardAlgorithmName; + private final int lengthInBytes; + + /** + * Returns a DigestAlgorithm object given a String. The String is expected to be one of the + * options for standardAlgorithmName. Throws an IllegalArgumentException if no Enum exists with + * that value. + * + * @param standardAlgorithmName + * String value of the Enum + * @return DigestAlgorithm object + */ + public static DigestAlgorithm findByString(final String standardAlgorithmName) { + for (DigestAlgorithm algorithm: DigestAlgorithm.values()) { + if (algorithm.getStandardAlgorithmName().equals(standardAlgorithmName)) { + return algorithm; + } + } + throw new IllegalArgumentException(String.format("No constant with text \"%s\" found", + standardAlgorithmName)); + } +} \ No newline at end of file diff --git a/tools/tcg_rim_tool/src/main/java/hirs/swid/ReferenceManifest.java b/tools/tcg_rim_tool/src/main/java/hirs/swid/ReferenceManifest.java new file mode 100644 index 00000000..325597f3 --- /dev/null +++ b/tools/tcg_rim_tool/src/main/java/hirs/swid/ReferenceManifest.java @@ -0,0 +1,165 @@ +package hirs.swid; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.google.common.base.Preconditions; +import jakarta.persistence.Access; +import jakarta.persistence.AccessType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.Table; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.extern.log4j.Log4j2; +import org.apache.commons.codec.binary.Hex; +import org.hibernate.annotations.JdbcTypeCode; + +import javax.xml.XMLConstants; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.UUID; + +/** + * This class represents the Reference Integrity Manifest object that will be + * loaded into the DB and displayed in the ACA. + */ +@Getter @Setter @ToString +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +@Log4j2 +@Entity +@Inheritance(strategy = InheritanceType.SINGLE_TABLE) +@Table(name = "ReferenceManifest") +@Access(AccessType.FIELD) +public class ReferenceManifest { + + /** + * Holds the name of the 'hexDecHash' field. + */ + public static final String HEX_DEC_HASH_FIELD = "hexDecHash"; + /** + * String for display of a Base RIM. + */ + public static final String BASE_RIM = "Base"; + /** + * String for display of a Support RIM. + */ + public static final String SUPPORT_RIM = "Support"; + /** + * String for display of a Support RIM. + */ + public static final String MEASUREMENT_RIM = "Measurement"; + + /** + * String for the xml schema ios standard. + */ + public static final String SCHEMA_STATEMENT = "ISO/IEC 19770-2:2015 Schema (XSD 1.0) " + + "- September 2015, see http://standards.iso.org/iso/19770/-2/2015/schema.xsd"; + /** + * String for the xml schema URL file name. + */ + public static final String SCHEMA_URL = "swid_schema.xsd"; + /** + * String for the language type for the xml schema. + */ + public static final String SCHEMA_LANGUAGE = XMLConstants.W3C_XML_SCHEMA_NS_URI; + /** + * String for the package location of the xml generated java files. + */ + public static final String SCHEMA_PACKAGE = "hirs.utils.xjc"; + + @EqualsAndHashCode.Include + @Column(columnDefinition = "mediumblob", nullable = false) + private byte[] rimBytes; + @EqualsAndHashCode.Include + @Column(nullable = false) + private String rimType = "Base"; + @Column + private String tagId = null; + @Column + private boolean swidPatch = false; + @Column + private boolean swidSupplemental = false; + @Column + private String platformManufacturer = null; + @Column + private String platformManufacturerId = null; + @Column + private String swidTagVersion = null; + @Column + private String swidVersion = null; + @Column + private String platformModel = null; + @Column(nullable = false) + private String fileName = null; + @JdbcTypeCode(java.sql.Types.VARCHAR) + @Column + private UUID associatedRim; + @Column + private String deviceName; + @Column + private String hexDecHash = ""; + @Column + private String eventLogHash = ""; + + /** + * Default constructor necessary for Hibernate. + */ + protected ReferenceManifest() { + super(); + this.rimBytes = null; + this.rimType = null; + this.platformManufacturer = null; + this.platformManufacturerId = null; + this.platformModel = null; + this.fileName = BASE_RIM; + this.tagId = null; + this.associatedRim = null; + } + + /** + * Default constructor for ingesting the bytes of the file content. + * @param rimBytes - file contents. + */ + public ReferenceManifest(final byte[] rimBytes) { + Preconditions.checkArgument(rimBytes != null, + "Cannot construct a RIM from a null byte array"); + + Preconditions.checkArgument(rimBytes.length > 0, + "Cannot construct a RIM from an empty byte array"); + + this.rimBytes = rimBytes.clone(); + MessageDigest digest = null; + this.hexDecHash = ""; + try { + digest = MessageDigest.getInstance("SHA-256"); + this.hexDecHash = Hex.encodeHexString( + digest.digest(rimBytes)); + } catch (NoSuchAlgorithmException noSaEx) { + log.error(noSaEx); + } + } + + /** + * Getter for the Reference Integrity Manifest as a byte array. + * + * @return array of bytes + */ + @JsonIgnore + public byte[] getRimBytes() { + if (this.rimBytes != null) { + return this.rimBytes.clone(); + } + return null; + } + + public boolean isBase() { + return rimType.equals(BASE_RIM); + } + + public boolean isSupport() { + return rimType.equals(SUPPORT_RIM); + } +} diff --git a/tools/tcg_rim_tool/src/main/java/hirs/swid/SupportReferenceManifest.java b/tools/tcg_rim_tool/src/main/java/hirs/swid/SupportReferenceManifest.java new file mode 100644 index 00000000..15eb6262 --- /dev/null +++ b/tools/tcg_rim_tool/src/main/java/hirs/swid/SupportReferenceManifest.java @@ -0,0 +1,207 @@ +package hirs.swid; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import hirs.swid.ReferenceManifest; +import hirs.attestationca.persist.service.ReferenceManifestServiceImpl; +import hirs.attestationca.persist.service.selector.ReferenceManifestSelector; +import hirs.utils.tpm.eventlog.TCGEventLog; +import hirs.utils.tpm.eventlog.TpmPcrEvent; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.log4j.Log4j2; + +import java.io.IOException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; + +/** + * Sub class that will just focus on PCR Values and Events. + */ +@Log4j2 +@Getter +@Setter +@Entity +public class SupportReferenceManifest extends ReferenceManifest { + + @Column + @JsonIgnore + private int pcrHash = 0; + @Column + private boolean updated = false; + @Column + private boolean processed = false; + + /** + * This class enables the retrieval of SupportReferenceManifest by their attributes. + */ + public static class Selector extends ReferenceManifestSelector { + /** + * Construct a new ReferenceManifestSelector that will + * use the given (@link ReferenceManifestService} + * to retrieve one or may SupportReferenceManifest. + * + * @param referenceManifestManager the reference manifest manager to be used to retrieve + * reference manifests. + */ + public Selector(final ReferenceManifestServiceImpl referenceManifestManager) { + super(referenceManifestManager, SupportReferenceManifest.class); + } + + /** + * Specify the platform manufacturer that rims must have to be considered + * as matching. + * @param manufacturer string for the manufacturer + * @return this instance + */ + public Selector byManufacturer(final String manufacturer) { + setFieldValue(PLATFORM_MANUFACTURER, manufacturer); + return this; + } + + /** + * Specify the platform model that rims must have to be considered + * as matching. + * @param manufacturer string for the manufacturer + * @param model string for the model + * @return this instance + */ + public Selector byManufacturerModel(final String manufacturer, final String model) { + setFieldValue(PLATFORM_MANUFACTURER, manufacturer); + setFieldValue(PLATFORM_MODEL, model); + return this; + } + + /** + * Specify the device name that rims must have to be considered + * as matching. + * @param deviceName string for the deviceName + * @return this instance + */ + public Selector byDeviceName(final String deviceName) { + setFieldValue("deviceName", deviceName); + return this; + } + + /** + * Specify the file name that rims should have. + * @param fileName the name of the file associated with the rim + * @return this instance + */ + public Selector byFileName(final String fileName) { + setFieldValue(RIM_FILENAME_FIELD, fileName); + return this; + } + + /** + * Specify the RIM hash associated with the support RIM. + * @param hexDecHash the hash of the file associated with the rim + * @return this instance + */ + public Selector byHexDecHash(final String hexDecHash) { + setFieldValue(HEX_DEC_HASH_FIELD, hexDecHash); + return this; + } + } + + /** + * Main constructor for the RIM object. This takes in a byte array of a + * valid swidtag file and parses the information. + * + * @param fileName - string representation of the uploaded file. + * @param rimBytes byte array representation of the RIM + * @throws IOException if unable to unmarshal the string + */ + public SupportReferenceManifest(final String fileName, + final byte[] rimBytes) throws IOException { + super(rimBytes); + this.setFileName(fileName); + this.setRimType(SUPPORT_RIM); + this.pcrHash = 0; + } + + /** + * Main constructor for the RIM object. This takes in a byte array of a + * valid swidtag file and parses the information. + * + * @param rimBytes byte array representation of the RIM + * @throws IOException if unable to unmarshal the string + */ + public SupportReferenceManifest(final byte[] rimBytes) throws IOException { + this("blank.rimel", rimBytes); + } + + /** + * Default constructor necessary for Hibernate. + */ + protected SupportReferenceManifest() { + super(); + this.pcrHash = 0; + } + + /** + * Get a Selector for use in retrieving ReferenceManifest. + * + * @param rimMan the ReferenceManifestService to be used to retrieve + * persisted RIMs + * @return a Selector instance to use for retrieving RIMs + */ + public static Selector select(final ReferenceManifestServiceImpl rimMan) { + return new Selector(rimMan); + } + + /** + * Getter method for the expected PCR values contained within the support + * RIM. + * @return a string array of the pcr values. + */ + public String[] getExpectedPCRList() { + try { + TCGEventLog logProcessor = new TCGEventLog(this.getRimBytes()); + this.pcrHash = Arrays.hashCode(logProcessor.getExpectedPCRValues()); + return logProcessor.getExpectedPCRValues(); + } catch (CertificateException cEx) { + log.error(cEx); + } catch (NoSuchAlgorithmException noSaEx) { + log.error(noSaEx); + } catch (IOException ioEx) { + log.error(ioEx); + } + + return new String[0]; + } + + /** + * Getter method for the event log that should be present in the support RIM. + * + * @return list of TPM PCR Events for display + */ + public Collection getEventLog() { + TCGEventLog logProcessor = null; + try { + logProcessor = new TCGEventLog(this.getRimBytes()); + return logProcessor.getEventList(); + } catch (CertificateException cEx) { + log.error(cEx); + } catch (NoSuchAlgorithmException noSaEx) { + log.error(noSaEx); + } catch (IOException ioEx) { + log.error(ioEx); + } + + return new ArrayList<>(); + } + + /** + * This is a method to indicate whether or not this support + * rim is a base log file. + * @return flag for base. + */ + public boolean isBaseSupport() { + return !this.isSwidSupplemental() && !this.isSwidPatch(); + } +} diff --git a/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidResource.java b/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidResource.java new file mode 100644 index 00000000..b79f090d --- /dev/null +++ b/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidResource.java @@ -0,0 +1,83 @@ +package hirs.swid; + +import com.google.common.base.Preconditions; +import hirs.swid.DigestAlgorithm; +import hirs.swid.xjc.File; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +import javax.xml.namespace.QName; +import java.math.BigInteger; +import java.util.Map; + +/** + * This object is used to represent the content of a Swid Tags Directory + * section. + */ +@ToString +public class SwidResource { + + @Getter + @Setter + private String name, size, hashValue; + @Getter + private String rimFormat, rimType, rimUriGlobal; + private DigestAlgorithm digest = DigestAlgorithm.SHA1; + @Getter + private boolean validFileSize = false; + + /** + * Default constructor. + */ + public SwidResource() { + name = null; + size = null; + rimFormat = null; + rimType = null; + rimUriGlobal = null; + hashValue = null; + } + + /** + * The main constructor that processes a {@code hirs.utils.xjc.File}. + * + * @param file {@link File} + * @param digest algorithm associated with pcr values + */ + public SwidResource(final File file, final DigestAlgorithm digest) { + Preconditions.checkArgument(file != null, + "Cannot construct a RIM Resource from a null File object"); + + this.name = file.getName(); + // at this time, there is a possibility to get an object with + // no size even though it is required. + if (file.getSize() != null) { + this.size = file.getSize().toString(); + } else { + this.size = BigInteger.ZERO.toString(); + } + + for (Map.Entry entry + : file.getOtherAttributes().entrySet()) { + switch (entry.getKey().getLocalPart()) { + case "supportRIMFormat": + this.rimFormat = entry.getValue(); + break; + case "supportRIMType": + this.rimType = entry.getValue(); + break; + case "supportRIMURIGlobal": + this.rimUriGlobal = entry.getValue(); + break; + case "hash": + this.hashValue = entry.getValue(); + break; + default: + } + } + + this.digest = digest; +// tpmWhiteList = new TpmWhiteListBaseline(this.name); + } +} \ No newline at end of file diff --git a/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidTagValidator.java b/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidTagValidator.java index 10d83a91..30ce7212 100644 --- a/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidTagValidator.java +++ b/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidTagValidator.java @@ -88,6 +88,22 @@ public class SwidTagValidator { this.trustStoreFile = trustStoreFile; } + /** + * 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) { + log.error("Error while unmarshalling rim bytes: " + e.getMessage()); + } + } + public SwidTagValidator() { try { JAXBContext jaxbContext = JAXBContext.newInstance(SwidTagConstants.SCHEMA_PACKAGE); From 66501c944468ac25f2b1cb4df6e40265b70b6d7f Mon Sep 17 00:00:00 2001 From: chubtub <43381989+chubtub@users.noreply.github.com> Date: Wed, 13 Sep 2023 17:14:31 -0400 Subject: [PATCH 12/16] Revert rimtool SwidTagValidator class changes, these will be in issue-582. Revert SupportReferenceManifest migration to rimtool, this class will remain in the ACA. --- .../hirs/swid/SupportReferenceManifest.java | 207 ------------------ .../main/java/hirs/swid/SwidTagValidator.java | 16 -- 2 files changed, 223 deletions(-) delete mode 100644 tools/tcg_rim_tool/src/main/java/hirs/swid/SupportReferenceManifest.java diff --git a/tools/tcg_rim_tool/src/main/java/hirs/swid/SupportReferenceManifest.java b/tools/tcg_rim_tool/src/main/java/hirs/swid/SupportReferenceManifest.java deleted file mode 100644 index 15eb6262..00000000 --- a/tools/tcg_rim_tool/src/main/java/hirs/swid/SupportReferenceManifest.java +++ /dev/null @@ -1,207 +0,0 @@ -package hirs.swid; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import hirs.swid.ReferenceManifest; -import hirs.attestationca.persist.service.ReferenceManifestServiceImpl; -import hirs.attestationca.persist.service.selector.ReferenceManifestSelector; -import hirs.utils.tpm.eventlog.TCGEventLog; -import hirs.utils.tpm.eventlog.TpmPcrEvent; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import lombok.Getter; -import lombok.Setter; -import lombok.extern.log4j.Log4j2; - -import java.io.IOException; -import java.security.NoSuchAlgorithmException; -import java.security.cert.CertificateException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; - -/** - * Sub class that will just focus on PCR Values and Events. - */ -@Log4j2 -@Getter -@Setter -@Entity -public class SupportReferenceManifest extends ReferenceManifest { - - @Column - @JsonIgnore - private int pcrHash = 0; - @Column - private boolean updated = false; - @Column - private boolean processed = false; - - /** - * This class enables the retrieval of SupportReferenceManifest by their attributes. - */ - public static class Selector extends ReferenceManifestSelector { - /** - * Construct a new ReferenceManifestSelector that will - * use the given (@link ReferenceManifestService} - * to retrieve one or may SupportReferenceManifest. - * - * @param referenceManifestManager the reference manifest manager to be used to retrieve - * reference manifests. - */ - public Selector(final ReferenceManifestServiceImpl referenceManifestManager) { - super(referenceManifestManager, SupportReferenceManifest.class); - } - - /** - * Specify the platform manufacturer that rims must have to be considered - * as matching. - * @param manufacturer string for the manufacturer - * @return this instance - */ - public Selector byManufacturer(final String manufacturer) { - setFieldValue(PLATFORM_MANUFACTURER, manufacturer); - return this; - } - - /** - * Specify the platform model that rims must have to be considered - * as matching. - * @param manufacturer string for the manufacturer - * @param model string for the model - * @return this instance - */ - public Selector byManufacturerModel(final String manufacturer, final String model) { - setFieldValue(PLATFORM_MANUFACTURER, manufacturer); - setFieldValue(PLATFORM_MODEL, model); - return this; - } - - /** - * Specify the device name that rims must have to be considered - * as matching. - * @param deviceName string for the deviceName - * @return this instance - */ - public Selector byDeviceName(final String deviceName) { - setFieldValue("deviceName", deviceName); - return this; - } - - /** - * Specify the file name that rims should have. - * @param fileName the name of the file associated with the rim - * @return this instance - */ - public Selector byFileName(final String fileName) { - setFieldValue(RIM_FILENAME_FIELD, fileName); - return this; - } - - /** - * Specify the RIM hash associated with the support RIM. - * @param hexDecHash the hash of the file associated with the rim - * @return this instance - */ - public Selector byHexDecHash(final String hexDecHash) { - setFieldValue(HEX_DEC_HASH_FIELD, hexDecHash); - return this; - } - } - - /** - * Main constructor for the RIM object. This takes in a byte array of a - * valid swidtag file and parses the information. - * - * @param fileName - string representation of the uploaded file. - * @param rimBytes byte array representation of the RIM - * @throws IOException if unable to unmarshal the string - */ - public SupportReferenceManifest(final String fileName, - final byte[] rimBytes) throws IOException { - super(rimBytes); - this.setFileName(fileName); - this.setRimType(SUPPORT_RIM); - this.pcrHash = 0; - } - - /** - * Main constructor for the RIM object. This takes in a byte array of a - * valid swidtag file and parses the information. - * - * @param rimBytes byte array representation of the RIM - * @throws IOException if unable to unmarshal the string - */ - public SupportReferenceManifest(final byte[] rimBytes) throws IOException { - this("blank.rimel", rimBytes); - } - - /** - * Default constructor necessary for Hibernate. - */ - protected SupportReferenceManifest() { - super(); - this.pcrHash = 0; - } - - /** - * Get a Selector for use in retrieving ReferenceManifest. - * - * @param rimMan the ReferenceManifestService to be used to retrieve - * persisted RIMs - * @return a Selector instance to use for retrieving RIMs - */ - public static Selector select(final ReferenceManifestServiceImpl rimMan) { - return new Selector(rimMan); - } - - /** - * Getter method for the expected PCR values contained within the support - * RIM. - * @return a string array of the pcr values. - */ - public String[] getExpectedPCRList() { - try { - TCGEventLog logProcessor = new TCGEventLog(this.getRimBytes()); - this.pcrHash = Arrays.hashCode(logProcessor.getExpectedPCRValues()); - return logProcessor.getExpectedPCRValues(); - } catch (CertificateException cEx) { - log.error(cEx); - } catch (NoSuchAlgorithmException noSaEx) { - log.error(noSaEx); - } catch (IOException ioEx) { - log.error(ioEx); - } - - return new String[0]; - } - - /** - * Getter method for the event log that should be present in the support RIM. - * - * @return list of TPM PCR Events for display - */ - public Collection getEventLog() { - TCGEventLog logProcessor = null; - try { - logProcessor = new TCGEventLog(this.getRimBytes()); - return logProcessor.getEventList(); - } catch (CertificateException cEx) { - log.error(cEx); - } catch (NoSuchAlgorithmException noSaEx) { - log.error(noSaEx); - } catch (IOException ioEx) { - log.error(ioEx); - } - - return new ArrayList<>(); - } - - /** - * This is a method to indicate whether or not this support - * rim is a base log file. - * @return flag for base. - */ - public boolean isBaseSupport() { - return !this.isSwidSupplemental() && !this.isSwidPatch(); - } -} diff --git a/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidTagValidator.java b/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidTagValidator.java index 30ce7212..10d83a91 100644 --- a/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidTagValidator.java +++ b/tools/tcg_rim_tool/src/main/java/hirs/swid/SwidTagValidator.java @@ -88,22 +88,6 @@ public class SwidTagValidator { this.trustStoreFile = trustStoreFile; } - /** - * 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) { - log.error("Error while unmarshalling rim bytes: " + e.getMessage()); - } - } - public SwidTagValidator() { try { JAXBContext jaxbContext = JAXBContext.newInstance(SwidTagConstants.SCHEMA_PACKAGE); From 771d5588d535e8385a47781376c63d2892d4bcf2 Mon Sep 17 00:00:00 2001 From: Cyrus <24922493+cyrus-dev@users.noreply.github.com> Date: Thu, 14 Sep 2023 09:14:11 -0400 Subject: [PATCH 13/16] Updated the code to print out the available Restful URIs. Added some modifications to the provisioner code for testing and updated the Restful base mapping --- ...estfulAttestationCertificateAuthority.java | 4 +-- .../AnnotationDrivenEndpointsListener.java | 26 +++++++++++++++++++ HIRS_Provisioner/hirs-provisioner-config.sh | 1 - .../src/RestfulClientProvisioner.cpp | 5 ++-- HIRS_ProvisionerTPM2/src/TPM2_Provisioner.cpp | 10 +++---- 5 files changed, 36 insertions(+), 10 deletions(-) create mode 100644 HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/listener/AnnotationDrivenEndpointsListener.java diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/RestfulAttestationCertificateAuthority.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/RestfulAttestationCertificateAuthority.java index 2b011879..7e850645 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/RestfulAttestationCertificateAuthority.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/RestfulAttestationCertificateAuthority.java @@ -8,9 +8,9 @@ import hirs.attestationca.persist.entity.manager.PolicyRepository; import hirs.attestationca.persist.entity.manager.ReferenceDigestValueRepository; import hirs.attestationca.persist.entity.manager.ReferenceManifestRepository; import hirs.attestationca.persist.entity.manager.TPM2ProvisionerStateRepository; -import hirs.attestationca.persist.entity.userdefined.certificate.IssuedAttestationCertificate; import hirs.attestationca.persist.service.SupplyChainValidationService; import hirs.structs.converters.StructConverter; +import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.PropertySource; @@ -31,7 +31,7 @@ import java.security.cert.X509Certificate; @PropertySource(value = "file:/etc/hirs/aca/application.properties", ignoreResourceNotFound = true) @RestController -@RequestMapping("/") +@RequestMapping("/client") public class RestfulAttestationCertificateAuthority extends AttestationCertificateAuthority implements RestfulInterface { /** diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/listener/AnnotationDrivenEndpointsListener.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/listener/AnnotationDrivenEndpointsListener.java new file mode 100644 index 00000000..0a990bd4 --- /dev/null +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/listener/AnnotationDrivenEndpointsListener.java @@ -0,0 +1,26 @@ +package hirs.attestationca.portal.listener; + +import lombok.extern.log4j.Log4j2; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.context.event.EventListener; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +import java.util.Map; + +@Log4j2 +@Configuration +public class AnnotationDrivenEndpointsListener { + + @EventListener + public void handleContextRefresh(ContextRefreshedEvent event) { + ApplicationContext applicationContext = event.getApplicationContext(); + RequestMappingHandlerMapping requestMappingHandlerMapping = applicationContext + .getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class); + Map map = requestMappingHandlerMapping.getHandlerMethods(); + map.forEach((key, value) -> log.debug("{} {}", key, value)); + } +} diff --git a/HIRS_Provisioner/hirs-provisioner-config.sh b/HIRS_Provisioner/hirs-provisioner-config.sh index ec8bf638..db86e854 100755 --- a/HIRS_Provisioner/hirs-provisioner-config.sh +++ b/HIRS_Provisioner/hirs-provisioner-config.sh @@ -66,7 +66,6 @@ echo "----> Downloading truststore" | tee -a $PROVISIONER_LOG_FILE wget https://"$ATTESTATION_CA_FQDN":"$ATTESTATION_CA_PORT"/HIRS_AttestationCA/client-files/TrustStore.jks --no-check-certificate -P ${CERTIFICATES} >/dev/null 2>/dev/null if [ ! -f "${CERTIFICATES}/TrustStore.jks" ]; then echo "----> ERROR: Truststore could not be downloaded from $ATTESTATION_CA_FQDN" | tee -a $PROVISIONER_LOG_FILE - exit 1 fi sed -i "s/provisioner\.aca\.host\s*=\s*.*/provisioner.aca.host = $ATTESTATION_CA_FQDN/" $PROVISIONER_PROPERTIES diff --git a/HIRS_ProvisionerTPM2/src/RestfulClientProvisioner.cpp b/HIRS_ProvisionerTPM2/src/RestfulClientProvisioner.cpp index 87102cb3..63eeffca 100644 --- a/HIRS_ProvisionerTPM2/src/RestfulClientProvisioner.cpp +++ b/HIRS_ProvisionerTPM2/src/RestfulClientProvisioner.cpp @@ -65,7 +65,8 @@ string RestfulClientProvisioner::sendIdentityClaim( // Send serialized Identity Claim to ACA LOGGER.info("Sending Serialized Identity Claim Binary"); auto r = cpr::Post(cpr::Url{"https://" + acaAddress + ":" + to_string(port) - + "/HIRS_AttestationCA/identity-claim-tpm2/" + + "/HIRS_AttestationCA/portal/" + + "client/identity-claim-tpm2/" + "process"}, cpr::Body{identityClaimByteString}, cpr::Header{{"Content-Type", @@ -121,7 +122,7 @@ string RestfulClientProvisioner::sendAttestationCertificateRequest( // Send serialized certificate request to ACA LOGGER.info("Sending Serialized DeviceInfo Binary"); auto r = cpr::Post(cpr::Url{"https://" + acaAddress + ":" + to_string(port) - + "/HIRS_AttestationCA" + + "/HIRS_AttestationCA/portal/client" + "/request-certificate-tpm2"}, cpr::Body{certificateRequestByteString}, cpr::Header{{"Content-Type", diff --git a/HIRS_ProvisionerTPM2/src/TPM2_Provisioner.cpp b/HIRS_ProvisionerTPM2/src/TPM2_Provisioner.cpp index 612f2d92..4b87b439 100644 --- a/HIRS_ProvisionerTPM2/src/TPM2_Provisioner.cpp +++ b/HIRS_ProvisionerTPM2/src/TPM2_Provisioner.cpp @@ -62,12 +62,12 @@ int provision() { // get platform credential cout << "----> Collecting platform credential from TPM" << endl; - string platformCredential = tpm2.getPlatformCredentialDefault(); + // string platformCredential = tpm2.getPlatformCredentialDefault(); std::vector platformCredentials; // if platformCredential is empty, not in TPM // pull from properties file - if (platformCredential.empty()) { + // if (platformCredential.empty()) { const std::string& cert_dir = props.get( "tcg.cert.dir", @@ -78,9 +78,9 @@ int provision() { } catch (HirsRuntimeException& hirsRuntimeException) { logger.error(hirsRuntimeException.what()); } - } else { - platformCredentials.push_back(platformCredential); - } +// } else { +// platformCredentials.push_back(platformCredential); +// } // collect device info cout << "----> Collecting device information" << endl; From a56fd3a8fa16c0ff20f5881b71e46019a742342b Mon Sep 17 00:00:00 2001 From: iadgovuser29 <33426478+iadgovuser29@users.noreply.github.com> Date: Fri, 15 Sep 2023 15:34:15 -0400 Subject: [PATCH 14/16] Context change to enable path control [no ci] --- .../persist/RestfulAttestationCertificateAuthority.java | 2 +- .../page/controllers/CertificateDetailsPageController.java | 2 +- .../portal/page/controllers/CertificatePageController.java | 2 +- .../portal/page/controllers/DevicePageController.java | 2 +- .../portal/page/controllers/HelpPageController.java | 2 +- .../portal/page/controllers/IndexPageController.java | 2 +- .../portal/page/controllers/LombokLoggingController.java | 2 +- .../portal/page/controllers/PolicyPageController.java | 2 +- .../controllers/ReferenceManifestDetailsPageController.java | 2 +- .../page/controllers/ReferenceManifestPageController.java | 2 +- .../portal/page/controllers/RimDatabasePageController.java | 2 +- .../page/controllers/ValidationReportsPageController.java | 2 +- .../src/main/resources/application.properties | 4 ++-- .../src/main/webapp/WEB-INF/tags/page.tag | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/RestfulAttestationCertificateAuthority.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/RestfulAttestationCertificateAuthority.java index 7e850645..ced6614d 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/RestfulAttestationCertificateAuthority.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/RestfulAttestationCertificateAuthority.java @@ -31,7 +31,7 @@ import java.security.cert.X509Certificate; @PropertySource(value = "file:/etc/hirs/aca/application.properties", ignoreResourceNotFound = true) @RestController -@RequestMapping("/client") +@RequestMapping("/HIRS_AttestationCA") public class RestfulAttestationCertificateAuthority extends AttestationCertificateAuthority implements RestfulInterface { /** diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificateDetailsPageController.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificateDetailsPageController.java index e9a76940..cde878d4 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificateDetailsPageController.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificateDetailsPageController.java @@ -24,7 +24,7 @@ import java.util.UUID; */ @Log4j2 @Controller -@RequestMapping("/certificate-details") +@RequestMapping("/HIRS_AttestationCAPortal/portal/certificate-details") public class CertificateDetailsPageController extends PageController { /** diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificatePageController.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificatePageController.java index 674d6c7f..fd4ac806 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificatePageController.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificatePageController.java @@ -68,7 +68,7 @@ import java.util.zip.ZipOutputStream; */ @Log4j2 @Controller -@RequestMapping("/certificate-request") +@RequestMapping("/HIRS_AttestationCAPortal/portal/certificate-request") public class CertificatePageController extends PageController { @Autowired(required = false) private EntityManager entityManager; diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/DevicePageController.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/DevicePageController.java index b596e788..a7a479da 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/DevicePageController.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/DevicePageController.java @@ -43,7 +43,7 @@ import java.util.UUID; */ @Log4j2 @Controller -@RequestMapping("/devices") +@RequestMapping("/HIRS_AttestationCAPortal/portal/devices") public class DevicePageController extends PageController { private final DeviceRepository deviceRepository; diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/HelpPageController.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/HelpPageController.java index c9b97938..0f874f93 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/HelpPageController.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/HelpPageController.java @@ -20,7 +20,7 @@ import static hirs.attestationca.portal.page.Page.HELP; */ @Log4j2 @Controller -@RequestMapping("/help") +@RequestMapping("/HIRS_AttestationCAPortal/portal/help") public class HelpPageController extends PageController { @Autowired diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/IndexPageController.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/IndexPageController.java index 509a7231..d1430342 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/IndexPageController.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/IndexPageController.java @@ -14,7 +14,7 @@ import org.springframework.web.servlet.ModelAndView; */ @Controller @Log4j2 -@RequestMapping("/index") +@RequestMapping("/HIRS_AttestationCAPortal/portal/index") public class IndexPageController extends PageController { /** diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/LombokLoggingController.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/LombokLoggingController.java index 197ecc42..f1e9bbe8 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/LombokLoggingController.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/LombokLoggingController.java @@ -8,7 +8,7 @@ import org.springframework.web.bind.annotation.RestController; @Log4j2 public class LombokLoggingController { - @RequestMapping("/lombok") + @RequestMapping("/HIRS_AttestationCAPortal/portal/lombok") public String index() { log.trace("A TRACE Message"); log.debug("A DEBUG Message"); diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/PolicyPageController.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/PolicyPageController.java index 4e2f4a68..69bccb23 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/PolicyPageController.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/PolicyPageController.java @@ -28,7 +28,7 @@ import java.util.Map; */ @Log4j2 @Controller -@RequestMapping("/policy") +@RequestMapping("/HIRS_AttestationCAPortal/portal/policy") public class PolicyPageController extends PageController { /** diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/ReferenceManifestDetailsPageController.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/ReferenceManifestDetailsPageController.java index a2ee5373..c2947c68 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/ReferenceManifestDetailsPageController.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/ReferenceManifestDetailsPageController.java @@ -46,7 +46,7 @@ import java.util.UUID; */ @Log4j2 @Controller -@RequestMapping("/rim-details") +@RequestMapping("/HIRS_AttestationCAPortal/portal/rim-details") public class ReferenceManifestDetailsPageController extends PageController { private final ReferenceManifestRepository referenceManifestRepository; diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/ReferenceManifestPageController.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/ReferenceManifestPageController.java index d757e251..e8c75606 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/ReferenceManifestPageController.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/ReferenceManifestPageController.java @@ -66,7 +66,7 @@ import java.util.zip.ZipOutputStream; */ @Log4j2 @Controller -@RequestMapping("/reference-manifests") +@RequestMapping("/HIRS_AttestationCAPortal/portal/reference-manifests") public class ReferenceManifestPageController extends PageController { private static final String LOG_FILE_PATTERN = "([^\\s]+(\\.(?i)(rimpcr|rimel|bin|log))$)"; diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/RimDatabasePageController.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/RimDatabasePageController.java index b7aef697..0a2e76ed 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/RimDatabasePageController.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/RimDatabasePageController.java @@ -39,7 +39,7 @@ import java.lang.ref.Reference; */ @Log4j2 @Controller -@RequestMapping("/rim-database") +@RequestMapping("/HIRS_AttestationCAPortal/portal/rim-database") public class RimDatabasePageController extends PageController { @Autowired(required = false) diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/ValidationReportsPageController.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/ValidationReportsPageController.java index 9106180f..590a5030 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/ValidationReportsPageController.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/ValidationReportsPageController.java @@ -61,7 +61,7 @@ import java.util.regex.Pattern; */ @Log4j2 @Controller -@RequestMapping("/validation-reports") +@RequestMapping("/HIRS_AttestationCAPortal/portal/validation-reports") public class ValidationReportsPageController extends PageController { private final SupplyChainValidationSummaryRepository supplyChainValidatorSummaryRepository; diff --git a/HIRS_AttestationCAPortal/src/main/resources/application.properties b/HIRS_AttestationCAPortal/src/main/resources/application.properties index 0d840bfc..17515454 100644 --- a/HIRS_AttestationCAPortal/src/main/resources/application.properties +++ b/HIRS_AttestationCAPortal/src/main/resources/application.properties @@ -20,8 +20,8 @@ aca.certificates.validity = 3652 server.tomcat.additional-tld-skip-patterns=jakarta.persistence-api*.jar, jakarta.xml.bind-api*.jar, txw2*.jar, *commons*.jar, *annotations*.jar, *checker*.jar, *lombok*.jar, *jsr*.jar, *guava*.jar, *access*.jar, *activation*.jar, *bcprov*.jar, *bcmail*.jar, *bcutil*.jar, *bcpkix*.jar, *json*.jar server.tomcat.basedir=/opt/embeddedtomcat server.servlet.register-default-servlet=true -server.servlet.context-path=/HIRS_AttestationCAPortal -spring.mvc.servlet.path=/portal +server.servlet.context-path=/ +spring.mvc.servlet.path=/ server.tomcat.accesslog.enabled=true server.tomcat.accesslog.directory=/var/log/hirs diff --git a/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/tags/page.tag b/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/tags/page.tag index bacdde8e..d4807d91 100644 --- a/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/tags/page.tag +++ b/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/tags/page.tag @@ -13,7 +13,7 @@ - + From ea0d46147e9ad43593188e3b7f69b1b985256a4b Mon Sep 17 00:00:00 2001 From: iadgovuser29 <33426478+iadgovuser29@users.noreply.github.com> Date: Mon, 18 Sep 2023 10:21:55 -0400 Subject: [PATCH 15/16] Some package scripts needed chmod +x --- package/scripts/db/db_create.sh | 0 package/scripts/db/db_drop.sh | 0 package/scripts/db/start_mysqld.sh | 0 package/scripts/pki/pki_chain_gen.sh | 0 package/scripts/pki/pki_setup.sh | 0 5 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 package/scripts/db/db_create.sh mode change 100644 => 100755 package/scripts/db/db_drop.sh mode change 100644 => 100755 package/scripts/db/start_mysqld.sh mode change 100644 => 100755 package/scripts/pki/pki_chain_gen.sh mode change 100644 => 100755 package/scripts/pki/pki_setup.sh diff --git a/package/scripts/db/db_create.sh b/package/scripts/db/db_create.sh old mode 100644 new mode 100755 diff --git a/package/scripts/db/db_drop.sh b/package/scripts/db/db_drop.sh old mode 100644 new mode 100755 diff --git a/package/scripts/db/start_mysqld.sh b/package/scripts/db/start_mysqld.sh old mode 100644 new mode 100755 diff --git a/package/scripts/pki/pki_chain_gen.sh b/package/scripts/pki/pki_chain_gen.sh old mode 100644 new mode 100755 diff --git a/package/scripts/pki/pki_setup.sh b/package/scripts/pki/pki_setup.sh old mode 100644 new mode 100755 From 5092718c3c6a83c31222e0f77b7f6bae3419dac1 Mon Sep 17 00:00:00 2001 From: iadgovuser58 <124906646+iadgovuser58@users.noreply.github.com> Date: Mon, 18 Sep 2023 11:22:43 -0400 Subject: [PATCH 16/16] updated to include protobuf-compiler and cronie --- .ci/docker/Dockerfile.rocky88ci | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.ci/docker/Dockerfile.rocky88ci b/.ci/docker/Dockerfile.rocky88ci index d1576077..c7a6cc39 100644 --- a/.ci/docker/Dockerfile.rocky88ci +++ b/.ci/docker/Dockerfile.rocky88ci @@ -29,9 +29,9 @@ EXPOSE 8080 EXPOSE 8443 # Install HIRS dependencies -RUN dnf -y install mariadb-server initscripts firewalld policycoreutils net-tools git && dnf clean all +RUN dnf -y install mariadb-server initscripts firewalld policycoreutils net-tools git protobuf-compiler cronie && dnf clean all # maybe: libtool cmake make gcc-c++ sudo vim wget openssl openssl-devel protobuf tpm2-tools libcurl-devel libssh-devel -# prob not: rpmdevtools tpm2-tss-devel tpm2-abrmd protobuf-compiler protobuf-devel python36 log4cplus-devel re2-devel tpm2-tss-devel tpm2-abrmd-devel +# prob not: rpmdevtools tpm2-tss-devel tpm2-abrmd protobuf-devel python36 log4cplus-devel re2-devel tpm2-tss-devel tpm2-abrmd-devel # Install PACCOR for Device Info Gathering # RUN mkdir paccor && pushd paccor && wget https://github.com/nsacyber/paccor/releases/download/v1.1.4r6/paccor-1.1.4-6.noarch.rpm && yum -y install paccor-*.rpm && popd