mirror of
https://github.com/nsacyber/HIRS.git
synced 2025-01-18 18:56:29 +00:00
Merge pull request #584 from nsacyber/v3_provision-init-setup
Initial Setup for ACA provisioning
This commit is contained in:
commit
9fea7788ed
@ -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()
|
||||
}
|
||||
|
@ -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<TPM2ProvisionerState> 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();
|
||||
}
|
||||
}
|
@ -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());
|
||||
|
@ -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.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;
|
||||
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("/HIRS_AttestationCA")
|
||||
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)
|
||||
* <p>
|
||||
* 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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -14,4 +14,7 @@ public interface CACredentialRepository extends JpaRepository<CertificateAuthori
|
||||
@Query(value = "SELECT * FROM Certificate where DTYPE='CertificateAuthorityCredential'", nativeQuery = true)
|
||||
@Override
|
||||
List<CertificateAuthorityCredential> findAll();
|
||||
List<CertificateAuthorityCredential> findBySubject(String subject);
|
||||
List<CertificateAuthorityCredential> findBySubjectSorted(String subject);
|
||||
CertificateAuthorityCredential findBySubjectKeyIdentifier(byte[] subjectKeyIdentifier);
|
||||
}
|
||||
|
@ -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<T extends Certificate> extends JpaReposit
|
||||
List<PlatformCredential> 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<PlatformCredential> 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);
|
||||
}
|
||||
|
@ -19,4 +19,5 @@ public interface ReferenceDigestValueRepository extends JpaRepository<ReferenceD
|
||||
List<ReferenceDigestValue> getValuesByRimId(UUID associatedRimId);
|
||||
List<ReferenceDigestValue> findBySupportRimId(UUID supportRimId);
|
||||
List<ReferenceDigestValue> findBySupportRimHash(String supportRimHash);
|
||||
List<ReferenceDigestValue> findByManufacturerAndModel(String manufacturer, String model);
|
||||
}
|
||||
|
@ -15,11 +15,12 @@ import java.util.UUID;
|
||||
public interface ReferenceManifestRepository extends JpaRepository<ReferenceManifest, UUID> {
|
||||
|
||||
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<BaseReferenceManifest> 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<BaseReferenceManifest> 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<ReferenceMani
|
||||
EventLogMeasurements getEventLogRimEntityById(UUID uuid);
|
||||
@Query(value = "SELECT * FROM ReferenceManifest WHERE deviceName = ?1 AND DTYPE = 'SupportReferenceManifest'", nativeQuery = true)
|
||||
List<SupportReferenceManifest> 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<SupportReferenceManifest> getSupportByManufacturerModel(String manufacturer, String model);
|
||||
}
|
||||
|
@ -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, Long> {
|
||||
|
||||
TPM2ProvisionerState findByFirstPartOfNonce(Long findByFirstPartOfNonce);
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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<CertificateAuthorityCredential> {
|
||||
/**
|
||||
* 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.
|
||||
|
@ -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<PlatformCredential> platformCredentials;
|
||||
|
||||
/**
|
||||
* This class enables the retrieval of IssuedAttestationCertificate by their attributes.
|
||||
*/
|
||||
// public static class Selector extends CertificateSelector<IssuedAttestationCertificate> {
|
||||
// /**
|
||||
// * 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<PlatformCredential> platformCredentials;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@ -81,7 +43,7 @@ public class IssuedAttestationCertificate extends DeviceAssociatedCertificate {
|
||||
*/
|
||||
public IssuedAttestationCertificate(final byte[] certificateBytes,
|
||||
final EndorsementCredential endorsementCredential,
|
||||
final Set<PlatformCredential> platformCredentials)
|
||||
final List<PlatformCredential> 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<PlatformCredential> platformCredentials)
|
||||
final List<PlatformCredential> platformCredentials)
|
||||
throws IOException {
|
||||
this(readBytes(certificatePath), endorsementCredential, platformCredentials);
|
||||
}
|
||||
|
@ -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<PlatformCredential> {
|
||||
/**
|
||||
* 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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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<SupportReferenceManifest> {
|
||||
/**
|
||||
* 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.
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -0,0 +1,293 @@
|
||||
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.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.exceptions.CertificateProcessingException;
|
||||
import hirs.attestationca.persist.provision.helper.CredentialManagementHelper;
|
||||
import hirs.attestationca.persist.provision.helper.IssuedCertificateAttributeHelper;
|
||||
import hirs.attestationca.persist.provision.helper.ProvisionUtils;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
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.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@Log4j2
|
||||
@NoArgsConstructor
|
||||
public class AbstractRequestHandler {
|
||||
|
||||
@Getter
|
||||
private int validDays;
|
||||
@Getter
|
||||
private PrivateKey privateKey;
|
||||
@Setter
|
||||
@Getter
|
||||
private PolicyRepository policyRepository;
|
||||
|
||||
public AbstractRequestHandler(final PrivateKey privateKey,
|
||||
final int validDays) {
|
||||
this.privateKey = privateKey;
|
||||
this.validDays = validDays;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<PlatformCredential> 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<PlatformCredential> parsePcsFromIdentityClaim(
|
||||
final ProvisionerTpm2.IdentityClaim identityClaim,
|
||||
final EndorsementCredential endorsementCredential,
|
||||
final CertificateRepository certificateRepository) {
|
||||
List<PlatformCredential> 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<PlatformCredential> 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 = ProvisionUtils.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<PlatformCredential> getPlatformCredentials(final CertificateRepository certificateRepository,
|
||||
final EndorsementCredential ec) {
|
||||
List<PlatformCredential> 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;
|
||||
}
|
||||
}
|
@ -0,0 +1,220 @@
|
||||
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.provision.helper.ProvisionUtils;
|
||||
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 = ProvisionUtils.parseIdentityClaim(identityClaim);
|
||||
|
||||
// Get endorsement public key
|
||||
RSAPublicKey ekPub = ProvisionUtils.parsePublicKey(claim.getEkPublicArea().toByteArray());
|
||||
|
||||
// Get attestation public key
|
||||
RSAPublicKey akPub = ProvisionUtils.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<PlatformCredential> 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()) {
|
||||
TPMInfo savedInfo = device.getDeviceInfo().getTpmInfo();
|
||||
TPMInfo tpmInfo = new TPMInfo(savedInfo.getTpmMake(),
|
||||
savedInfo.getTpmVersionMajor(),
|
||||
savedInfo.getTpmVersionMinor(),
|
||||
savedInfo.getTpmVersionRevMajor(),
|
||||
savedInfo.getTpmVersionRevMinor(),
|
||||
savedInfo.getPcrValues(),
|
||||
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(),
|
||||
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 = ProvisionUtils.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;
|
||||
}
|
||||
}
|
@ -0,0 +1,619 @@
|
||||
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.provision.helper.ProvisionUtils;
|
||||
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 java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
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 = ProvisionUtils.parseIdentityClaim(identityClaim);
|
||||
|
||||
// parse the EK Public key from the IdentityClaim once for use in supply chain validation
|
||||
// and later tpm20MakeCredential function
|
||||
RSAPublicKey ekPub = ProvisionUtils.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 = 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;
|
||||
|
||||
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<PlatformCredential> 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;
|
||||
|
||||
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(deviceInfoReport.getNetworkInfo().getHostname());
|
||||
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 uhEx) {
|
||||
log.error("Unable to parse IP address: ", uhEx);
|
||||
}
|
||||
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<ReferenceManifest> 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 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, tpmInfo,
|
||||
claim.getClientVersion());
|
||||
dvReport.setPaccorOutputString(claim.getPaccorOutput());
|
||||
|
||||
return dvReport;
|
||||
}
|
||||
|
||||
private boolean generateDigestRecords(final String manufacturer, final String model) {
|
||||
List<ReferenceDigestValue> rdValues = new LinkedList<>();
|
||||
SupportReferenceManifest baseSupportRim = null;
|
||||
List<SupportReferenceManifest> supplementalRims = new ArrayList<>();
|
||||
List<SupportReferenceManifest> patchRims = new ArrayList<>();
|
||||
List<SupportReferenceManifest> dbSupportRims = this.referenceManifestRepository
|
||||
.getSupportByManufacturerModel(manufacturer, model);
|
||||
List<ReferenceDigestValue> sourcedValues = referenceDigestValueRepository
|
||||
.findByManufacturerAndModel(manufacturer, model);
|
||||
|
||||
Map<String, ReferenceDigestValue> 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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private List<PlatformCredential> getPlatformCredentials(final CertificateRepository certificateRepository,
|
||||
final EndorsementCredential ec) {
|
||||
List<PlatformCredential> 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;
|
||||
}
|
||||
}
|
@ -0,0 +1,345 @@
|
||||
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.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;
|
||||
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 java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@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(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);
|
||||
|
||||
// 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 = ProvisionUtils.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 =
|
||||
ProvisionUtils.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<PlatformCredential> 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<PlatformCredential> platformCredentials, final Device device) {
|
||||
// decrypt the asymmetric / symmetric blobs
|
||||
log.debug("unwrapping identity request");
|
||||
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);
|
||||
|
||||
// generate a session key and convert to byte array
|
||||
log.debug("generating symmetric key for response");
|
||||
SymmetricKey sessionKey = ProvisionUtils.generateSymmetricKey();
|
||||
|
||||
// generate the asymmetric contents for the identity response
|
||||
log.debug("generating asymmetric contents for response");
|
||||
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 = ProvisionUtils.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 = ProvisionUtils.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 = ProvisionUtils.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 request
|
||||
* to be decrypted
|
||||
* @return the decrypted symmetric portion of an identity request.
|
||||
*/
|
||||
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;
|
||||
SymmetricKeyParams symmetricKeyParams = request.getSymmetricAlgorithm();
|
||||
if (symmetricKeyParams != null && symmetricKeyParams.getParams() != null) {
|
||||
iv = symmetricKeyParams.getParams().getIv();
|
||||
} else {
|
||||
iv = ProvisionUtils.extractInitialValue(request);
|
||||
}
|
||||
|
||||
// determine the encryption scheme from the algorithm
|
||||
EncryptionScheme asymmetricScheme =
|
||||
EncryptionScheme.fromInt(request.getAsymmetricAlgorithm().getEncryptionScheme());
|
||||
|
||||
// decrypt the asymmetric blob
|
||||
byte[] decryptedAsymmetricBlob =
|
||||
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 =
|
||||
ProvisionUtils.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<PlatformCredential> getPlatformCredentials(final EndorsementCredential ec) {
|
||||
List<PlatformCredential> 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;
|
||||
}
|
||||
|
||||
}
|
@ -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<PlatformCredential> 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;
|
||||
}
|
||||
}
|
@ -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<PlatformCredential> 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]);
|
||||
}
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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<T extends Certificate> extends DefaultDbService<T> {
|
||||
|
||||
// @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<T> 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:
|
||||
*
|
||||
* <pre>
|
||||
* {@code
|
||||
* CertificateSelector certSelector =
|
||||
* new CertificateSelector(Certificate.Type.CERTIFICATE_AUTHORITY)
|
||||
* .byIssuer("CN=Some certain issuer");
|
||||
*
|
||||
* Set<Certificate> certificates = certificateManager.get(certSelector);}
|
||||
* </pre>
|
||||
*
|
||||
* @param <T> 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 <T extends Certificate> Set<T> get(final CertificateSelector certificateSelector) {
|
||||
// return new HashSet<>(
|
||||
// (List<T>) 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 <code>ArchivableEntity</code>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
@ -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<T extends AbstractEntity> {
|
||||
/**
|
||||
* 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<T> clazz;
|
||||
@PersistenceContext
|
||||
private EntityManager entityManager;
|
||||
private JpaRepository repository;
|
||||
// structure for retrying methods in the database
|
||||
private RetryTemplate retryTemplate;
|
||||
|
||||
/**
|
||||
* Creates a new <code>DefaultDbService</code>.
|
||||
*
|
||||
* @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<T> clazz) {
|
||||
setRetryTemplate();
|
||||
}
|
||||
|
||||
public void defineRepository(final JpaRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public List<T> 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<Class<? extends Throwable>, 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 <code>Object</code> from the database. This searches the
|
||||
* database for an entry whose name matches <code>name</code>. It then
|
||||
* reconstructs the <code>Object</code> 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 <code>Object</code>
|
||||
*/
|
||||
public final T get(final String name) throws DBManagerException {
|
||||
return retryTemplate.execute(new RetryCallback<T, DBManagerException>() {
|
||||
@Override
|
||||
public T doWithRetry(final RetryContext context) throws DBManagerException {
|
||||
return doGet(name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the <code>Object</code> from the database. This searches the
|
||||
* database for an entry whose id matches <code>id</code>. It then
|
||||
* reconstructs the <code>Object</code> 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 <code>Object</code>
|
||||
*/
|
||||
public final T get(final Serializable id) throws DBManagerException {
|
||||
return retryTemplate.execute(new RetryCallback<T, DBManagerException>() {
|
||||
@Override
|
||||
public T doWithRetry(final RetryContext context) throws DBManagerException {
|
||||
return doGet(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the <code>Object</code> from the database. This searches the
|
||||
* database for an entry whose name matches <code>name</code>. It then
|
||||
* reconstructs the <code>Object</code> 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 <code>Object</code>
|
||||
*/
|
||||
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 <code>Object</code> from the database. This searches the
|
||||
* database for an entry whose id matches <code>id</code>. It then
|
||||
* reconstructs the <code>Object</code> 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 <code>Object</code>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
@ -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<Device> {
|
||||
|
||||
@Autowired
|
||||
private EntityManager entityManager;
|
||||
@Autowired
|
||||
private DeviceRepository deviceRepository;
|
||||
|
||||
|
||||
public void saveDevice(Device device) {
|
||||
this.deviceRepository.save(device);
|
||||
}
|
||||
|
||||
public void saveDevices(List<Device> devices) {
|
||||
for (Device device : devices) {
|
||||
this.deviceRepository.save(device);
|
||||
}
|
||||
}
|
||||
}
|
@ -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<Path> loadAll();
|
||||
}
|
@ -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<Path> loadAll() {
|
||||
try {
|
||||
return Files.walk(this.rootLocation, 1)
|
||||
.filter(path -> !path.equals(this.rootLocation))
|
||||
.map(this.rootLocation::relativize);
|
||||
}
|
||||
catch (IOException e) {
|
||||
return 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<PolicySettings> {
|
||||
|
||||
@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);
|
||||
// }
|
||||
}
|
@ -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<ReferenceDigestValue> {
|
||||
|
||||
@Autowired
|
||||
private ReferenceDigestValueRepository repository;
|
||||
|
||||
public List<ReferenceDigestValue> getValuesByRimId(final UUID baseId) {
|
||||
return new LinkedList<>();
|
||||
}
|
||||
}
|
@ -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<T extends ReferenceManifest> extends DefaultDbService<ReferenceManifest> implements OrderedListQuerier<ReferenceManifest> {
|
||||
|
||||
/**
|
||||
* 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 <T extends ReferenceManifest> List<T> get(
|
||||
final ReferenceManifestSelector referenceManifestSelector) {
|
||||
log.info("Getting the full set of Reference Manifest files.");
|
||||
// return new HashSet<>(
|
||||
// (List<T>) getWithCriteria(
|
||||
// referenceManifestSelector.getReferenceManifestClass(),
|
||||
// Collections.singleton(referenceManifestSelector.getCriterion())
|
||||
// )
|
||||
// );
|
||||
return (List<T>) repository.findAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilteredRecordsList getOrderedList(Class<? extends ReferenceManifest> clazz,
|
||||
String columnToOrder, boolean ascending, int firstResult,
|
||||
int maxResults, String search,
|
||||
Map<String, Boolean> searchableColumns) throws DBManagerException {
|
||||
return new FilteredRecordsList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilteredRecordsList<ReferenceManifest> getOrderedList(Class<? extends ReferenceManifest> clazz,
|
||||
String columnToOrder, boolean ascending,
|
||||
int firstResult, int maxResults, String search,
|
||||
Map<String, Boolean> searchableColumns,
|
||||
CriteriaModifier<ReferenceManifest> criteriaModifier) throws DBManagerException {
|
||||
return new FilteredRecordsList<>();
|
||||
}
|
||||
}
|
@ -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<PlatformCredential> 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();
|
||||
}
|
@ -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<SupplyChainValidation> {
|
||||
@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<PlatformCredential> 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<SupportReferenceManifest> 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<SupplyChainValidation> 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<ArchivableEntity> 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<SupplyCha
|
||||
if (credential.getAuthorityKeyIdentifier() != null
|
||||
&& !credential.getAuthorityKeyIdentifier().isEmpty()) {
|
||||
byte[] bytes = Hex.decode(credential.getAuthorityKeyIdentifier());
|
||||
skiCA = (CertificateAuthorityCredential) certificateRepository.findBySubjectKeyIdentifier(bytes);
|
||||
// CYRUS is SKI unique?
|
||||
skiCA = caCredentialRepository.findBySubjectKeyIdentifier(bytes);
|
||||
}
|
||||
|
||||
if (skiCA == null) {
|
||||
if (credential.getIssuerSorted() == null
|
||||
|| credential.getIssuerSorted().isEmpty()) {
|
||||
certAuthsWithMatchingIssuer = certificateRepository.findBySubject(credential.getHolderIssuer(),
|
||||
"CertificateAuthorityCredential");
|
||||
certAuthsWithMatchingIssuer = caCredentialRepository.findBySubject(credential.getIssuer());
|
||||
} else {
|
||||
//Get certificates by subject organization
|
||||
certAuthsWithMatchingIssuer = certificateRepository.findBySubjectSorted(credential.getIssuerSorted(),
|
||||
"CertificateAuthorityCredential");
|
||||
|
||||
certAuthsWithMatchingIssuer = caCredentialRepository.findBySubjectSorted(credential.getIssuerSorted());
|
||||
}
|
||||
} else {
|
||||
certAuthsWithMatchingIssuer.add(skiCA);
|
||||
}
|
||||
Set<String> queriedOrganizations = new HashSet<>(previouslyQueriedSubjects);
|
||||
queriedOrganizations.add(credential.getHolderIssuer());
|
||||
queriedOrganizations.add(credential.getIssuer());
|
||||
|
||||
HashSet<CertificateAuthorityCredential> 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<SupplyCha
|
||||
return keyStore;
|
||||
}
|
||||
|
||||
private boolean checkForMultipleBaseCredentials(final String platformSerialNumber) {
|
||||
boolean multiple = false;
|
||||
PlatformCredential baseCredential = null;
|
||||
private String[] buildStoredPcrs(final String pcrContent, final int algorithmLength) {
|
||||
// we have a full set of PCR values
|
||||
String[] pcrSet = pcrContent.split("\\n");
|
||||
String[] storedPcrs = new String[TPMMeasurementRecord.MAX_PCR_ID + 1];
|
||||
|
||||
if (platformSerialNumber != null) {
|
||||
List<PlatformCredential> 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;
|
||||
}
|
||||
}
|
||||
|
@ -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<T extends Certificate> {
|
||||
|
||||
private final CertificateServiceImpl certificateService;
|
||||
private final Class<T> certificateClass;
|
||||
|
||||
private final Map<String, Object> 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<T> 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<T> 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<T extends Certificate> {
|
||||
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<T> 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<T> 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<X509Certificate> getX509Certificates() throws IOException {
|
||||
Set<X509Certificate> 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<T extends Certificate> {
|
||||
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<T extends Certificate> {
|
||||
return certificateClass;
|
||||
}
|
||||
|
||||
// construct and execute query
|
||||
private Set<T> execute() {
|
||||
return certificateService.get(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the selector to query for archived and unarchived certificates.
|
||||
* @return the selector
|
||||
|
@ -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 <T> the type of Reference Integrity Manifest that will be retrieved.
|
||||
@ -44,7 +43,6 @@ public abstract class ReferenceManifestSelector<T extends ReferenceManifest> {
|
||||
public static final String RIM_FILENAME_FIELD = "fileName";
|
||||
private static final String RIM_TYPE_FIELD = "rimType";
|
||||
|
||||
private final ReferenceManifestServiceImpl referenceManifestManager;
|
||||
private final Class<T> referenceTypeClass;
|
||||
|
||||
private final Map<String, Object> fieldValueSelections;
|
||||
@ -53,35 +51,25 @@ public abstract class ReferenceManifestSelector<T extends ReferenceManifest> {
|
||||
/**
|
||||
* 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<T> referenceTypeClass) {
|
||||
this(referenceManifestManager, referenceTypeClass, true);
|
||||
public ReferenceManifestSelector(final Class<T> 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<T> referenceTypeClass,
|
||||
public ReferenceManifestSelector(final Class<T> 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<T extends ReferenceManifest> {
|
||||
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<T> 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<T> 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<T extends ReferenceManifest> {
|
||||
return this.referenceTypeClass;
|
||||
}
|
||||
|
||||
// construct and execute query
|
||||
private List<T> execute() {
|
||||
List<T> results = this.referenceManifestManager.get(this);
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the selector to query for archived and unarchived rims.
|
||||
*
|
||||
|
@ -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:
|
||||
* <p>
|
||||
* <pre>
|
||||
* <PcrValue PcrNumber="0">06fl7EXo34MWxuLq9kcXI9la9NA=</ns3:PcrValue>
|
||||
* </pre>
|
||||
* <p>
|
||||
* but using TPMMeasurementRecords result in something like:
|
||||
* <p>
|
||||
* <pre>
|
||||
* <PcrValue PcrNumber="2">
|
||||
* <hash>
|
||||
* <digest>AAECAwQFBgcICQoLDA0ODxAREhM=</digest>
|
||||
* <algorithm>SHA1</algorithm>
|
||||
* </hash>
|
||||
* </PcrValue>
|
||||
* </pre>
|
||||
*
|
||||
*/
|
||||
@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<TPMMeasurementRecord> 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<TPMMeasurementRecord> 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<TPMMeasurementRecord> getPcrValueList() {
|
||||
return Collections.unmodifiableList(pcrValueList);
|
||||
}
|
||||
}
|
@ -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.
|
||||
* <p>
|
||||
* 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<TPMMeasurementRecord> 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;
|
||||
}
|
||||
}
|
@ -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.
|
||||
* <p>
|
||||
* 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();
|
||||
|
||||
}
|
||||
}
|
@ -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<TpmPcrEvent> validateTpmEvents(final TCGEventLog tcgMeasurementLog,
|
||||
final Map<String, ReferenceDigestValue> eventValueMap,
|
||||
final PolicySettings policySettings) {
|
||||
List<TpmPcrEvent> 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<TPMMeasurementRecord> 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;
|
||||
}
|
||||
}
|
@ -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<ComponentResult> 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<PlatformCredential, SupplyChainValidation> deltaMapping) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AppraisalStatus validateEndorsementCredential(final EndorsementCredential ec,
|
||||
final KeyStore trustStore,
|
||||
final boolean acceptExpired) {
|
||||
return null;
|
||||
}
|
||||
}
|
@ -27,13 +27,12 @@ import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
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 +55,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", "hirs.attestationca.persist.entity", "hirs.attestationca.persist.service"})
|
||||
@EnableJpaRepositories(basePackages = "hirs.attestationca.persist.entity.manager")
|
||||
public class PersistenceJPAConfig implements WebMvcConfigurer {
|
||||
|
||||
@ -130,6 +129,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 +196,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<RequestMappingInfo, HandlerMethod> map = requestMappingHandlerMapping.getHandlerMethods();
|
||||
map.forEach((key, value) -> log.debug("{} {}", key, value));
|
||||
}
|
||||
}
|
@ -24,7 +24,7 @@ import java.util.UUID;
|
||||
*/
|
||||
@Log4j2
|
||||
@Controller
|
||||
@RequestMapping("/certificate-details")
|
||||
@RequestMapping("/HIRS_AttestationCAPortal/portal/certificate-details")
|
||||
public class CertificateDetailsPageController extends PageController<CertificateDetailsPageParams> {
|
||||
|
||||
/**
|
||||
@ -90,15 +90,15 @@ public class CertificateDetailsPageController extends PageController<Certificate
|
||||
break;
|
||||
case "endorsement":
|
||||
data.putAll(CertificateStringMapBuilder.getEndorsementInformation(uuid,
|
||||
certificateRepository));
|
||||
certificateRepository, caCredentialRepository));
|
||||
break;
|
||||
case "platform":
|
||||
data.putAll(CertificateStringMapBuilder.getPlatformInformation(uuid,
|
||||
certificateRepository, componentResultRepository));
|
||||
certificateRepository, componentResultRepository, caCredentialRepository));
|
||||
break;
|
||||
case "issued":
|
||||
data.putAll(CertificateStringMapBuilder.getIssuedInformation(uuid,
|
||||
certificateRepository));
|
||||
certificateRepository, caCredentialRepository));
|
||||
break;
|
||||
default:
|
||||
String typeError = "Invalid certificate type: " + params.getType();
|
||||
|
@ -75,7 +75,7 @@ import java.util.zip.ZipOutputStream;
|
||||
*/
|
||||
@Log4j2
|
||||
@Controller
|
||||
@RequestMapping("/certificate-request")
|
||||
@RequestMapping("/HIRS_AttestationCAPortal/portal/certificate-request")
|
||||
public class CertificatePageController extends PageController<NoPageParams> {
|
||||
@Autowired(required = false)
|
||||
private EntityManager entityManager;
|
||||
|
@ -43,7 +43,7 @@ import java.util.UUID;
|
||||
*/
|
||||
@Log4j2
|
||||
@Controller
|
||||
@RequestMapping("/devices")
|
||||
@RequestMapping("/HIRS_AttestationCAPortal/portal/devices")
|
||||
public class DevicePageController extends PageController<NoPageParams> {
|
||||
|
||||
private final DeviceRepository deviceRepository;
|
||||
|
@ -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<NoPageParams> {
|
||||
|
||||
@Autowired
|
||||
|
@ -14,7 +14,7 @@ import org.springframework.web.servlet.ModelAndView;
|
||||
*/
|
||||
@Controller
|
||||
@Log4j2
|
||||
@RequestMapping("/index")
|
||||
@RequestMapping("/HIRS_AttestationCAPortal/portal/index")
|
||||
public class IndexPageController extends PageController<NoPageParams> {
|
||||
|
||||
/**
|
||||
|
@ -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");
|
||||
|
@ -28,7 +28,7 @@ import java.util.Map;
|
||||
*/
|
||||
@Log4j2
|
||||
@Controller
|
||||
@RequestMapping("/policy")
|
||||
@RequestMapping("/HIRS_AttestationCAPortal/portal/policy")
|
||||
public class PolicyPageController extends PageController<NoPageParams> {
|
||||
|
||||
/**
|
||||
|
@ -46,7 +46,7 @@ import java.util.UUID;
|
||||
*/
|
||||
@Log4j2
|
||||
@Controller
|
||||
@RequestMapping("/rim-details")
|
||||
@RequestMapping("/HIRS_AttestationCAPortal/portal/rim-details")
|
||||
public class ReferenceManifestDetailsPageController extends PageController<ReferenceManifestDetailsPageParams> {
|
||||
|
||||
private final ReferenceManifestRepository referenceManifestRepository;
|
||||
|
@ -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<NoPageParams> {
|
||||
|
||||
private static final String LOG_FILE_PATTERN = "([^\\s]+(\\.(?i)(rimpcr|rimel|bin|log))$)";
|
||||
@ -491,9 +491,10 @@ public class ReferenceManifestPageController extends PageController<NoPageParams
|
||||
private ReferenceManifest findBaseRim(final SupportReferenceManifest supportRim) {
|
||||
if (supportRim != null && (supportRim.getId() != null
|
||||
&& !supportRim.getId().toString().equals(""))) {
|
||||
List<BaseReferenceManifest> baseRims = this.referenceManifestRepository
|
||||
List<BaseReferenceManifest> baseRims = new LinkedList<>();
|
||||
baseRims.add(this.referenceManifestRepository
|
||||
.getBaseByManufacturerModel(supportRim.getPlatformManufacturer(),
|
||||
supportRim.getPlatformModel());
|
||||
supportRim.getPlatformModel()));
|
||||
|
||||
for (BaseReferenceManifest base : baseRims) {
|
||||
if (base.isBase()) {
|
||||
|
@ -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<NoPageParams> {
|
||||
|
||||
@Autowired(required = false)
|
||||
|
@ -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<NoPageParams> {
|
||||
|
||||
private final SupplyChainValidationSummaryRepository supplyChainValidatorSummaryRepository;
|
||||
|
@ -43,7 +43,8 @@ public final class CertificateStringMapBuilder {
|
||||
* @return a hash map with the general certificate information.
|
||||
*/
|
||||
public static HashMap<String, String> getGeneralCertificateInfo(
|
||||
final Certificate certificate, final CertificateRepository certificateRepository) {
|
||||
final Certificate certificate, final CertificateRepository certificateRepository,
|
||||
final CACredentialRepository caCertificateRepository) {
|
||||
HashMap<String, String> 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<CertificateAuthorityCredential> 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<String, String> 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<String, String> getEndorsementInformation(final UUID uuid,
|
||||
final CertificateRepository certificateRepository) {
|
||||
final CertificateRepository certificateRepository,
|
||||
final CACredentialRepository caCertificateRepository) {
|
||||
HashMap<String, String> 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<String, Object> getPlatformInformation(final UUID uuid,
|
||||
final CertificateRepository certificateRepository,
|
||||
final ComponentResultRepository componentResultRepository)
|
||||
final ComponentResultRepository componentResultRepository,
|
||||
final CACredentialRepository caCertificateRepository)
|
||||
throws IllegalArgumentException, IOException {
|
||||
HashMap<String, Object> 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<String, String> getIssuedInformation(final UUID uuid,
|
||||
final CertificateRepository certificateRepository) {
|
||||
final CertificateRepository certificateRepository,
|
||||
final CACredentialRepository caCredentialRepository) {
|
||||
HashMap<String, String> 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) {
|
||||
|
@ -12,13 +12,13 @@ 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
|
||||
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
|
||||
|
@ -13,7 +13,7 @@
|
||||
<c:set var="icons" value="${images}/icons" scope="session" />
|
||||
<c:set var="common" value="${baseURL}/common" scope="session" />
|
||||
<c:set var="lib" value="${baseURL}/lib" scope="session" />
|
||||
<c:set var="portal" value="${baseURL}/portal" scope="session" />
|
||||
<c:set var="portal" value="${baseURL}/HIRS_AttestationCAPortal/portal" scope="session" />
|
||||
<c:set var="pagePath" value="${portal}/${page.prefixPath}${page.viewName}" scope="session" />
|
||||
<c:set var="certificateRequest" value="${portal}/certificate-request" scope="session" />
|
||||
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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<string> 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;
|
||||
|
@ -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
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
|
@ -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')
|
||||
|
Loading…
Reference in New Issue
Block a user