This is a series of code changes to begin setting up for provisioning.

The code has not been tested to provision. The url linkage needs to be
worked on again.
This commit is contained in:
Cyrus 2023-08-29 15:15:34 -04:00
parent afee8019af
commit 6a2c5d246b
52 changed files with 4356 additions and 1098 deletions

View File

@ -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()
}

View File

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

View File

@ -0,0 +1,146 @@
package hirs.attestationca.persist;
import hirs.attestationca.persist.entity.manager.CertificateRepository;
import hirs.attestationca.persist.entity.manager.ComponentResultRepository;
import hirs.attestationca.persist.entity.manager.DeviceRepository;
import hirs.attestationca.persist.entity.manager.IssuedCertificateRepository;
import hirs.attestationca.persist.entity.manager.PolicyRepository;
import hirs.attestationca.persist.entity.manager.ReferenceDigestValueRepository;
import hirs.attestationca.persist.entity.manager.ReferenceManifestRepository;
import hirs.attestationca.persist.entity.manager.TPM2ProvisionerStateRepository;
import hirs.attestationca.persist.entity.userdefined.certificate.IssuedAttestationCertificate;
import hirs.attestationca.persist.service.SupplyChainValidationService;
import hirs.structs.converters.StructConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
/**
* Restful implementation of the {@link AttestationCertificateAuthority}.
* Exposes the ACA methods as REST endpoints.
*/
@PropertySource(value = "file:/etc/hirs/aca/application.properties",
ignoreResourceNotFound = true)
@RestController
@RequestMapping("/")
public class RestfulAttestationCertificateAuthority extends AttestationCertificateAuthority implements RestfulInterface {
/**
* Constructor.
*
* @param supplyChainValidationService scp service
* @param privateKey the ACA private key
* @param acaCertificate the ACA certificate
* @param componentResultRepository the component result repository
* @param certificateRepository the certificate manager
* @param referenceManifestRepository the referenceManifestManager
* @param validDays the number of days issued certs are valid
* @param deviceRepository the device manager
* @param referenceDigestValueRepository the reference event repository
* @param policyRepository the provisioning policy entity
* @param tpm2ProvisionerStateRepository the provisioner state
*/
@SuppressWarnings({"checkstyle:parameternumber"})
@Autowired
public RestfulAttestationCertificateAuthority(
final SupplyChainValidationService supplyChainValidationService,
final PrivateKey privateKey, final X509Certificate acaCertificate,
final StructConverter structConverter,
final ComponentResultRepository componentResultRepository,
final CertificateRepository certificateRepository,
final IssuedCertificateRepository issuedCertificateRepository,
final ReferenceManifestRepository referenceManifestRepository,
final DeviceRepository deviceRepository,
final ReferenceDigestValueRepository referenceDigestValueRepository,
@Value("${aca.certificates.validity}") final int validDays,
final PolicyRepository policyRepository,
final TPM2ProvisionerStateRepository tpm2ProvisionerStateRepository) {
super(supplyChainValidationService, privateKey, acaCertificate, structConverter,
componentResultRepository, certificateRepository, issuedCertificateRepository,
referenceManifestRepository,
validDays, deviceRepository,
referenceDigestValueRepository, policyRepository, tpm2ProvisionerStateRepository);
}
/**
* Processes a given IdentityRequestEnvelope and
* generates a IdentityResponseEnvelope. In most cases,
* a client will generate the request using the TPM "Collate Identity" process.
*
* Wrap the {@link AttestationCertificateAuthority#processIdentityRequest(byte[])}
* with a Spring {@link org.springframework.web.bind.annotation.RequestMapping}. Effectively, this method then will allow spring to
* serialize and deserialize the request and responses on method invocation and
* return, respectively.
*
* @param identityRequest generated during the collate identity process with a Tpm
* @return response for the request
*/
@Override
@ResponseBody
@RequestMapping(value = "/identity-request/process",
method = RequestMethod.POST,
consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public byte[] processIdentityRequest(@RequestBody final byte[] identityRequest) {
return super.processIdentityRequest(identityRequest);
}
/**
* Listener for identity requests from TPM 2.0 provisioning.
*
* Processes a given IdentityClaim and generates a response
* containing an encrypted nonce to be returned by the client in
* a future handshake request.
*
* @param identityClaim The request object from the provisioner.
* @return The response to the provisioner.
*/
@Override
@ResponseBody
@RequestMapping(value = "/identity-claim-tpm2/process",
method = RequestMethod.POST,
consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public byte[] processIdentityClaimTpm2(@RequestBody final byte[] identityClaim) {
return super.processIdentityClaimTpm2(identityClaim);
}
/**
* Processes a given CertificateRequest
* and generates a response containing the signed, public certificate for
* the client's desired attestation key, if the correct nonce is supplied.
*
* @param certificateRequest request containing nonce from earlier identity
* * claim handshake
* @return The response to the client provisioner.
*/
@Override
@ResponseBody
@RequestMapping(value = "/request-certificate-tpm2",
method = RequestMethod.POST,
consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public byte[] processCertificateRequest(@RequestBody final byte[] certificateRequest) {
return super.processCertificateRequest(certificateRequest);
}
/**
* (non-javadoc)
* <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();
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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.

View File

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

View File

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

View File

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

View File

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

View File

@ -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.

View File

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

View File

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

View File

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

View File

@ -0,0 +1,485 @@
package hirs.attestationca.persist.provision;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import hirs.attestationca.configuration.provisionerTpm2.ProvisionerTpm2;
import hirs.attestationca.persist.entity.manager.CertificateRepository;
import hirs.attestationca.persist.entity.manager.PolicyRepository;
import hirs.attestationca.persist.entity.userdefined.Certificate;
import hirs.attestationca.persist.entity.userdefined.Device;
import hirs.attestationca.persist.entity.userdefined.PolicySettings;
import hirs.attestationca.persist.entity.userdefined.certificate.EndorsementCredential;
import hirs.attestationca.persist.entity.userdefined.certificate.IssuedAttestationCertificate;
import hirs.attestationca.persist.entity.userdefined.certificate.PlatformCredential;
import hirs.attestationca.persist.entity.userdefined.info.TPMInfo;
import hirs.attestationca.persist.exceptions.CertificateProcessingException;
import hirs.attestationca.persist.exceptions.IdentityProcessingException;
import hirs.attestationca.persist.exceptions.UnexpectedServerException;
import hirs.attestationca.persist.provision.helper.CredentialManagementHelper;
import hirs.attestationca.persist.provision.helper.IssuedCertificateAttributeHelper;
import hirs.utils.HexUtils;
import hirs.utils.enums.DeviceInfoEnums;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.codec.binary.Hex;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPublicKeySpec;
import java.util.Calendar;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
@Log4j2
@NoArgsConstructor
public class AbstractRequestHandler {
/**
* The default size for IV blocks.
*/
public final static int DEFAULT_IV_SIZE = 16;
/**
* Defines the well known exponent.
* https://en.wikipedia.org/wiki/65537_(number)#Applications
*/
private final static BigInteger EXPONENT = new BigInteger("010001", DEFAULT_IV_SIZE);
/**
* Number of bytes to include in the TPM2.0 nonce.
*/
public static final int NONCE_LENGTH = 20;
public static final int SEED_LENGTH = 32;
public static final int MAX_SECRET_LENGTH = 32;
public static final int RSA_MODULUS_LENGTH = 256;
public static final int AES_KEY_LENGTH_BYTES = 16;
public static final int HMAC_KEY_LENGTH_BYTES = 32;
public static final int HMAC_SIZE_LENGTH_BYTES = 2;
public static final int TPM2_CREDENTIAL_BLOB_SIZE = 392;
// Constants used to parse out the ak name from the ak public data. Used in generateAkName
public static final String AK_NAME_PREFIX = "000b";
public static final String AK_NAME_HASH_PREFIX =
"0001000b00050072000000100014000b0800000000000100";
public static final String TPM_SIGNATURE_ALG = "sha";
@Getter
private int validDays;
@Getter
private PrivateKey privateKey;
@Setter
@Getter
private String tpmQuoteHash = "";
@Setter
@Getter
private String tpmQuoteSignature = "";
@Setter
@Getter
private PolicyRepository policyRepository;
public AbstractRequestHandler(final PrivateKey privateKey,
final int validDays) {
this.privateKey = privateKey;
this.validDays = validDays;
}
/**
* Parse public key from public data segment generated by TPM 2.0.
* @param publicArea the public area segment to parse
* @return the RSA public key of the supplied public data
*/
RSAPublicKey parsePublicKey(final byte[] publicArea) {
int pubLen = publicArea.length;
if (pubLen < RSA_MODULUS_LENGTH) {
throw new IllegalArgumentException(
"EK or AK public data segment is not long enough");
}
// public data ends with 256 byte modulus
byte[] modulus = HexUtils.subarray(publicArea,
pubLen - RSA_MODULUS_LENGTH,
pubLen - 1);
return (RSAPublicKey) assemblePublicKey(modulus);
}
/**
* Constructs a public key where the modulus is in raw form.
*
* @param modulus
* in byte array form
* @return public key using specific modulus and the well known exponent
*/
PublicKey assemblePublicKey(final byte[] modulus) {
return assemblePublicKey(Hex.encodeHexString(modulus));
}
/**
* Constructs a public key where the modulus is Hex encoded.
*
* @param modulus
* hex encoded modulus
* @return public key using specific modulus and the well known exponent
*/
PublicKey assemblePublicKey(final String modulus) {
return assemblePublicKey(new BigInteger(modulus, DEFAULT_IV_SIZE));
}
/**
* Assembles a public key using a defined big int modulus and the well known exponent.
*/
private PublicKey assemblePublicKey(final BigInteger modulus) {
// generate a key spec using mod and exp
RSAPublicKeySpec keySpec = new RSAPublicKeySpec(modulus, EXPONENT);
// create the public key
try {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(keySpec);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new UnexpectedServerException(
"Encountered unexpected error creating public key: " + e.getMessage(), e);
}
}
/**
* Helper method to parse a byte array into an {@link ProvisionerTpm2.IdentityClaim}.
*
* @param identityClaim byte array that should be converted to a Protobuf IdentityClaim
* object
* @throws {@link IdentityProcessingException} if byte array could not be parsed
* @return the Protobuf generated Identity Claim object
*/
public ProvisionerTpm2.IdentityClaim parseIdentityClaim(final byte[] identityClaim) {
try {
return ProvisionerTpm2.IdentityClaim.parseFrom(identityClaim);
} catch (InvalidProtocolBufferException ipbe) {
throw new IdentityProcessingException(
"Could not deserialize Protobuf Identity Claim object.", ipbe);
}
}
/**
* Helper method to extract a DER encoded ASN.1 certificate from an X509 certificate.
*
* @param certificate the X509 certificate to be converted to DER encoding
* @throws {@link UnexpectedServerException} if error occurs during encoding retrieval
* @return the byte array representing the DER encoded certificate
*/
public byte[] getDerEncodedCertificate(final X509Certificate certificate) {
try {
return certificate.getEncoded();
} catch (CertificateEncodingException ceEx) {
log.error("Error converting certificate to ASN.1 DER Encoding.", ceEx);
throw new UnexpectedServerException(
"Encountered error while converting X509 Certificate to ASN.1 DER Encoding: "
+ ceEx.getMessage(), ceEx);
}
}
/**
* Generates a array of random bytes.
*
* @param numberOfBytes
* to be generated
* @return byte array filled with the specified number of bytes.
*/
public byte[] generateRandomBytes(final int numberOfBytes) {
byte[] bytes = new byte[numberOfBytes];
SecureRandom random = new SecureRandom();
random.nextBytes(bytes);
return bytes;
}
/**
* Generates a credential using the specified public key.
*
* @param publicKey cannot be null
* @param endorsementCredential the endorsement credential
* @param platformCredentials the set of platform credentials
* @param deviceName The host name used in the subject alternative name
* @return identity credential
*/
protected X509Certificate generateCredential(final PublicKey publicKey,
final EndorsementCredential endorsementCredential,
final List<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;
}
/**
* This method takes the provided TPM Quote and splits it between the PCR
* quote and the signature hash.
* @param tpmQuote contains hash values for the quote and the signature
*/
public boolean parseTPMQuote(final String tpmQuote) {
boolean success = false;
if (tpmQuote != null) {
String[] lines = tpmQuote.split(":");
if (lines[1].contains("signature")) {
this.tpmQuoteHash = lines[1].replace("signature", "").trim();
} else {
this.tpmQuoteHash = lines[1].trim();
}
this.tpmQuoteSignature = lines[2].trim();
success = true;
}
return success;
}
/**
* Helper method to create the TPMInfo used for device.
* @return a new TPMInfo Instance
*/
public TPMInfo createTpmInfo(final String pcrValues) {
return new TPMInfo(DeviceInfoEnums.NOT_SPECIFIED,
(short) 0,
(short) 0,
(short) 0,
(short) 0,
pcrValues.getBytes(StandardCharsets.UTF_8),
this.getTpmQuoteHash().getBytes(StandardCharsets.UTF_8),
this.getTpmQuoteSignature().getBytes(StandardCharsets.UTF_8));
}
/**
* Helper method to create an {@link IssuedAttestationCertificate} object, set its
* corresponding device and persist it.
*
* @param derEncodedAttestationCertificate the byte array representing the Attestation
* certificate
* @param endorsementCredential the endorsement credential used to generate the AC
* @param platformCredentials the platform credentials used to generate the AC
* @param device the device to which the attestation certificate is tied
* @throws {@link CertificateProcessingException} if error occurs in persisting the Attestation
* Certificate
*/
public void saveAttestationCertificate(final CertificateRepository certificateRepository,
final byte[] derEncodedAttestationCertificate,
final EndorsementCredential endorsementCredential,
final List<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 = 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;
}
@SuppressWarnings("magicnumber")
private int daysBetween(final Date date1, final Date date2) {
return (int) ((date2.getTime() - date1.getTime()) / (1000 * 60 * 60 * 24));
}
}

View File

@ -0,0 +1,218 @@
package hirs.attestationca.persist.provision;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import hirs.attestationca.configuration.provisionerTpm2.ProvisionerTpm2;
import hirs.attestationca.persist.entity.manager.CertificateRepository;
import hirs.attestationca.persist.entity.manager.DeviceRepository;
import hirs.attestationca.persist.entity.manager.TPM2ProvisionerStateRepository;
import hirs.attestationca.persist.entity.tpm.TPM2ProvisionerState;
import hirs.attestationca.persist.entity.userdefined.Device;
import hirs.attestationca.persist.entity.userdefined.SupplyChainValidationSummary;
import hirs.attestationca.persist.entity.userdefined.certificate.EndorsementCredential;
import hirs.attestationca.persist.entity.userdefined.certificate.PlatformCredential;
import hirs.attestationca.persist.entity.userdefined.info.TPMInfo;
import hirs.attestationca.persist.entity.userdefined.report.DeviceInfoReport;
import hirs.attestationca.persist.enums.AppraisalStatus;
import hirs.attestationca.persist.exceptions.CertificateProcessingException;
import hirs.attestationca.persist.service.SupplyChainValidationService;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.ArrayUtils;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPublicKey;
import java.util.List;
@Log4j2
public class CertificateRequestHandler extends AbstractRequestHandler {
private SupplyChainValidationService supplyChainValidationService;
private CertificateRepository certificateRepository;
private DeviceRepository deviceRepository;
private X509Certificate acaCertificate;
private TPM2ProvisionerStateRepository tpm2ProvisionerStateRepository;
/**
* Constructor.
* @param certificateRepository db connector for all certificates.
* @param deviceRepository database connector for Devices.
* @param validDays int for the time in which a certificate is valid.
* @param tpm2ProvisionerStateRepository db connector for provisioner state.
*/
public CertificateRequestHandler(final SupplyChainValidationService supplyChainValidationService,
final CertificateRepository certificateRepository,
final DeviceRepository deviceRepository,
final PrivateKey privateKey,
final X509Certificate acaCertificate,
final int validDays,
final TPM2ProvisionerStateRepository tpm2ProvisionerStateRepository) {
super(privateKey, validDays);
this.supplyChainValidationService = supplyChainValidationService;
this.certificateRepository = certificateRepository;
this.deviceRepository = deviceRepository;
this.acaCertificate = acaCertificate;
this.tpm2ProvisionerStateRepository = tpm2ProvisionerStateRepository;
}
/**
* Basic implementation of the ACA processCertificateRequest method.
* Parses the nonce, validates its correctness, generates the signed,
* public attestation certificate, stores it, and returns it to the client.
*
* @param certificateRequest request containing nonce from earlier identity
* claim handshake
* @return a certificateResponse containing the signed certificate
*/
public byte[] processCertificateRequest(final byte[] certificateRequest) {
log.info("Certificate Request received...");
if (ArrayUtils.isEmpty(certificateRequest)) {
throw new IllegalArgumentException("The CertificateRequest sent by the client"
+ " cannot be null or empty.");
}
// attempt to deserialize Protobuf CertificateRequest
ProvisionerTpm2.CertificateRequest request;
try {
request = ProvisionerTpm2.CertificateRequest.parseFrom(certificateRequest);
} catch (InvalidProtocolBufferException ipbe) {
throw new CertificateProcessingException(
"Could not deserialize Protobuf Certificate Request object.", ipbe);
}
// attempt to retrieve provisioner state based on nonce in request
TPM2ProvisionerState tpm2ProvisionerState = getTpm2ProvisionerState(request);
if (tpm2ProvisionerState != null) {
// Reparse Identity Claim to gather necessary components
byte[] identityClaim = tpm2ProvisionerState.getIdentityClaim();
ProvisionerTpm2.IdentityClaim claim = parseIdentityClaim(identityClaim);
// Get endorsement public key
RSAPublicKey ekPub = parsePublicKey(claim.getEkPublicArea().toByteArray());
// Get attestation public key
RSAPublicKey akPub = parsePublicKey(claim.getAkPublicArea().toByteArray());
// Get Endorsement Credential if it exists or was uploaded
EndorsementCredential endorsementCredential = parseEcFromIdentityClaim(claim, ekPub, certificateRepository);
// Get Platform Credentials if they exist or were uploaded
List<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()) {
parseTPMQuote(request.getQuote().toStringUtf8());
TPMInfo savedInfo = device.getDeviceInfo().getTpmInfo();
TPMInfo tpmInfo = new TPMInfo(savedInfo.getTpmMake(),
savedInfo.getTpmVersionMajor(),
savedInfo.getTpmVersionMinor(),
savedInfo.getTpmVersionRevMajor(),
savedInfo.getTpmVersionRevMinor(),
savedInfo.getPcrValues(),
getTpmQuoteHash().getBytes(StandardCharsets.UTF_8),
getTpmQuoteSignature().getBytes(StandardCharsets.UTF_8));
DeviceInfoReport dvReport = new DeviceInfoReport(
device.getDeviceInfo().getNetworkInfo(),
device.getDeviceInfo().getOSInfo(),
device.getDeviceInfo().getFirmwareInfo(),
device.getDeviceInfo().getHardwareInfo(), tpmInfo,
claim.getClientVersion());
device.setDeviceInfo(dvReport);
device = this.deviceRepository.save(device);
}
AppraisalStatus.Status validationResult = doQuoteValidation(device);
if (validationResult == AppraisalStatus.Status.PASS) {
// Create signed, attestation certificate
X509Certificate attestationCertificate = generateCredential(akPub,
endorsementCredential, platformCredentials, deviceName, acaCertificate);
byte[] derEncodedAttestationCertificate = getDerEncodedCertificate(
attestationCertificate);
// We validated the nonce and made use of the identity claim so state can be deleted
tpm2ProvisionerStateRepository.delete(tpm2ProvisionerState);
// Package the signed certificate into a response
ByteString certificateBytes = ByteString
.copyFrom(derEncodedAttestationCertificate);
ProvisionerTpm2.CertificateResponse response = ProvisionerTpm2.CertificateResponse
.newBuilder().setCertificate(certificateBytes)
.setStatus(ProvisionerTpm2.ResponseStatus.PASS)
.build();
saveAttestationCertificate(certificateRepository, derEncodedAttestationCertificate,
endorsementCredential, platformCredentials, device);
return response.toByteArray();
} else {
log.error("Supply chain validation did not succeed. "
+ "Firmware Quote Validation failed. Result is: "
+ validationResult);
ProvisionerTpm2.CertificateResponse response = ProvisionerTpm2.CertificateResponse
.newBuilder()
.setStatus(ProvisionerTpm2.ResponseStatus.FAIL)
.build();
return response.toByteArray();
}
} else {
log.error("Could not process credential request. Invalid nonce provided: "
+ request.getNonce().toString());
throw new CertificateProcessingException("Invalid nonce given in request by client.");
}
}
/**
* Helper method to unwrap the certificate request sent by the client and verify the
* provided nonce.
*
* @param request Client Certificate Request containing nonce to complete identity claim
* @return the {@link TPM2ProvisionerState} if valid nonce provided / null, otherwise
*/
private TPM2ProvisionerState getTpm2ProvisionerState(
final ProvisionerTpm2.CertificateRequest request) {
if (request.hasNonce()) {
byte[] nonce = request.getNonce().toByteArray();
return TPM2ProvisionerState.getTPM2ProvisionerState(tpm2ProvisionerStateRepository,
nonce);
}
return null;
}
/**
* Performs supply chain validation for just the quote under Firmware validation.
* Performed after main supply chain validation and a certificate request.
*
* @param device associated device to validate.
* @return the {@link AppraisalStatus} of the supply chain validation
*/
private AppraisalStatus.Status doQuoteValidation(final Device device) {
// perform supply chain validation
SupplyChainValidationSummary scvs = supplyChainValidationService.validateQuote(
device);
AppraisalStatus.Status validationResult;
// either validation wasn't enabled or device already failed
if (scvs == null) {
// this will just allow for the certificate to be saved.
validationResult = AppraisalStatus.Status.PASS;
} else {
device.setSummaryId(scvs.getId().toString());
// update the validation result in the device
validationResult = scvs.getOverallValidationResult();
device.setSupplyChainValidationStatus(validationResult);
deviceRepository.save(device);
}
return validationResult;
}
}

View File

@ -0,0 +1,815 @@
package hirs.attestationca.persist.provision;
import com.google.protobuf.ByteString;
import hirs.attestationca.configuration.provisionerTpm2.ProvisionerTpm2;
import hirs.attestationca.persist.entity.manager.CertificateRepository;
import hirs.attestationca.persist.entity.manager.DeviceRepository;
import hirs.attestationca.persist.entity.manager.PolicyRepository;
import hirs.attestationca.persist.entity.manager.ReferenceDigestValueRepository;
import hirs.attestationca.persist.entity.manager.ReferenceManifestRepository;
import hirs.attestationca.persist.entity.manager.TPM2ProvisionerStateRepository;
import hirs.attestationca.persist.entity.tpm.TPM2ProvisionerState;
import hirs.attestationca.persist.entity.userdefined.Device;
import hirs.attestationca.persist.entity.userdefined.PolicySettings;
import hirs.attestationca.persist.entity.userdefined.ReferenceManifest;
import hirs.attestationca.persist.entity.userdefined.SupplyChainValidationSummary;
import hirs.attestationca.persist.entity.userdefined.certificate.EndorsementCredential;
import hirs.attestationca.persist.entity.userdefined.certificate.PlatformCredential;
import hirs.attestationca.persist.entity.userdefined.info.FirmwareInfo;
import hirs.attestationca.persist.entity.userdefined.info.HardwareInfo;
import hirs.attestationca.persist.entity.userdefined.info.NetworkInfo;
import hirs.attestationca.persist.entity.userdefined.info.OSInfo;
import hirs.attestationca.persist.entity.userdefined.info.TPMInfo;
import hirs.attestationca.persist.entity.userdefined.report.DeviceInfoReport;
import hirs.attestationca.persist.entity.userdefined.rim.BaseReferenceManifest;
import hirs.attestationca.persist.entity.userdefined.rim.EventLogMeasurements;
import hirs.attestationca.persist.entity.userdefined.rim.ReferenceDigestValue;
import hirs.attestationca.persist.entity.userdefined.rim.SupportReferenceManifest;
import hirs.attestationca.persist.enums.AppraisalStatus;
import hirs.attestationca.persist.exceptions.IdentityProcessingException;
import hirs.attestationca.persist.service.SupplyChainValidationService;
import hirs.utils.HexUtils;
import hirs.utils.SwidResource;
import hirs.utils.enums.DeviceInfoEnums;
import hirs.utils.tpm.eventlog.TCGEventLog;
import hirs.utils.tpm.eventlog.TpmPcrEvent;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.ArrayUtils;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.OAEPParameterSpec;
import javax.crypto.spec.PSource;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.MGF1ParameterSpec;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Log4j2
public class IdentityClaimHandler extends AbstractRequestHandler {
private static final String PCR_QUOTE_MASK = "0,1,2,3,4,5,6,7,8,9,10,11,12,13,"
+ "14,15,16,17,18,19,20,21,22,23";
private static final int NUM_OF_VARIABLES = 5;
/**
* Number of bytes to include in the TPM2.0 nonce.
*/
public static final int NONCE_LENGTH = 20;
private static final int MAC_BYTES = 6;
private SupplyChainValidationService supplyChainValidationService;
private CertificateRepository certificateRepository;
private ReferenceManifestRepository referenceManifestRepository;
private ReferenceDigestValueRepository referenceDigestValueRepository;
private DeviceRepository deviceRepository;
private TPM2ProvisionerStateRepository tpm2ProvisionerStateRepository;
/**
* Constructor
*/
public IdentityClaimHandler(
final SupplyChainValidationService supplyChainValidationService,
final CertificateRepository certificateRepository,
final ReferenceManifestRepository referenceManifestRepository,
final ReferenceDigestValueRepository referenceDigestValueRepository,
final DeviceRepository deviceRepository,
final TPM2ProvisionerStateRepository tpm2ProvisionerStateRepository,
final PolicyRepository policyRepository) {
this.supplyChainValidationService = supplyChainValidationService;
this.certificateRepository = certificateRepository;
this.referenceManifestRepository = referenceManifestRepository;
this.referenceDigestValueRepository = referenceDigestValueRepository;
this.deviceRepository = deviceRepository;
this.tpm2ProvisionerStateRepository = tpm2ProvisionerStateRepository;
setPolicyRepository(policyRepository);
}
/**
* Basic implementation of the ACA processIdentityClaimTpm2 method. Parses the claim,
* stores the device info, performs supply chain validation, generates a nonce,
* and wraps that nonce with the make credential process before returning it to the client.
* attCert.setPcrValues(pcrValues);
* @param identityClaim the request to process, cannot be null
* @return an identity claim response for the specified request containing a wrapped blob
*/
public byte[] processIdentityClaimTpm2(final byte[] identityClaim) {
log.error("Identity Claim received...");
if (ArrayUtils.isEmpty(identityClaim)) {
log.error("Identity claim empty throwing exception.");
throw new IllegalArgumentException("The IdentityClaim sent by the client"
+ " cannot be null or empty.");
}
// attempt to deserialize Protobuf IdentityClaim
ProvisionerTpm2.IdentityClaim claim = parseIdentityClaim(identityClaim);
// parse the EK Public key from the IdentityClaim once for use in supply chain validation
// and later tpm20MakeCredential function
RSAPublicKey ekPub = parsePublicKey(claim.getEkPublicArea().toByteArray());
AppraisalStatus.Status validationResult = AppraisalStatus.Status.FAIL;
try {
validationResult = doSupplyChainValidation(claim, ekPub);
} catch (Exception ex) {
for (StackTraceElement ste : ex.getStackTrace()) {
log.error(ste.toString());
}
}
ByteString blobStr = ByteString.copyFrom(new byte[]{});
if (validationResult == AppraisalStatus.Status.PASS) {
RSAPublicKey akPub = parsePublicKey(claim.getAkPublicArea().toByteArray());
byte[] nonce = generateRandomBytes(NONCE_LENGTH);
blobStr = tpm20MakeCredential(ekPub, akPub, nonce);
PolicyRepository scp = this.getPolicyRepository();
PolicySettings policySettings = scp.findByName("Default");
String pcrQuoteMask = PCR_QUOTE_MASK;
String strNonce = HexUtils.byteArrayToHexString(nonce);
log.info("Sending nonce: " + strNonce);
log.info("Persisting claim of length: " + identityClaim.length);
tpm2ProvisionerStateRepository.save(new TPM2ProvisionerState(nonce, identityClaim));
if (policySettings != null && policySettings.isIgnoreImaEnabled()) {
pcrQuoteMask = PCR_QUOTE_MASK.replace("10,", "");
}
// Package response
ProvisionerTpm2.IdentityClaimResponse response
= ProvisionerTpm2.IdentityClaimResponse.newBuilder()
.setCredentialBlob(blobStr).setPcrMask(pcrQuoteMask)
.setStatus(ProvisionerTpm2.ResponseStatus.PASS)
.build();
return response.toByteArray();
} else {
log.error("Supply chain validation did not succeed. Result is: "
+ validationResult);
// empty response
ProvisionerTpm2.IdentityClaimResponse response
= ProvisionerTpm2.IdentityClaimResponse.newBuilder()
.setCredentialBlob(blobStr)
.setStatus(ProvisionerTpm2.ResponseStatus.FAIL)
.build();
return response.toByteArray();
}
}
/**
* Performs supply chain validation.
*
* @param claim the identity claim
* @param ekPub the public endorsement key
* @return the {@link AppraisalStatus} of the supply chain validation
*/
private AppraisalStatus.Status doSupplyChainValidation(
final ProvisionerTpm2.IdentityClaim claim, final PublicKey ekPub) {
// attempt to find an endorsement credential to validate
EndorsementCredential endorsementCredential = parseEcFromIdentityClaim(claim, ekPub, certificateRepository);
// attempt to find platform credentials to validate
List<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;
String deviceName = deviceInfoReport.getNetworkInfo().getHostname();
try {
deviceInfoReport = parseDeviceInfo(claim);
} catch (NoSuchAlgorithmException noSaEx) {
log.error(noSaEx);
}
if (deviceInfoReport == null) {
log.error("Failed to deserialize Device Info Report");
throw new IdentityProcessingException("Device Info Report failed to deserialize "
+ "from Identity Claim");
}
log.info("Processing Device Info Report");
// store device and device info report.
Device device = this.deviceRepository.findByName(deviceName);
device.setDeviceInfo(deviceInfoReport);
return this.deviceRepository.save(device);
}
/**
* Converts a protobuf DeviceInfo object to a HIRS Utils DeviceInfoReport object.
* @param claim the protobuf serialized identity claim containing the device info
* @return a HIRS Utils DeviceInfoReport representation of device info
*/
@SuppressWarnings("methodlength")
private DeviceInfoReport parseDeviceInfo(final ProvisionerTpm2.IdentityClaim claim)
throws NoSuchAlgorithmException {
ProvisionerTpm2.DeviceInfo dv = claim.getDv();
String pcrValues = "";
// Get network info
ProvisionerTpm2.NetworkInfo nwProto = dv.getNw();
InetAddress ip = null;
try {
ip = InetAddress.getByName(nwProto.getIpAddress());
} catch (UnknownHostException e) {
log.error("Unable to parse IP address: ", e);
}
String[] macAddressParts = nwProto.getMacAddress().split(":");
// convert mac hex string to byte values
byte[] macAddressBytes = new byte[MAC_BYTES];
Integer hex;
if (macAddressParts.length == MAC_BYTES) {
for (int i = 0; i < MAC_BYTES; i++) {
hex = HexUtils.hexToInt(macAddressParts[i]);
macAddressBytes[i] = hex.byteValue();
}
}
NetworkInfo nw = new NetworkInfo(nwProto.getHostname(), ip, macAddressBytes);
// Get firmware info
ProvisionerTpm2.FirmwareInfo fwProto = dv.getFw();
FirmwareInfo fw = new FirmwareInfo(fwProto.getBiosVendor(), fwProto.getBiosVersion(),
fwProto.getBiosReleaseDate());
// Get OS info
ProvisionerTpm2.OsInfo osProto = dv.getOs();
OSInfo os = new OSInfo(osProto.getOsName(), osProto.getOsVersion(), osProto.getOsArch(),
osProto.getDistribution(), osProto.getDistributionRelease());
// Get hardware info
ProvisionerTpm2.HardwareInfo hwProto = dv.getHw();
// Make sure chassis info has at least one chassis
String firstChassisSerialNumber = DeviceInfoEnums.NOT_SPECIFIED;
if (hwProto.getChassisInfoCount() > 0) {
firstChassisSerialNumber = hwProto.getChassisInfo(0).getSerialNumber();
}
// Make sure baseboard info has at least one baseboard
String firstBaseboardSerialNumber = DeviceInfoEnums.NOT_SPECIFIED;
if (hwProto.getBaseboardInfoCount() > 0) {
firstBaseboardSerialNumber = hwProto.getBaseboardInfo(0).getSerialNumber();
}
HardwareInfo hw = new HardwareInfo(hwProto.getManufacturer(), hwProto.getProductName(),
hwProto.getProductVersion(), hwProto.getSystemSerialNumber(),
firstChassisSerialNumber, firstBaseboardSerialNumber);
if (dv.hasPcrslist()) {
pcrValues = dv.getPcrslist().toStringUtf8();
}
// check for RIM Base and Support files, if they don't exists in the database, load them
String defaultClientName = String.format("%s_%s",
dv.getHw().getManufacturer(),
dv.getHw().getProductName());
BaseReferenceManifest dbBaseRim = null;
SupportReferenceManifest support;
EventLogMeasurements measurements;
String tagId = "";
String fileName = "";
Pattern pattern = Pattern.compile("([^\\s]+(\\.(?i)(rimpcr|rimel|bin|log))$)");
Matcher matcher;
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
List<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 tpm = createTpmInfo(pcrValues);
// Create final report
DeviceInfoReport dvReport = new DeviceInfoReport(nw, os, fw, hw, tpm,
claim.getClientVersion());
dvReport.setPaccorOutputString(claim.getPaccorOutput());
return dvReport;
}
private boolean generateDigestRecords(final String manufacturer, final String model) {
List<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;
}
/**
* Performs the first step of the TPM 2.0 identity claim process. Takes an ek, ak, and secret
* and then generates a seed that is used to generate AES and HMAC keys. Parses the ak name.
* Encrypts the seed with the public ek. Uses the AES key to encrypt the secret. Uses the HMAC
* key to generate an HMAC to cover the encrypted secret and the ak name. The output is an
* encrypted blob that acts as the first part of a challenge-response authentication mechanism
* to validate an identity claim.
*
* Equivalent to calling tpm2_makecredential using tpm2_tools.
*
* @param ek endorsement key in the identity claim
* @param ak attestation key in the identity claim
* @param secret a nonce
* @return the encrypted blob forming the identity claim challenge
*/
protected ByteString tpm20MakeCredential(final RSAPublicKey ek, final RSAPublicKey ak,
final byte[] secret) {
// check size of the secret
if (secret.length > MAX_SECRET_LENGTH) {
throw new IllegalArgumentException("Secret must be " + MAX_SECRET_LENGTH
+ " bytes or smaller.");
}
// generate a random 32 byte seed
byte[] seed = generateRandomBytes(SEED_LENGTH);
try {
// encrypt seed with pubEk
Cipher asymCipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
OAEPParameterSpec oaepSpec = new OAEPParameterSpec("SHA-256", "MGF1",
MGF1ParameterSpec.SHA256, new PSource.PSpecified("IDENTITY\0".getBytes()));
asymCipher.init(Cipher.PUBLIC_KEY, ek, oaepSpec);
asymCipher.update(seed);
byte[] encSeed = asymCipher.doFinal();
// generate ak name from akMod
byte[] akModTemp = ak.getModulus().toByteArray();
byte[] akMod = new byte[RSA_MODULUS_LENGTH];
int startpos = 0;
// BigIntegers are signed, so a modulus that has a first bit of 1
// will be padded with a zero byte that must be removed
if (akModTemp[0] == 0x00) {
startpos = 1;
}
System.arraycopy(akModTemp, startpos, akMod, 0, RSA_MODULUS_LENGTH);
byte[] akName = generateAkName(akMod);
// generate AES and HMAC keys from seed
byte[] aesKey = cryptKDFa(seed, "STORAGE", akName, AES_KEY_LENGTH_BYTES);
byte[] hmacKey = cryptKDFa(seed, "INTEGRITY", null, HMAC_KEY_LENGTH_BYTES);
// use two bytes to add a size prefix on secret
ByteBuffer b;
b = ByteBuffer.allocate(2);
b.putShort((short) (secret.length));
byte[] secretLength = b.array();
byte[] secretBytes = new byte[secret.length + 2];
System.arraycopy(secretLength, 0, secretBytes, 0, 2);
System.arraycopy(secret, 0, secretBytes, 2, secret.length);
// encrypt size prefix + secret with AES key
Cipher symCipher = Cipher.getInstance("AES/CFB/NoPadding");
byte[] defaultIv = HexUtils.hexStringToByteArray("00000000000000000000000000000000");
IvParameterSpec ivSpec = new IvParameterSpec(defaultIv);
symCipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(aesKey, "AES"), ivSpec);
byte[] encSecret = symCipher.doFinal(secretBytes);
// generate HMAC covering encrypted secret and ak name
Mac integrityHmac = Mac.getInstance("HmacSHA256");
SecretKeySpec integrityKey = new SecretKeySpec(hmacKey, integrityHmac.getAlgorithm());
integrityHmac.init(integrityKey);
byte[] message = new byte[encSecret.length + akName.length];
System.arraycopy(encSecret, 0, message, 0, encSecret.length);
System.arraycopy(akName, 0, message, encSecret.length, akName.length);
integrityHmac.update(message);
byte[] integrity = integrityHmac.doFinal();
b = ByteBuffer.allocate(2);
b.putShort((short) (HMAC_SIZE_LENGTH_BYTES + HMAC_KEY_LENGTH_BYTES + encSecret.length));
byte[] topSize = b.array();
// return ordered blob of assembled credentials
byte[] bytesToReturn = assembleCredential(topSize, integrity, encSecret, encSeed);
return ByteString.copyFrom(bytesToReturn);
} catch (BadPaddingException | IllegalBlockSizeException | NoSuchAlgorithmException
| InvalidKeyException | InvalidAlgorithmParameterException
| NoSuchPaddingException e) {
throw new IdentityProcessingException(
"Encountered error while making the identity claim challenge: "
+ e.getMessage(), e);
}
}
@SuppressWarnings("magicnumber")
private byte[] assembleCredential(final byte[] topSize, final byte[] integrityHmac,
final byte[] encryptedSecret,
final byte[] encryptedSeed) {
/*
* Credential structure breakdown with endianness:
* 0-1 topSize (2), LE
* 2-3 hashsize (2), BE always 0x0020
* 4-35 integrity HMac (32)
* 36-133 (98 = 32*3 +2) of zeros, copy over from encSecret starting at [36]
* 134-135 (2) LE size, always 0x0001
* 136-391 (256) copy over with encSeed
* */
byte[] credentialBlob = new byte[TPM2_CREDENTIAL_BLOB_SIZE];
credentialBlob[0] = topSize[1];
credentialBlob[1] = topSize[0];
credentialBlob[2] = 0x00;
credentialBlob[3] = 0x20;
System.arraycopy(integrityHmac, 0, credentialBlob, 4, 32);
for (int i = 0; i < 98; i++) {
credentialBlob[36 + i] = 0x00;
}
System.arraycopy(encryptedSecret, 0, credentialBlob, 36, encryptedSecret.length);
credentialBlob[134] = 0x00;
credentialBlob[135] = 0x01;
System.arraycopy(encryptedSeed, 0, credentialBlob, 136, 256);
// return the result
return credentialBlob;
}
/**
* Determines the AK name from the AK Modulus.
* @param akModulus modulus of an attestation key
* @return the ak name byte array
* @throws NoSuchAlgorithmException Underlying SHA256 method used a bad algorithm
*/
byte[] generateAkName(final byte[] akModulus) throws NoSuchAlgorithmException {
byte[] namePrefix = HexUtils.hexStringToByteArray(AK_NAME_PREFIX);
byte[] hashPrefix = HexUtils.hexStringToByteArray(AK_NAME_HASH_PREFIX);
byte[] toHash = new byte[hashPrefix.length + akModulus.length];
System.arraycopy(hashPrefix, 0, toHash, 0, hashPrefix.length);
System.arraycopy(akModulus, 0, toHash, hashPrefix.length, akModulus.length);
byte[] nameHash = sha256hash(toHash);
byte[] toReturn = new byte[namePrefix.length + nameHash.length];
System.arraycopy(namePrefix, 0, toReturn, 0, namePrefix.length);
System.arraycopy(nameHash, 0, toReturn, namePrefix.length, nameHash.length);
return toReturn;
}
/**
* This replicates the TPM 2.0 CryptKDFa function to an extent. It will only work for generation
* that uses SHA-256, and will only generate values of 32 B or less. Counters above zero and
* multiple contexts are not supported in this implementation. This should work for all uses of
* the KDF for TPM2_MakeCredential.
*
* @param seed random value used to generate the key
* @param label first portion of message used to generate key
* @param context second portion of message used to generate key
* @param sizeInBytes size of key to generate in bytes
* @return the derived key
* @throws NoSuchAlgorithmException Wrong crypto algorithm selected
* @throws InvalidKeyException Invalid key used
*/
@SuppressWarnings("magicnumber")
private byte[] cryptKDFa(final byte[] seed, final String label, final byte[] context,
final int sizeInBytes)
throws NoSuchAlgorithmException, InvalidKeyException {
ByteBuffer b = ByteBuffer.allocate(4);
b.putInt(1);
byte[] counter = b.array();
// get the label
String labelWithEnding = label;
if (label.charAt(label.length() - 1) != "\0".charAt(0)) {
labelWithEnding = label + "\0";
}
byte[] labelBytes = labelWithEnding.getBytes();
b = ByteBuffer.allocate(4);
b.putInt(sizeInBytes * 8);
byte[] desiredSizeInBits = b.array();
int sizeOfMessage = 8 + labelBytes.length;
if (context != null) {
sizeOfMessage += context.length;
}
byte[] message = new byte[sizeOfMessage];
int marker = 0;
System.arraycopy(counter, 0, message, marker, 4);
marker += 4;
System.arraycopy(labelBytes, 0, message, marker, labelBytes.length);
marker += labelBytes.length;
if (context != null) {
System.arraycopy(context, 0, message, marker, context.length);
marker += context.length;
}
System.arraycopy(desiredSizeInBits, 0, message, marker, 4);
Mac hmac;
byte[] toReturn = new byte[sizeInBytes];
hmac = Mac.getInstance("HmacSHA256");
SecretKeySpec hmacKey = new SecretKeySpec(seed, hmac.getAlgorithm());
hmac.init(hmacKey);
hmac.update(message);
byte[] hmacResult = hmac.doFinal();
System.arraycopy(hmacResult, 0, toReturn, 0, sizeInBytes);
return toReturn;
}
/**
* Computes the sha256 hash of the given blob.
* @param blob byte array to take the hash of
* @return sha256 hash of blob
* @throws NoSuchAlgorithmException improper algorithm selected
*/
private byte[] sha256hash(final byte[] blob) throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(blob);
return md.digest();
}
}

View File

@ -0,0 +1,574 @@
package hirs.attestationca.persist.provision;
import hirs.attestationca.persist.entity.manager.CertificateRepository;
import hirs.attestationca.persist.entity.manager.DeviceRepository;
import hirs.attestationca.persist.entity.userdefined.Certificate;
import hirs.attestationca.persist.entity.userdefined.Device;
import hirs.attestationca.persist.entity.userdefined.SupplyChainValidationSummary;
import hirs.attestationca.persist.entity.userdefined.certificate.EndorsementCredential;
import hirs.attestationca.persist.entity.userdefined.certificate.PlatformCredential;
import hirs.attestationca.persist.entity.userdefined.report.DeviceInfoReport;
import hirs.attestationca.persist.enums.AppraisalStatus;
import hirs.attestationca.persist.exceptions.CertificateProcessingException;
import hirs.attestationca.persist.exceptions.IdentityProcessingException;
import hirs.attestationca.persist.provision.helper.CredentialManagementHelper;
import hirs.attestationca.persist.service.SupplyChainValidationService;
import hirs.structs.converters.SimpleStructBuilder;
import hirs.structs.converters.StructConverter;
import hirs.structs.elements.aca.IdentityRequestEnvelope;
import hirs.structs.elements.aca.IdentityResponseEnvelope;
import hirs.structs.elements.aca.SymmetricAttestation;
import hirs.structs.elements.tpm.EncryptionScheme;
import hirs.structs.elements.tpm.IdentityProof;
import hirs.structs.elements.tpm.IdentityRequest;
import hirs.structs.elements.tpm.SymmetricKey;
import hirs.structs.elements.tpm.SymmetricKeyParams;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.SerializationUtils;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.OAEPParameterSpec;
import javax.crypto.spec.PSource;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.security.spec.MGF1ParameterSpec;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
@Log4j2
public class IdentityRequestHandler extends AbstractRequestHandler {
/**
* Container wired ACA private key.
*/
private final PrivateKey privateKey;
private int validDays;
private StructConverter structConverter;
private CertificateRepository certificateRepository;
private DeviceRepository deviceRepository;
private SupplyChainValidationService supplyChainValidationService;
private X509Certificate acaCertificate;
/**
* Constructor.
* @param structConverter the struct converter
* @param certificateRepository
* @param deviceRepository
* @param supplyChainValidationService the supply chain service
* @param privateKey
* @param validDays int for the time in which a certificate is valid.
* @param acaCertificate object holding the x509 certificate
*/
public IdentityRequestHandler(final StructConverter structConverter,
final CertificateRepository certificateRepository,
final DeviceRepository deviceRepository,
final SupplyChainValidationService supplyChainValidationService,
final PrivateKey privateKey,
final int validDays, final X509Certificate acaCertificate) {
super(privateKey, validDays);
this.structConverter = structConverter;
this.certificateRepository = certificateRepository;
this.deviceRepository = deviceRepository;
this.supplyChainValidationService = supplyChainValidationService;
this.privateKey = privateKey;
this.acaCertificate = acaCertificate;
}
/**
* Basic implementation of the ACA processIdentityRequest method.
*
* @param identityRequest cannot be null
* @return an identity response for the specified request
*/
public byte[] processIdentityRequest(final byte[] identityRequest) {
log.info("Identity Request Received...");
if (ArrayUtils.isEmpty(identityRequest)) {
throw new IllegalArgumentException("The IdentityRequest sent by the client"
+ " cannot be null or empty.");
}
log.debug("received request to process identity request");
// translate the bytes into the challenge
IdentityRequestEnvelope challenge =
structConverter.convert(identityRequest, IdentityRequestEnvelope.class);
byte[] identityProof = unwrapIdentityRequest(challenge.getRequest());
// the decrypted symmetric blob should be in the format of an IdentityProof. Use the
// struct converter to generate it.
IdentityProof proof = structConverter.convert(identityProof, IdentityProof.class);
// convert the credential into an actual key.
log.debug("assembling public endorsement key");
PublicKey ekPublicKey = null;
// attempt to find an endorsement credential to validate
EndorsementCredential endorsementCredential = null;
// first check the identity request for the endorsement credential
byte[] ecBytesFromIdentityRequest = proof.getEndorsementCredential();
if (ArrayUtils.isNotEmpty(ecBytesFromIdentityRequest)) {
endorsementCredential = CredentialManagementHelper.storeEndorsementCredential(
this.certificateRepository, ecBytesFromIdentityRequest);
try {
BigInteger publicKeyModulus = Certificate.getPublicKeyModulus(
endorsementCredential.getX509Certificate());
if (publicKeyModulus != null) {
ekPublicKey = assemblePublicKey(publicKeyModulus.toByteArray());
} else {
throw new IdentityProcessingException("TPM 1.2 Provisioning requires EK "
+ "Credentials to be created with RSA");
}
} catch (IOException ioEx) {
log.error("Could not retrieve the public key modulus from the EK cert");
}
} else if (ArrayUtils.isNotEmpty(challenge.getEndorsementCredentialModulus())) {
log.warn("EKC was not in the identity proof from the client. Checking for uploads.");
// Check if the EC was uploaded
ekPublicKey =
assemblePublicKey(new String(challenge.getEndorsementCredentialModulus()));
endorsementCredential = getEndorsementCredential(ekPublicKey);
} else {
log.warn("Zero-length endorsement credential received in identity request.");
}
// get platform credential from the identity request
List<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(challenge.getRequest());
// the decrypted symmetric blob should be in the format of an IdentityProof. Use the
// struct converter to generate it.
IdentityProof proof = structConverter.convert(identityProof, IdentityProof.class);
// generate a session key and convert to byte array
log.debug("generating symmetric key for response");
SymmetricKey sessionKey = generateSymmetricKey();
// generate the asymmetric contents for the identity response
log.debug("generating asymmetric contents for response");
byte[] asymmetricContents = generateAsymmetricContents(proof, sessionKey, ekPublicKey);
// generate the identity credential
log.debug("generating credential from identity proof");
// transform the public key struct into a public key
PublicKey publicKey = assemblePublicKey(proof.getIdentityKey().getStorePubKey().getKey());
X509Certificate credential = generateCredential(publicKey, endorsementCredential,
platformCredentials, device.getDeviceInfo()
.getNetworkInfo()
.getIpAddress()
.getHostName(), acaCertificate);
// generate the attestation using the credential and the key for this session
log.debug("generating symmetric response");
SymmetricAttestation attestation = generateAttestation(credential, sessionKey);
// construct the response with the both the asymmetric contents and the CA attestation
IdentityResponseEnvelope identityResponse =
new SimpleStructBuilder<>(IdentityResponseEnvelope.class)
.set("asymmetricContents", asymmetricContents)
.set("symmetricAttestation", attestation).build();
// save new attestation certificate
byte[] derEncodedAttestationCertificate = getDerEncodedCertificate(credential);
saveAttestationCertificate(this.certificateRepository, derEncodedAttestationCertificate,
endorsementCredential, platformCredentials, device);
return identityResponse;
}
/**
* Unwraps a given identityRequest. That is to say, decrypt the asymmetric portion of a data
* structure to determine the method to decrypt the symmetric portion.
*
* @param identityRequest
* to be decrypted
* @return the decrypted symmetric portion of an identity request.
*/
private byte[] unwrapIdentityRequest(final byte[] identityRequest) {
IdentityRequest request = structConverter.convert(identityRequest, IdentityRequest.class);
// in case the TPM did not specify the IV, it must be extracted from the symmetric blob.
// the IV will then be the the first block of the cipher text.
final byte[] iv;
SymmetricKeyParams symmetricKeyParams = request.getSymmetricAlgorithm();
if (symmetricKeyParams != null && symmetricKeyParams.getParams() != null) {
iv = symmetricKeyParams.getParams().getIv();
} else {
iv = extractInitialValue(request);
}
// determine the encryption scheme from the algorithm
EncryptionScheme asymmetricScheme =
EncryptionScheme.fromInt(request.getAsymmetricAlgorithm().getEncryptionScheme());
// decrypt the asymmetric blob
byte[] decryptedAsymmetricBlob =
decryptAsymmetricBlob(request.getAsymmetricBlob(), asymmetricScheme);
// construct our symmetric key structure from the decrypted asymmetric blob
SymmetricKey symmetricKey =
structConverter.convert(decryptedAsymmetricBlob, SymmetricKey.class);
byte[] decryptedSymmetricBlob =
decryptSymmetricBlob(request.getSymmetricBlob(), symmetricKey.getKey(), iv,
"AES/CBC/PKCS5Padding");
// decrypt the symmetric blob
return decryptedSymmetricBlob;
}
/**
* Gets the Endorsement Credential from the DB given the EK public key.
* @param ekPublicKey the EK public key
* @return the Endorsement credential, if found, otherwise null
*/
private EndorsementCredential getEndorsementCredential(final PublicKey ekPublicKey) {
log.debug("Searching for endorsement credential based on public key: " + ekPublicKey);
if (ekPublicKey == null) {
throw new IllegalArgumentException("Cannot look up an EC given a null public key");
}
EndorsementCredential credential = null;
try {
credential = certificateRepository.findByPublicKeyModulusHexValue(Certificate
.getPublicKeyModulus(ekPublicKey)
.toString());
} catch (IOException ioEx) {
log.error("Could not extract public key modulus", ioEx);
}
if (credential == null) {
log.warn("Unable to find endorsement credential for public key.");
} else {
log.debug("Endorsement credential found.");
}
return credential;
}
private List<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;
}
/**
* Will attempt to decrypt the asymmetric blob that originated from an
* {@link hirs.structs.elements.tpm.IdentityRequest} using the cipher transformation.
*
* @param asymmetricBlob to be decrypted
* @param scheme to decrypt with
* @return decrypted blob
*/
private byte[] decryptAsymmetricBlob(final byte[] asymmetricBlob, final EncryptionScheme scheme) {
try {
// create a cipher from the specified transformation
Cipher cipher = Cipher.getInstance(scheme.toString());
switch (scheme) {
case OAEP:
OAEPParameterSpec spec =
new OAEPParameterSpec("Sha1", "MGF1", MGF1ParameterSpec.SHA1,
new PSource.PSpecified("".getBytes()));
cipher.init(Cipher.PRIVATE_KEY, privateKey, spec);
break;
default:
// initialize the cipher to decrypt using the ACA private key.
cipher.init(Cipher.DECRYPT_MODE, privateKey);
}
cipher.update(asymmetricBlob);
return cipher.doFinal();
} catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException
| BadPaddingException | IllegalBlockSizeException
| InvalidAlgorithmParameterException e) {
throw new IdentityProcessingException(
"Encountered error while decrypting asymmetric blob of an identity request: "
+ e.getMessage(), e);
}
}
/**
* Will attempt to decrypt the symmetric blob that originated from an
* {@link hirs.structs.elements.tpm.IdentityRequest} using the specified symmetric key
* and cipher transformation.
*
* @param symmetricBlob to be decrypted
* @param symmetricKey to use to decrypt
* @param iv to use with decryption cipher
* @param transformation of the cipher
* @return decrypted symmetric blob
*/
private byte[] decryptSymmetricBlob(final byte[] symmetricBlob, final byte[] symmetricKey,
final byte[] iv, final String transformation) {
try {
// create a cipher from the specified transformation
Cipher cipher = Cipher.getInstance(transformation);
// generate a key specification to initialize the cipher
SecretKeySpec keySpec = new SecretKeySpec(symmetricKey, "AES");
// initialize the cipher to decrypt using the symmetric key
cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv));
// decrypt the symmetric blob
return cipher.doFinal(symmetricBlob);
} catch (IllegalBlockSizeException | InvalidKeyException | NoSuchAlgorithmException
| BadPaddingException | NoSuchPaddingException
| InvalidAlgorithmParameterException exception) {
log.error("Encountered error while decrypting symmetric blob of an identity request: "
+ exception.getMessage(), exception);
}
return new byte[0];
}
private SymmetricKey generateSymmetricKey() {
// create a session key for the CA contents
byte[] responseSymmetricKey =
generateRandomBytes(DEFAULT_IV_SIZE);
// create a symmetric key struct for the CA contents
SymmetricKey sessionKey =
new SimpleStructBuilder<>(SymmetricKey.class)
.set("algorithmId", SymmetricKey.ALGORITHM_AES)
.set("encryptionScheme", SymmetricKey.SCHEME_CBC)
.set("key", responseSymmetricKey).build();
return sessionKey;
}
/**
* Generate asymmetric contents part of the identity response.
*
* @param proof identity requests symmetric contents, otherwise, the identity proof
* @param symmetricKey identity response session key
* @param publicKey of the EK certificate contained within the identity proof
* @return encrypted asymmetric contents
*/
byte[] generateAsymmetricContents(final IdentityProof proof,
final SymmetricKey symmetricKey,
final PublicKey publicKey) {
try {
// obtain the identity key from the identity proof
byte[] identityKey = structConverter.convert(proof.getIdentityKey());
byte[] sessionKey = structConverter.convert(symmetricKey);
// create a SHA1 digest of the identity key
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(identityKey);
// generate the digest
byte[] identityDigest = md.digest();
// combine the session key with the digest of the identity key
byte[] asymmetricContents = ArrayUtils.addAll(sessionKey, identityDigest);
// encrypt the asymmetric contents and return
OAEPParameterSpec oaepSpec =
new OAEPParameterSpec("Sha1", "MGF1", MGF1ParameterSpec.SHA1,
new PSource.PSpecified("TCPA".getBytes()));
// initialize the asymmetric cipher using the default OAEP transformation
Cipher cipher = Cipher.getInstance(EncryptionScheme.OAEP.toString());
// initialize the cipher using the public spec with the additional OAEP specification
cipher.init(Cipher.PUBLIC_KEY, publicKey, oaepSpec);
return cipher.doFinal(asymmetricContents);
} catch (NoSuchAlgorithmException | IllegalBlockSizeException | NoSuchPaddingException
| InvalidKeyException | BadPaddingException
| InvalidAlgorithmParameterException e) {
throw new CertificateProcessingException(
"Encountered error while generating ACA session key: " + e.getMessage(), e);
}
}
/**
* Extracts the IV from the identity request. That is, take the first block of data from the
* symmetric blob and treat that as the IV. This modifies the original symmetric block.
*
* @param identityRequest to extract the IV from
* @return the IV from the identity request
*/
private byte[] extractInitialValue(final IdentityRequest identityRequest) {
// make a reference to the symmetric blob
byte[] symmetricBlob = identityRequest.getSymmetricBlob();
// create the IV
byte[] iv = new byte[DEFAULT_IV_SIZE];
// initialize a new symmetric blob with the length of the original minus the IV
byte[] updatedBlob = new byte[symmetricBlob.length - iv.length];
// copy the IV out of the original symmetric blob
System.arraycopy(symmetricBlob, 0, iv, 0, iv.length);
// copy everything but the IV out of the original blob into the new blob
System.arraycopy(symmetricBlob, iv.length, updatedBlob, 0, updatedBlob.length);
// reassign the symmetric blob to the request.
identityRequest.setSymmetricBlob(updatedBlob);
return iv;
}
/**
* Generate the Identity Response using the identity credential and the session key.
*
* @param credential the identity credential
* @param symmetricKey generated session key for this request/response chain
* @return identity response for an identity request
*/
SymmetricAttestation generateAttestation(final X509Certificate credential,
final SymmetricKey symmetricKey) {
try {
// initialize the symmetric cipher
Cipher aesCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
// generate a key specification to initialize the cipher
SecretKeySpec keySpec = new SecretKeySpec(symmetricKey.getKey(), "AES");
// fill IV with random bytes
byte[] credentialIV = generateRandomBytes(DEFAULT_IV_SIZE);
// create IV encryption parameter specification
IvParameterSpec ivParameterSpec = new IvParameterSpec(credentialIV);
// initialize the cipher to decrypt using the symmetric key
aesCipher.init(Cipher.ENCRYPT_MODE, keySpec, ivParameterSpec);
// encrypt the credential
byte[] encryptedCredential = aesCipher.doFinal(credential.getEncoded());
// prepend the IV to the encrypted credential
byte[] credentialBytes = ArrayUtils.addAll(credentialIV, encryptedCredential);
// create attestation for identity response that contains the credential
SymmetricAttestation attestation =
new SimpleStructBuilder<>(SymmetricAttestation.class)
.set("credential", credentialBytes)
.set("algorithm",
new SimpleStructBuilder<>(SymmetricKeyParams.class)
.set("algorithmId", SymmetricKeyParams.ALGORITHM_AES)
.set("encryptionScheme",
SymmetricKeyParams.SCHEME_CBC_PKCS5PADDING)
.set("signatureScheme", 0).build()).build();
return attestation;
} catch (BadPaddingException | IllegalBlockSizeException | NoSuchAlgorithmException
| InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException
| CertificateEncodingException exception) {
throw new CertificateProcessingException(
"Encountered error while generating Identity Response: "
+ exception.getMessage(), exception);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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.
*

View File

@ -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>&nbsp;
* <pre>
* &lt;PcrValue PcrNumber="0"&gt;06fl7EXo34MWxuLq9kcXI9la9NA=&lt;/ns3:PcrValue&gt;
* </pre>
* <p>
* but using TPMMeasurementRecords result in something like:
* <p>&nbsp;
* <pre>
* &lt;PcrValue PcrNumber="2"&gt;
* &lt;hash&gt;
* &lt;digest&gt;AAECAwQFBgcICQoLDA0ODxAREhM=&lt;/digest&gt;
* &lt;algorithm&gt;SHA1&lt;/algorithm&gt;
* &lt;/hash&gt;
* &lt;/PcrValue&gt;
* </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);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,7 @@
package hirs.attestationca.portal;
import hirs.attestationca.persist.service.SupplyChainValidationService;
import hirs.attestationca.persist.service.SupplyChainValidationServiceImpl;
import jakarta.annotation.PostConstruct;
import lombok.extern.log4j.Log4j2;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
@ -26,14 +28,14 @@ import org.springframework.web.servlet.config.annotation.DefaultServletHandlerCo
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
//import javax.sql.DataSource;
import javax.sql.DataSource;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.util.Properties;
@ -56,7 +58,7 @@ import java.util.Properties;
@PropertySource(value = "file:/etc/hirs/aca/application.properties",
ignoreResourceNotFound = true)
})
@ComponentScan({"hirs.attestationca.portal", "hirs.attestationca.portal.page.controllers", "hirs.attestationca.persist.entity"})
@ComponentScan({"hirs.attestationca.portal", "hirs.attestationca.portal.page.controllers", "hirs.attestationca.persist.entity", "hirs.attestationca.persist.service"})
@EnableJpaRepositories(basePackages = "hirs.attestationca.persist.entity.manager")
public class PersistenceJPAConfig implements WebMvcConfigurer {
@ -88,6 +90,11 @@ public class PersistenceJPAConfig implements WebMvcConfigurer {
return entityManagerBean;
}
// @Bean
// public SupplyChainValidationService supplyChainValidationService() {
// return new SupplyChainValidationServiceImpl();
// }
@Bean
public DataSource dataSource() {
final DriverManagerDataSource dataSource = new DriverManagerDataSource();
@ -130,6 +137,34 @@ public class PersistenceJPAConfig implements WebMvcConfigurer {
// }
}
/**
* @return the {@link PrivateKey} of the ACA
*/
@Bean
public PrivateKey privateKey() {
// obtain the key store
KeyStore keyStore = keyStore();
try {
// load the key from the key store
PrivateKey acaKey = (PrivateKey) keyStore.getKey(keyAlias,
keyStorePassword.toCharArray());
// break early if the certificate is not available.
if (acaKey == null) {
throw new BeanInitializationException(String.format("Key with alias "
+ "%s was not in KeyStore %s. Ensure that the KeyStore has the "
+ "specified certificate. ", keyAlias, keyStoreLocation));
}
return acaKey;
} catch (Exception ex) {
throw new BeanInitializationException("Encountered error loading ACA private key "
+ "from key store: " + ex.getMessage(), ex);
}
}
/**
* @return the {@link X509Certificate} of the ACA
*/
@ -169,13 +204,13 @@ public class PersistenceJPAConfig implements WebMvcConfigurer {
keyStore.load(Files.newInputStream(keyStorePath), keyStorePassword.toCharArray());
return keyStore;
} catch (Exception e) {
} catch (Exception ex) {
log.error(String.format(
"Encountered error while loading ACA key store. The most common issue is "
+ "that configured password does not work on the configured key"
+ " store %s.", keyStorePath));
log.error(String.format("Exception message: %s", e.getMessage()));
throw new BeanInitializationException(e.getMessage(), e);
log.error(String.format("Exception message: %s", ex.getMessage()));
throw new BeanInitializationException(ex.getMessage(), ex);
}
}

View File

@ -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();

View File

@ -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()) {

View File

@ -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) {

View File

@ -15,7 +15,7 @@ jakarta.persistence.sharedCache.mode = UNSPECIFIED
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
#spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
aca.certificates.validity = 3652
# Tomcat Config
server.tomcat.additional-tld-skip-patterns=jakarta.persistence-api*.jar, jakarta.xml.bind-api*.jar, txw2*.jar, *commons*.jar, *annotations*.jar, *checker*.jar, *lombok*.jar, *jsr*.jar, *guava*.jar, *access*.jar, *activation*.jar, *bcprov*.jar, *bcmail*.jar, *bcutil*.jar, *bcpkix*.jar, *json*.jar
server.tomcat.basedir=/opt/embeddedtomcat

View File

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

View File

@ -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')