diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/manager/IDevIDCertificateRepository.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/manager/IDevIDCertificateRepository.java new file mode 100644 index 00000000..5fd72a43 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/manager/IDevIDCertificateRepository.java @@ -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 { + + List findByArchiveFlag(boolean archiveFlag); + + Page findByArchiveFlag(boolean archiveFlag, Pageable pageable); + /*List findBySubject(String subject); + List findBySubjectSorted(String subject); + List findBySubjectAndArchiveFlag(String subject, boolean archiveFlag); + List findBySubjectSortedAndArchiveFlag(String subject, boolean archiveFlag); + IDevIDCertificate findBySubjectKeyIdentifier(byte[] subjectKeyIdentifier); + IDevIDCertificate findBySubjectKeyIdStringAndArchiveFlag(String subjectKeyIdString, boolean archiveFlag); + */ +} \ No newline at end of file diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/Certificate.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/Certificate.java index c6dc458f..257c834e 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/Certificate.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/Certificate.java @@ -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. * diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/IDevIDCertificate.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/IDevIDCertificate.java new file mode 100644 index 00000000..a282c6b8 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/IDevIDCertificate.java @@ -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 getTPMPolicyQualifiers(byte[] policyBytes) throws IOException { + CertificatePolicies certPolicies = + CertificatePolicies.getInstance(JcaX509ExtensionUtils.parseExtensionValue(policyBytes)); + Map 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 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 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; + } +} \ No newline at end of file diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/Page.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/Page.java index 0c0a6a39..eb39cf5e 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/Page.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/Page.java @@ -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. */ diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificateDetailsPageController.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificateDetailsPageController.java index cde878d4..eef67603 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificateDetailsPageController.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificateDetailsPageController.java @@ -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 { 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 { * @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 { 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 { this.endorsementCredentialRepository = endorsementCredentialRepository; this.issuedCertificateRepository = issuedCertificateRepository; this.caCredentialRepository = caCredentialRepository; + this.iDevIDCertificateRepository = iDevIDCertificateRepository; try { certificateAuthorityCredential @@ -171,6 +176,9 @@ public class CertificatePageController extends PageController { 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 { log.debug("Returning list of size: " + records.size()); return new DataTableResponse<>(records, input); } + else if (certificateType.equals(IDEVIDCERTIFICATE)) { + FilteredRecordsList records = new FilteredRecordsList(); + org.springframework.data.domain.Page 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 { } private ZipOutputStream bulkDownload(final ZipOutputStream zipOut, - final List certificates, - final String singleFileName) throws IOException { + final List certificates, + final String singleFileName) throws IOException { String zipFileName; // get all files for (Certificate certificate : certificates) { @@ -677,6 +702,7 @@ public class CertificatePageController extends PageController { 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 { 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 { 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 { 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)) { diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/utils/CertificateStringMapBuilder.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/utils/CertificateStringMapBuilder.java index 92dbe917..e06216f6 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/utils/CertificateStringMapBuilder.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/utils/CertificateStringMapBuilder.java @@ -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 getIdevidInformation(final UUID uuid, + final CertificateRepository certificateRepository, + final CACredentialRepository caCredentialRepository) { + + HashMap 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; + } } diff --git a/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/certificate-details.jsp b/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/certificate-details.jsp index e77158fe..6550ebab 100644 --- a/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/certificate-details.jsp +++ b/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/certificate-details.jsp @@ -37,6 +37,12 @@ + + IDevID Certificate + + + + Unknown Certificate @@ -105,10 +111,48 @@
-
Serial Number
+
Certificate Serial Number
+ +
+ + +
Hardware Module Name
+
+
Hardware Type${initialData.hwTypeReadable}
+
Hardware Serial Number:
+
    +
  • EK Certificate Public Key: ${initialData.hwSerialNum}
  • +
+
+
+ +
Hardware Module Name
+
+
Hardware Type${initialData.hwTypeReadable}
+
Hardware Serial Number${initialData.hwSerialNum}
+
+
+
+
+
+ +
+
Hardware Module Name
+
+
Hardware Type${initialData.hwTypeReadable}
+
Hardware Serial Number: +
    +
  • TCG TPM Manufacturer Code: ${initialData.tcgTpmManufacturer}
  • +
  • EK Authority Key Identifier: ${initialData.ekAuthorityKeyIdentifier}
  • +
  • EK CertificateSerialNumber: ${initialData.ekCertificateSerialNumber}
  • +
+
+
+
+
Validity
@@ -196,10 +240,14 @@
X509 Credential Version
${initialData.x509Version} (v${initialData.x509Version + 1})
-
-
Credential Type
-
${initialData.credentialType}
-
+ + +
+
Credential Type
+
${initialData.credentialType}
+
+
+
@@ -878,6 +926,35 @@ + + + +
+
TPM Policies
+
${initialData.tpmPolicies}
+
+
+
+
+
Key Usage
+ + +
${initialData.keyUsage}
+
+ +
Not Specified
+
+
+
+ + +
+
Extended Key Usage
+
${initialData.extendedKeyUsage}
+
+
+
+
+ + IDevID Certificates + + +
+ + Import IDevID Certificates + + + + + + + +
+
+
+ + + + + + + + + + +
IssuerSubjectValid (begin)Valid (end)Options
+
+ +
+ \ No newline at end of file diff --git a/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/index.jsp b/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/index.jsp index 4a00323d..eb953bb0 100644 --- a/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/index.jsp +++ b/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/index.jsp @@ -41,6 +41,12 @@

Upload, view and manage endorsement credentials.

+

+ + IDevID Certificates + +

+

Upload, view and manage IDevID certificates.

Reference Integrity Manifests diff --git a/HIRS_AttestationCAPortal/src/main/webapp/common/certificate_details.css b/HIRS_AttestationCAPortal/src/main/webapp/common/certificate_details.css index 5cafa466..af42d7aa 100644 --- a/HIRS_AttestationCAPortal/src/main/webapp/common/certificate_details.css +++ b/HIRS_AttestationCAPortal/src/main/webapp/common/certificate_details.css @@ -63,4 +63,11 @@ margin: 4px 2px; cursor: pointer; border-radius: 2px; +} + +.help-text { + text-decoration: underline dashed; + text-underline-position:under; + text-decoration-thickness: 1px; + cursor: help; } \ No newline at end of file diff --git a/HIRS_AttestationCAPortal/src/main/webapp/common/common.js b/HIRS_AttestationCAPortal/src/main/webapp/common/common.js index de543576..3258ee06 100644 --- a/HIRS_AttestationCAPortal/src/main/webapp/common/common.js +++ b/HIRS_AttestationCAPortal/src/main/webapp/common/common.js @@ -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 = '' @@ -196,4 +200,19 @@ function rimDownloadLink(id, pagePath){ + ''; return html; -} \ No newline at end of file +} + +/** +* 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(); +}