Create IDevID certificate page (#727)

Adding IDevID certificate page and parsing support
This commit is contained in:
CAFB385655BEB1060E85B6C080B432F8EB2A2AF78459BD6532124977B933154A 2024-04-18 20:28:11 +00:00 committed by chubtub
parent 55629a704e
commit 80ba36cbf2
12 changed files with 762 additions and 19 deletions

View File

@ -0,0 +1,25 @@
package hirs.attestationca.persist.entity.manager;
import hirs.attestationca.persist.entity.userdefined.certificate.IDevIDCertificate;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.UUID;
@Repository
public interface IDevIDCertificateRepository extends JpaRepository<IDevIDCertificate, UUID> {
List<IDevIDCertificate> findByArchiveFlag(boolean archiveFlag);
Page<IDevIDCertificate> findByArchiveFlag(boolean archiveFlag, Pageable pageable);
/*List<IDevIDCertificate> findBySubject(String subject);
List<IDevIDCertificate> findBySubjectSorted(String subject);
List<IDevIDCertificate> findBySubjectAndArchiveFlag(String subject, boolean archiveFlag);
List<IDevIDCertificate> findBySubjectSortedAndArchiveFlag(String subject, boolean archiveFlag);
IDevIDCertificate findBySubjectKeyIdentifier(byte[] subjectKeyIdentifier);
IDevIDCertificate findBySubjectKeyIdStringAndArchiveFlag(String subjectKeyIdString, boolean archiveFlag);
*/
}

View File

@ -318,9 +318,10 @@ public abstract class Certificate extends ArchivableEntity {
switch (getCertificateType()) {
case X509_CERTIFICATE:
X509Certificate x509Certificate = getX509Certificate();
this.serialNumber = x509Certificate.getSerialNumber();
this.issuer = x509Certificate.getIssuerX500Principal().getName();
this.subject = x509Certificate.getSubjectX500Principal().getName();
this.issuer = Certificate.getIssuerDNString(x509Certificate);
this.subject = Certificate.getSubjectDNString(x509Certificate);
this.encodedPublicKey = x509Certificate.getPublicKey().getEncoded();
BigInteger publicKeyModulus = getPublicKeyModulus(x509Certificate);
@ -851,6 +852,46 @@ public abstract class Certificate extends ArchivableEntity {
return Files.readAllBytes(certificatePath);
}
/**
* Retrieve a formatted subject DN string from a certificate. This allows for extended support of DNs found in
* various RFCs.
*
* @param certificate the certificate holding subject DNs
* @return IOException if there is an issue decoding the subject DNs
*/
public static String getSubjectDNString(final X509Certificate certificate)
throws IOException {
X509CertificateHolder certificateHolder = null;
try {
certificateHolder = new X509CertificateHolder(certificate.getEncoded());
} catch (CertificateEncodingException e) {
throw new IOException("Could not encode certificate", e);
}
X500Name x500Name = certificateHolder.getSubject();
return x500Name.toString();
}
/**
* Retrieve a formatted issuer DN string from a certificate. This allows for extended support of DNs found in
* various RFCs.
*
* @param certificate the certificate holding issuer DNs
* @return IOException if there is an issue decoding the issuer DNs
*/
public static String getIssuerDNString(final X509Certificate certificate)
throws IOException {
X509CertificateHolder certificateHolder = null;
try {
certificateHolder = new X509CertificateHolder(certificate.getEncoded());
} catch (CertificateEncodingException e) {
throw new IOException("Could not encode certificate", e);
}
X500Name x500Name = certificateHolder.getIssuer();
return x500Name.toString();
}
/**
* Retrieve an RSA-based X509 certificate's public key modulus.
*

View File

@ -0,0 +1,333 @@
package hirs.attestationca.persist.entity.userdefined.certificate;
import hirs.attestationca.persist.entity.userdefined.Certificate;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Transient;
import lombok.Getter;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.codec.binary.Hex;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.x509.CertificatePolicies;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.PolicyInformation;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@Entity
@Log4j2
public class IDevIDCertificate extends Certificate {
// Undefined expiry date, as specified in 802.1AR
private static final long UNDEFINED_EXPIRY_DATE = 253402300799L;
// Supported OIDs
@SuppressWarnings("PMD.AvoidUsingHardCodedIP")
private static final String SUBJECT_ALTERNATIVE_NAME_EXTENSION = "2.5.29.17";
private static final String HARDWARE_MODULE_NAME_OID = "1.3.6.1.5.5.7.8.4";
private static final String HWTYPE_TCG_TPM2_OID = "2.23.133.1.2";
// Other OIDs (policy)
private static final String POLICY_QUALIFIER_VERIFIED_TPM_RESIDENCY = "2.23.133.11.1.1";
private static final String POLICY_QUALIFIER_VERIFIED_TPM_FIXED = "2.23.133.11.1.2";
private static final String POLICY_QUALIFIER_VERIFIED_TPM_RESTRICTED = "2.23.133.11.1.3";
@Getter
@Transient
private byte[] subjectAltName;
/**
* Corresponds to the hwType field found in a Hardware Module Name (if present).
*/
@Getter
@Column
private String hwType;
/**
* Corresponds to the serial number found in a Hardware Module Name (if present).
*/
@Getter
@Column
private byte[] hwSerialNum;
/**
* TPM policy qualifiers (TCG only).
*/
@Getter
@Column
private String tpmPolicies;
/**
* Construct a new IDevIDCertificate given its binary contents. The given
* certificate should represent a valid X.509 certificate.
*
* @param certificateBytes the contents of a certificate file
* @throws IOException if there is a problem extracting information from the certificate
*/
public IDevIDCertificate(final byte[] certificateBytes)
throws IOException {
super(certificateBytes);
this.parseIDevIDCertificate();
}
/**
* Construct a new IDevIDCertificate by parsing the file at the given path.
* The given certificate should represent a valid X.509 certificate.
*
* @param certificatePath the path on disk to a certificate
* @throws IOException if there is a problem reading the file
*/
public IDevIDCertificate(final Path certificatePath)
throws IOException {
super(certificatePath);
this.parseIDevIDCertificate();
}
/**
* Default constructor for Hibernate.
*/
protected IDevIDCertificate() {
subjectAltName = null;
}
/**
* Obtains TPM policy qualifiers from the Certificate Policies extension, if present. These policy qualifiers are
* specified in the TCG document "TPM 2.0 Keys for Device Identity and Attestation".
*
* @return A {@link java.util.Map} containing the policy qualifiers obtained.
* @throws IOException if policy qualifiers cannot be parsed from extension value
*/
public Map<String, Boolean> getTPMPolicyQualifiers(byte[] policyBytes) throws IOException {
CertificatePolicies certPolicies =
CertificatePolicies.getInstance(JcaX509ExtensionUtils.parseExtensionValue(policyBytes));
Map<String, Boolean> policyQualifiers = new HashMap<>();
boolean verifiedTPMResidency = false;
boolean verifiedTPMFixed = false;
boolean verifiedTPMRestricted = false;
if (certPolicies != null) {
// Must contain at least one Policy
for (PolicyInformation policy : certPolicies.getPolicyInformation()) {
// Add the data based on the OIDs
switch (policy.getPolicyIdentifier().toString()) {
case POLICY_QUALIFIER_VERIFIED_TPM_RESIDENCY:
verifiedTPMResidency = true;
break;
case POLICY_QUALIFIER_VERIFIED_TPM_FIXED:
verifiedTPMFixed = true;
break;
case POLICY_QUALIFIER_VERIFIED_TPM_RESTRICTED:
verifiedTPMRestricted = true;
break;
default:
break;
}
}
}
// Add to map
policyQualifiers.put("verifiedTPMResidency", Boolean.valueOf(verifiedTPMResidency));
policyQualifiers.put("verifiedTPMFixed", Boolean.valueOf(verifiedTPMFixed));
policyQualifiers.put("verifiedTPMRestricted", Boolean.valueOf(verifiedTPMRestricted));
return policyQualifiers;
}
/**
* Parses fields related to IDevID certificates.
* @throws IOException if a problem is encountered during parsing
*/
private void parseIDevIDCertificate() throws IOException {
this.subjectAltName =
getX509Certificate().getExtensionValue(SUBJECT_ALTERNATIVE_NAME_EXTENSION);
if (this.subjectAltName != null) {
ASN1InputStream input;
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(subjectAltName);
input = new ASN1InputStream(byteArrayInputStream);
ASN1OctetString obj = (ASN1OctetString) input.readObject();
boolean tcgOid = false;
// Parse the otherName structure. According to the specification "TPM 2.0 Keys for Device Identity and
// Attestation", otherName can contain up to two structures: HardwareModuleName and PermanentIdentifier.
// Currently, this parser only supports HardwareModuleName (if present).
if (obj != null) {
// Parse Hardware Module Name structure, comprised of a hwType and hwSerialNum, and associated OID
// See also RFC 4108
ASN1Sequence seq1 = ASN1Sequence.getInstance(obj.getOctets());
// Iterate over GeneralNames sequence until HardwareModuleName is found
Iterator<ASN1Encodable> seqIterator = seq1.iterator();
ASN1TaggedObject tagObj1;
ASN1Encodable encodable;
while (seqIterator.hasNext()) {
encodable = seqIterator.next();
if (encodable != null) {
tagObj1 = ASN1TaggedObject.getInstance(encodable.toASN1Primitive());
if (tagObj1 != null && tagObj1.hasContextTag(0)) { // Corresponds to otherName
seq1 = ASN1Sequence.getInstance(tagObj1, false);
ASN1ObjectIdentifier obj1 = ASN1ObjectIdentifier.getInstance(seq1.getObjectAt(0));
tagObj1 = ASN1TaggedObject.getInstance(seq1.getObjectAt(1));
if (obj1.toString().equals(HARDWARE_MODULE_NAME_OID)) {
// HardwareModuleName sequence
seq1 = ASN1Sequence.getInstance(tagObj1, false);
seq1 = ASN1Sequence.getInstance(seq1.getObjectAt(0));
obj1 = ASN1ObjectIdentifier.getInstance(seq1.getObjectAt(0));
ASN1OctetString obj2;
try {
obj2 = ASN1OctetString.getInstance(seq1.getObjectAt(1));
} catch (IllegalArgumentException e) {
// Some certs have been found to contain tagged objects for hwSerialNum.
// Handle this as a special case.
log.warn("Could not parse octet string for hwSerialNum. Attempting to parse tag.");
try {
tagObj1 = ASN1TaggedObject.getInstance(seq1.getObjectAt(1));
obj2 = ASN1OctetString.getInstance(tagObj1, false);
}
catch (Exception i) { // Invalid object found
log.warn("Invalid object found for hwSerialNum.");
break;
}
}
// If an OID corresponding to TPM 2.0 for hwType is supported, according to the
// specification "TPM 2.0 Keys for Device Identity and Attestation", the contents of
// the hwSerialNum field will be parsed accordingly.
hwType = obj1.toString();
if (hasTCGOIDs()) {
tcgOid = true;
}
// Convert octet string to byte array
hwSerialNum = obj2.getOctets();
}
}
}
}
}
// Check for certificate policy qualifiers, which should be present for IDevIDs if in compliance with the
// TCG specification.
// For interoperability reasons, this will only log a warning if a TCG OID is specified above.
byte[] policyBytes = getX509Certificate().getExtensionValue(Extension.certificatePolicies.getId());
Map<String, Boolean> policyQualifiers = null;
if (policyBytes != null) {
policyQualifiers = this.getTPMPolicyQualifiers(policyBytes);
}
if (tcgOid) {
boolean failCondition;
if (policyQualifiers != null) {
StringBuilder qualifierSB = new StringBuilder();
policyQualifiers.forEach((key, value) -> {
if (value) {
if (qualifierSB.length() > 0) {
qualifierSB.append(" ");
}
qualifierSB.append(key);
}
});
tpmPolicies = qualifierSB.toString();
failCondition = !(policyQualifiers.get("verifiedTPMResidency") &&
(policyQualifiers.get("verifiedTPMFixed") ||
policyQualifiers.get("verifiedTPMRestricted")));
} else {
failCondition = true;
}
if (failCondition) {
log.warn("TPM policy qualifiers not found, or do not meet logical criteria. Certificate may not " +
"be in compliance with TCG specification.");
}
}
// Log a warning if notAfter field has an expiry date that is not indefinite
if (!this.getEndValidity().toInstant().equals(Instant.ofEpochSecond(UNDEFINED_EXPIRY_DATE))) {
log.warn("IDevID does not contain an indefinite expiry date. This may indicate an invalid " +
"certificate.");
}
input.close();
}
}
/**
* Function to check whether a given IDevID certificate has TCG OIDs, in order to check compliance with various
* fields.
*
* @return a boolean value
*/
public boolean hasTCGOIDs() {
if (this.getHwType() != null) {
return this.getHwType().equals(HWTYPE_TCG_TPM2_OID);
}
else {
return false;
}
}
@Override
@SuppressWarnings("checkstyle:avoidinlineconditionals")
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
if (!super.equals(o)) {
return false;
}
IDevIDCertificate that = (IDevIDCertificate) o;
if (!Objects.equals(getTpmPolicies(), that.getTpmPolicies())) {
return false;
}
if (!Objects.equals(getHwType(), that.getHwType())) {
return false;
}
return Arrays.equals(getHwSerialNum(), that.getHwSerialNum());
}
@Override
@SuppressWarnings({"checkstyle:magicnumber", "checkstyle:avoidinlineconditionals"})
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (getTpmPolicies() != null ? getTpmPolicies().hashCode() : 0);
result = 31 * result + (getHwType() != null ? getHwType().hashCode() : 0);
result = 31 * result + (getHwSerialNum() != null ? Arrays.hashCode(getHwSerialNum()) : 0);
return result;
}
}

View File

@ -27,6 +27,11 @@ public enum Page {
*/
PLATFORM_CREDENTIALS("Platform Certificates", "ic_important_devices",
null, "certificate-request/"),
/**
* Page to display and manage IDevID certificates.
*/
IDEVID_CERTIFICATES("IDevID Certificates", "ic_important_devices",
null, "certificate-request/"),
/**
* Page to display issued certificates.
*/

View File

@ -3,6 +3,7 @@ package hirs.attestationca.portal.page.controllers;
import hirs.attestationca.persist.entity.manager.CACredentialRepository;
import hirs.attestationca.persist.entity.manager.CertificateRepository;
import hirs.attestationca.persist.entity.manager.ComponentResultRepository;
import hirs.attestationca.persist.entity.manager.IDevIDCertificateRepository;
import hirs.attestationca.portal.page.Page;
import hirs.attestationca.portal.page.PageController;
import hirs.attestationca.portal.page.PageMessages;
@ -34,21 +35,25 @@ public class CertificateDetailsPageController extends PageController<Certificate
private final CertificateRepository certificateRepository;
private final CACredentialRepository caCredentialRepository;
private final ComponentResultRepository componentResultRepository;
private final IDevIDCertificateRepository iDevIDCertificateRepository;
/**
* Constructor providing the Page's display and routing specification.
* @param certificateRepository the certificate repository
* @param componentResultRepository the component result repository
* @param caCredentialRepository the ca credential manager
* @param iDevIDCertificateRepository the idevid certificate repository
*/
@Autowired
public CertificateDetailsPageController(final CertificateRepository certificateRepository,
final ComponentResultRepository componentResultRepository,
final CACredentialRepository caCredentialRepository) {
final CACredentialRepository caCredentialRepository,
final IDevIDCertificateRepository iDevIDCertificateRepository) {
super(Page.CERTIFICATE_DETAILS);
this.certificateRepository = certificateRepository;
this.componentResultRepository = componentResultRepository;
this.caCredentialRepository = caCredentialRepository;
this.iDevIDCertificateRepository = iDevIDCertificateRepository;
}
/**
@ -100,6 +105,10 @@ public class CertificateDetailsPageController extends PageController<Certificate
data.putAll(CertificateStringMapBuilder.getIssuedInformation(uuid,
certificateRepository, caCredentialRepository));
break;
case "idevid":
data.putAll(CertificateStringMapBuilder.getIdevidInformation(uuid,
certificateRepository, caCredentialRepository));
break;
default:
String typeError = "Invalid certificate type: " + params.getType();
messages.addError(typeError);

View File

@ -8,12 +8,14 @@ import hirs.attestationca.persist.entity.manager.CACredentialRepository;
import hirs.attestationca.persist.entity.manager.CertificateRepository;
import hirs.attestationca.persist.entity.manager.ComponentResultRepository;
import hirs.attestationca.persist.entity.manager.EndorsementCredentialRepository;
import hirs.attestationca.persist.entity.manager.IDevIDCertificateRepository;
import hirs.attestationca.persist.entity.manager.IssuedCertificateRepository;
import hirs.attestationca.persist.entity.manager.PlatformCertificateRepository;
import hirs.attestationca.persist.entity.userdefined.Certificate;
import hirs.attestationca.persist.entity.userdefined.certificate.CertificateAuthorityCredential;
import hirs.attestationca.persist.entity.userdefined.certificate.ComponentResult;
import hirs.attestationca.persist.entity.userdefined.certificate.EndorsementCredential;
import hirs.attestationca.persist.entity.userdefined.certificate.IDevIDCertificate;
import hirs.attestationca.persist.entity.userdefined.certificate.IssuedAttestationCertificate;
import hirs.attestationca.persist.entity.userdefined.certificate.PlatformCredential;
import hirs.attestationca.persist.entity.userdefined.certificate.attributes.ComponentIdentifier;
@ -89,9 +91,11 @@ public class CertificatePageController extends PageController<NoPageParams> {
private final EndorsementCredentialRepository endorsementCredentialRepository;
private final IssuedCertificateRepository issuedCertificateRepository;
private final CACredentialRepository caCredentialRepository;
private final IDevIDCertificateRepository iDevIDCertificateRepository;
private static final String TRUSTCHAIN = "trust-chain";
private static final String PLATFORMCREDENTIAL = "platform-credentials";
private static final String IDEVIDCERTIFICATE = "idevid-certificates";
private static final String ENDORSEMENTCREDENTIAL = "endorsement-key-credentials";
private static final String ISSUEDCERTIFICATES = "issued-certificates";
@ -109,8 +113,7 @@ public class CertificatePageController extends PageController<NoPageParams> {
* @param endorsementCredentialRepository the endorsement credential manager
* @param issuedCertificateRepository the issued certificate manager
* @param caCredentialRepository the ca credential manager
* @param acaCertificate the ACA's X509 certificate
*/
* @param acaCertificate the ACA's X509 certificate */
@Autowired
public CertificatePageController(final CertificateRepository certificateRepository,
final PlatformCertificateRepository platformCertificateRepository,
@ -118,6 +121,7 @@ public class CertificatePageController extends PageController<NoPageParams> {
final EndorsementCredentialRepository endorsementCredentialRepository,
final IssuedCertificateRepository issuedCertificateRepository,
final CACredentialRepository caCredentialRepository,
final IDevIDCertificateRepository iDevIDCertificateRepository,
final X509Certificate acaCertificate) {
super(Page.TRUST_CHAIN);
this.certificateRepository = certificateRepository;
@ -126,6 +130,7 @@ public class CertificatePageController extends PageController<NoPageParams> {
this.endorsementCredentialRepository = endorsementCredentialRepository;
this.issuedCertificateRepository = issuedCertificateRepository;
this.caCredentialRepository = caCredentialRepository;
this.iDevIDCertificateRepository = iDevIDCertificateRepository;
try {
certificateAuthorityCredential
@ -171,6 +176,9 @@ public class CertificatePageController extends PageController<NoPageParams> {
case PLATFORMCREDENTIAL:
mav = getBaseModelAndView(Page.PLATFORM_CREDENTIALS);
break;
case IDEVIDCERTIFICATE:
mav = getBaseModelAndView(Page.IDEVID_CERTIFICATES);
break;
case ENDORSEMENTCREDENTIAL:
mav = getBaseModelAndView(Page.ENDORSEMENT_KEY_CREDENTIALS);
break;
@ -318,6 +326,23 @@ public class CertificatePageController extends PageController<NoPageParams> {
log.debug("Returning list of size: " + records.size());
return new DataTableResponse<>(records, input);
}
else if (certificateType.equals(IDEVIDCERTIFICATE)) {
FilteredRecordsList<IDevIDCertificate> records = new FilteredRecordsList<IDevIDCertificate>();
org.springframework.data.domain.Page<IDevIDCertificate> pagedResult =
this.iDevIDCertificateRepository.findByArchiveFlag(false, paging);
if (pagedResult.hasContent()) {
records.addAll(pagedResult.getContent());
records.setRecordsTotal(pagedResult.getContent().size());
} else {
records.setRecordsTotal(input.getLength());
}
records.setRecordsFiltered(iDevIDCertificateRepository.findByArchiveFlag(false).size());
log.debug("Returning list of size: " + records.size());
return new DataTableResponse<>(records, input);
}
return new DataTableResponse<>(new FilteredRecordsList<>(), input);
}
@ -627,8 +652,8 @@ public class CertificatePageController extends PageController<NoPageParams> {
}
private ZipOutputStream bulkDownload(final ZipOutputStream zipOut,
final List<Certificate> certificates,
final String singleFileName) throws IOException {
final List<Certificate> certificates,
final String singleFileName) throws IOException {
String zipFileName;
// get all files
for (Certificate certificate : certificates) {
@ -677,6 +702,7 @@ public class CertificatePageController extends PageController<NoPageParams> {
case PLATFORMCREDENTIAL -> Page.PLATFORM_CREDENTIALS;
case ENDORSEMENTCREDENTIAL -> Page.ENDORSEMENT_KEY_CREDENTIALS;
case ISSUEDCERTIFICATES -> Page.ISSUED_CERTIFICATES;
case IDEVIDCERTIFICATE -> Page.IDEVID_CERTIFICATES;
default -> Page.TRUST_CHAIN;
};
}
@ -695,6 +721,8 @@ public class CertificatePageController extends PageController<NoPageParams> {
return EndorsementCredential.class;
case ISSUEDCERTIFICATES:
return IssuedAttestationCertificate.class;
case IDEVIDCERTIFICATE:
return IDevIDCertificate.class;
case TRUSTCHAIN:
return CertificateAuthorityCredential.class;
default:
@ -728,6 +756,10 @@ public class CertificatePageController extends PageController<NoPageParams> {
return this.certificateRepository
.findByCertificateHash(certificateHash,
"CertificateAuthorityCredential");
case IDEVIDCERTIFICATE:
return this.certificateRepository
.findByCertificateHash(certificateHash,
"IDevIDCertificate");
default:
return null;
}
@ -791,6 +823,8 @@ public class CertificatePageController extends PageController<NoPageParams> {
return new PlatformCredential(fileBytes);
case ENDORSEMENTCREDENTIAL:
return new EndorsementCredential(fileBytes);
case IDEVIDCERTIFICATE:
return new IDevIDCertificate(fileBytes);
case TRUSTCHAIN:
if (CredentialHelper.isMultiPEM(new String(fileBytes, StandardCharsets.UTF_8))) {
try (ByteArrayInputStream certInputStream = new ByteArrayInputStream(fileBytes)) {

View File

@ -7,6 +7,7 @@ import hirs.attestationca.persist.entity.userdefined.Certificate;
import hirs.attestationca.persist.entity.userdefined.certificate.CertificateAuthorityCredential;
import hirs.attestationca.persist.entity.userdefined.certificate.ComponentResult;
import hirs.attestationca.persist.entity.userdefined.certificate.EndorsementCredential;
import hirs.attestationca.persist.entity.userdefined.certificate.IDevIDCertificate;
import hirs.attestationca.persist.entity.userdefined.certificate.IssuedAttestationCertificate;
import hirs.attestationca.persist.entity.userdefined.certificate.PlatformCredential;
import hirs.attestationca.persist.entity.userdefined.certificate.attributes.ComponentIdentifier;
@ -20,6 +21,7 @@ import org.bouncycastle.util.encoders.Hex;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -60,8 +62,8 @@ public final class CertificateStringMapBuilder {
data.put("certificateId", certificate.getId().toString());
}
data.put("authInfoAccess", certificate.getAuthorityInfoAccess());
data.put("beginValidity", certificate.getBeginValidity().toString());
data.put("endValidity", certificate.getEndValidity().toString());
data.put("beginValidity", Long.toString(certificate.getBeginValidity().getTime()));
data.put("endValidity", Long.toString(certificate.getEndValidity().getTime()));
data.put("signature", Arrays.toString(certificate.getSignature()));
data.put("signatureSize", Integer.toString(certificate.getSignature().length
* Certificate.MIN_ATTR_CERT_LENGTH));
@ -525,4 +527,90 @@ public final class CertificateStringMapBuilder {
}
return data;
}
/**
* Returns the IDevID Certificate information.
*
* @param uuid ID for the certificate.
* @param certificateRepository the certificate manager for retrieving certs.
* @return a hash map with the endorsement certificate information.
*/
public static HashMap<String, String> getIdevidInformation(final UUID uuid,
final CertificateRepository certificateRepository,
final CACredentialRepository caCredentialRepository) {
HashMap<String, String> data = new HashMap<>();
IDevIDCertificate certificate = (IDevIDCertificate) certificateRepository.getCertificate(uuid);
if (certificate != null) {
data.putAll(getGeneralCertificateInfo(certificate, certificateRepository, caCredentialRepository));
if (certificate.getHwType() != null) {
data.put("hwType", certificate.getHwType());
String hwTypeReadable;
if (certificate.hasTCGOIDs()) {
hwTypeReadable = "TPM-Bound IDevID";
}
else {
hwTypeReadable = "Manufacturer Specific";
}
data.put("hwTypeReadable", hwTypeReadable);
}
if (certificate.getHwSerialNum() != null) {
String hwSerialStr = new String(certificate.getHwSerialNum(), StandardCharsets.US_ASCII);
// Obtain colon-delimited fields from hwSerialNum field, if present
if (certificate.hasTCGOIDs()) {
if (hwSerialStr.contains(":")) {
String[] hwSerialArray = hwSerialStr.split(":");
if (hwSerialArray.length >= 3) {
data.put("tcgTpmManufacturer", hwSerialArray[0]);
data.put("ekAuthorityKeyIdentifier", hwSerialArray[1]);
data.put("ekCertificateSerialNumber", hwSerialArray[2]);
}
}
else {
// Corresponds to digest of EK certificate
data.put("ekCertificateDigest", Boolean.valueOf(true).toString());
String hwSerialToAdd = Hex.toHexString(certificate.getHwSerialNum());
data.put("hwSerialNumHex", Boolean.valueOf(true).toString());
data.put("hwSerialNum", hwSerialToAdd);
}
}
else {
String hwSerialToAdd = hwSerialStr;
// Check if hwSerialNum is a printable ASCII string; default to hex otherwise
if (hwSerialStr.chars().allMatch(c -> c > 0x20 && c <= 0x7F)) {
data.put("hwSerialNum", hwSerialStr);
} else {
hwSerialToAdd = Hex.toHexString(certificate.getHwSerialNum());
data.put("hwSerialNumHex", Boolean.valueOf(true).toString());
}
data.put("hwSerialNum", hwSerialToAdd);
}
}
if (certificate.getKeyUsage() != null) {
data.put("keyUsage", certificate.getKeyUsage());
}
if (certificate.getExtendedKeyUsage() != null
&& !certificate.getExtendedKeyUsage().isEmpty()) {
data.put("extendedKeyUsage", certificate.getExtendedKeyUsage());
}
if (certificate.getTpmPolicies() != null) {
data.put("tpmPolicies", certificate.getTpmPolicies());
}
data.put("x509Version", Integer.toString(certificate
.getX509CredentialVersion()));
} else {
String notFoundMessage = "Unable to find IDevIDCertificate with ID: " + uuid;
log.error(notFoundMessage);
}
return data;
}
}

View File

@ -37,6 +37,12 @@
<img src="${icons}/ic_file_download_black_24dp.png" title="Download Certificate">
</a>
</c:when>
<c:when test="${param.type=='idevid'}">
IDevID Certificate
<a href="${portal}/certificate-request/idevid-certificates/download?id=${param.id}">
<img src="${icons}/ic_file_download_black_24dp.png" title="Download Certificate">
</a>
</c:when>
<c:otherwise>
Unknown Certificate
</c:otherwise>
@ -105,10 +111,48 @@
</c:if>
<c:if test="${not empty initialData.serialNumber}">
<div class="row">
<div class="col-md-1 col-md-offset-1"><span class="colHeader">Serial Number</span></div>
<div class="col-md-1 col-md-offset-1"><span class="colHeader">Certificate Serial Number</span></div>
<div id="serialNumber" class="col col-md-8 vertical"></div>
</div>
</c:if>
<c:if test="${not empty initialData.hwSerialNum}">
<div class="row">
<c:choose>
<c:when test="${not empty initialData.ekCertificateDigest}">
<div class="col-md-1 col-md-offset-1"><span class="colHeader help-text" title="Defined in RFC 4108, and contained in the Subject Alternate Name. This certificate is also TCG-compliant for this field, indicated by the use of a TCG OID for Hardware Type.">Hardware Module Name</span></div>
<div class="col col-md-8">
<div><span class="help-text" title="Contains an OID that, in conjunction with the Hardware Serial Number, identifies the device's hardware module. This certificate is using a TCG OID, indicating the use of a Trusted Platform Module. OID value: ${initialData.hwType}">Hardware Type</span>:&nbsp;<span id="hwTypeReadable">${initialData.hwTypeReadable}</span></div>
<div><span class="help-text" title="TCG-compliant: the device's TPM does not contain an EK certificate, and the Hardware Serial Number represents a digest of the EK Certificate public key. See TCG specification titled &quot;TPM 2.0 Keys for Device Identity and Attestation&quot;.">Hardware Serial Number</span>:</div>
<ul>
<li>EK Certificate Public Key:&nbsp;<span id="hwSerialNum">${initialData.hwSerialNum}</span></li>
</ul>
</div>
</c:when>
<c:otherwise>
<div class="col-md-1 col-md-offset-1"><span class="colHeader help-text" title="Defined in RFC 4108, and contained in the Subject Alternate Name. This certificate is not using a TCG OID for Hardware Type, so these fields may have manufacturer-specific context.">Hardware Module Name</span></div>
<div class="col col-md-8">
<div><span class="help-text" title="Contains an OID that, in conjunction with the Hardware Serial Number, identifies the device's hardware module. This certificate is using a non-TCG OID, possibly indicating manufacturer-specific context. OID value: ${initialData.hwType}">Hardware Type</span>:&nbsp;<span id="hwTypeReadable">${initialData.hwTypeReadable}</span></div>
<div><span class="help-text" title="Used for identifying the device's hardware module. This field may have manufacturer-specific context.">Hardware Serial Number</span>:&nbsp;<span id="hwSerialNum">${initialData.hwSerialNum}</span></div>
</div>
</c:otherwise>
</c:choose>
</div>
</c:if>
<c:if test="${not empty initialData.tcgTpmManufacturer}">
<div class="row">
<div class="col-md-1 col-md-offset-1"><span class="colHeader help-text" title="Defined in RFC 4108, and contained in the Subject Alternate Name. This certificate is also TCG-compliant for this field, indicated by the use of a TCG OID for Hardware Type.">Hardware Module Name</span></div>
<div class="col col-md-8">
<div><span class="help-text" title="Contains an OID that, in conjunction with the Hardware Serial Number, identifies the device's hardware module. This certificate is using a TCG OID, indicating the use of a Trusted Platform Module. OID value: ${initialData.hwType}">Hardware Type</span>:&nbsp;<span id="hwTypeReadable">${initialData.hwTypeReadable}</span></div>
<div><span class="help-text" title="TCG-compliant: the device's TPM contains an EK certificate, and the below fields are parsed accordingly from the Hardware Serial Number. See TCG specification titled &quot;TPM 2.0 Keys for Device Identity and Attestation&quot;.">Hardware Serial Number</span>:
<ul>
<li>TCG TPM Manufacturer Code:&nbsp;<span id="tcgTpmManufacturer">${initialData.tcgTpmManufacturer}</span></li>
<li>EK Authority Key Identifier:&nbsp;<span id="ekAuthorityKeyIdentifier">${initialData.ekAuthorityKeyIdentifier}</span></li>
<li>EK CertificateSerialNumber:&nbsp;<span id="ekCertificateSerialNumber">${initialData.ekCertificateSerialNumber}</span></li>
</ul>
</div>
</div>
</div>
</c:if>
<c:if test="${not empty initialData.beginValidity}">
<div class="row">
<div class="col-md-1 col-md-offset-1"><span class="colHeader">Validity</span></div>
@ -196,10 +240,14 @@
<div class="col-md-1 col-md-offset-1"><span class="colHeader">X509 Credential Version</span></div>
<div id="credentialVersion" class="col col-md-8 vertical">${initialData.x509Version} (v${initialData.x509Version + 1})</div>
</div>
<div class="row">
<div class="col-md-1 col-md-offset-1"><span class="colHeader">Credential Type</span></div>
<div id="credentialType" class="col col-md-8 vertical">${initialData.credentialType}</div>
</div>
<c:choose>
<c:when test="${not empty initialData.credentialType}">
<div class="row">
<div class="col-md-1 col-md-offset-1"><span class="colHeader">Credential Type</span></div>
<div id="credentialType" class="col col-md-8 vertical">${initialData.credentialType}</div>
</div>
</c:when>
</c:choose>
<!-- Add the different fields based on the certificate type -->
<c:choose>
<c:when test="${param.type=='certificateauthority'}">
@ -878,6 +926,35 @@
</div>
</div>
</c:when>
<c:when test="${param.type=='idevid'}">
<c:choose>
<c:when test="${not empty initialData.tpmPolicies}">
<div class="row">
<div class="col-md-1 col-md-offset-1"><span class="colHeader help-text" title="TPM verification policies, as defined in the TCG specification &quot;TPM 2.0 Keys for Device Identity and Attestation&quot;.">TPM Policies</span></div>
<div id="tpmPolicies" class="col col-md-8 vertical">${initialData.tpmPolicies}</div>
</div>
</c:when>
</c:choose>
<div class="row">
<div class="col-md-1 col-md-offset-1"><span class="colHeader">Key Usage</span></div>
<c:choose>
<c:when test="${not empty initialData.keyUsage}">
<div id="keyUsage" class="col col-md-8 vertical">${initialData.keyUsage}</div>
</c:when>
<c:otherwise>
<div id="keyUsage" class="col col-md-8 vertical">Not Specified</div>
</c:otherwise>
</c:choose>
</div>
<c:choose>
<c:when test="${not empty initialData.extendedKeyUsage}">
<div class="row">
<div class="col-md-1 col-md-offset-1"><span class="colHeader">Extended Key Usage</span></div>
<div id="extendedKeyUsage" class="col col-md-8 vertical">${initialData.extendedKeyUsage}</div>
</div>
</c:when>
</c:choose>
</c:when>
</c:choose>
</div>
<script>
@ -890,7 +967,8 @@
//Format validity time
$("#validity span").each(function () {
$(this).text(formatDateTime($(this).text()));
var dateText = $(this).text();
return $(this).text(formatCertificateDate(dateText));
});
//Convert byte array to string
@ -935,6 +1013,19 @@
}
</c:if>
<c:if test="${not empty initialData.hwSerialNumHex}">
var hwSerialNum = '${initialData.hwSerialNum}';
$("#hwSerialNum").html(parseHexString(hwSerialNum));
</c:if>
<c:if test="${not empty initialData.tcgTpmManufacturer}">
var ekAKI = '${initialData.ekAuthorityKeyIdentifier};'
var ekCSN = '${initialData.ekCertificateSerialNumber};'
$("#ekAuthorityKeyIdentifier").html(parseHexString(ekAKI));
$("#ekCertificateSerialNumber").html(parseHexString(ekCSN));
</c:if>
//Initilize tooltips
$('[data-toggle="tooltip"]').tooltip();

View File

@ -0,0 +1,85 @@
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%-- JSP TAGS --%>
<%@taglib prefix="c" uri="jakarta.tags.core" %>
<%@taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%@taglib prefix="my" tagdir="/WEB-INF/tags"%>
<%-- CONTENT --%>
<my:page>
<jsp:attribute name="script">
<script type="text/javascript" src="${lib}/jquery.spring-friendly/jquery.spring-friendly.js"></script>
</jsp:attribute>
<jsp:attribute name="pageHeaderTitle">IDevID Certificates</jsp:attribute>
<jsp:body>
<div class="aca-input-box-header">
<form:form method="POST" action="${portal}/certificate-request/idevid-certificates/upload" enctype="multipart/form-data">
Import IDevID Certificates
<my:file-chooser id="idevid-editor" label="Import IDevID Certificates">
<input id="importFile" type="file" name="file" multiple="multiple" />
</my:file-chooser>
<a href="${portal}/certificate-request/idevid-certificates/bulk">
<img src="${icons}/ic_file_download_black_24dp.png" title="Download All IDevID Certificates">
</a>
</form:form>
</div>
<br/>
<div class="aca-data-table">
<table id="idevidCertificateTable" class="display" width="100%">
<thead>
<tr>
<th>Issuer</th>
<th>Subject</th>
<th>Valid (begin)</th>
<th>Valid (end)</th>
<th>Options</th>
</tr>
</thead>
</table>
</div>
<script>
$(document).ready(function() {
var url = pagePath +'/list';
var columns = [
{data: 'issuer'},
{data: 'subject'},
{
data: 'beginValidity',
searchable:false,
render: function (data, type, full, meta) {
return formatCertificateDate(full.beginValidity);
}
},
{
data: 'endValidity',
searchable:false,
render: function (data, type, full, meta) {
return formatCertificateDate(full.endValidity);
}
},
{
data: 'id',
orderable: false,
searchable:false,
render: function(data, type, full, meta) {
// Set up a delete icon with link to handleDeleteRequest().
// sets up a hidden input field containing the ID which is
// used as a parameter to the REST POST call to delete
var html = '';
html += certificateDetailsLink('idevid', full.id, true);
html += certificateDownloadLink(full.id, pagePath);
html += certificateDeleteLink(full.id, pagePath);
return html;
}
}
];
//Set data tables
setDataTables("#idevidCertificateTable", url, columns);
});
</script>
</jsp:body>
</my:page>

View File

@ -41,6 +41,12 @@
</a>
</h3>
<h4>Upload, view and manage endorsement credentials.</h4>
<h3>
<a href="${certificateRequest}/idevid-certificates">
<img src="${icons}/ic_important_devices_black_24dp.png" /> IDevID Certificates
</a>
</h3>
<h4>Upload, view and manage IDevID certificates.</h4>
<h3>
<a href="${portal}/reference-manifests">
<img src="${icons}/ic_important_devices_black_24dp.png" /> Reference Integrity Manifests

View File

@ -64,3 +64,10 @@
cursor: pointer;
border-radius: 2px;
}
.help-text {
text-decoration: underline dashed;
text-underline-position:under;
text-decoration-thickness: 1px;
cursor: help;
}

View File

@ -2,7 +2,7 @@
function byteToHexString(arr){
var str = "";
$.each(arr, function(index, value){
str += ('0' + (value & 0xFF).toString(16)).slice(-2) + ": ";
str += ('0' + (value & 0xFF).toString(16)).slice(-2) + ":";
});
return (str.substring(0, str.length - 2)).toUpperCase();
}
@ -14,7 +14,7 @@ function parseHexString(hexString) {
if(str.length === 2) {
return str;
}
return str.match(/.{2}/g).join(': ');
return str.match(/.{2}/g).join(':');
}
//Parse the HEX string value to display as byte hex string
@ -28,7 +28,7 @@ function parseSerialNumber(hexString){
return str;
}
//Parse and return
return newString = hexString.match(/.{2}/g).join(':');
return newString = str.match(/.{2}/g).join(':');
}
@ -111,6 +111,10 @@ function certificateDetailsLink(type, id, sameType){
icon += "/ic_vpn_key_black_24dp.png";
title = "View Endorsement Certificate Details";
break;
case "idevid":
icon += "/ic_vpn_key_black_24dp.png";
title = "View IDevID Certificate Details";
break;
}
}
var html = '<a href=' + href + '>'
@ -197,3 +201,18 @@ function rimDownloadLink(id, pagePath){
return html;
}
/**
* Formats a given date to a UTC string, or returns an indefinite icon
* @param date to format
*/
function formatCertificateDate(dateText) {
var date = +dateText; // Convert to numeric
if (date == 253402300799000)
{
return 'Indefinite';
}
return new Date(date).toUTCString();
}