Merge pull request #878 from nsacyber/v3_issue-877

[#877] Fix ACA-issued certificates not verifying in OpenSSL
This commit is contained in:
iadgovuser26 2024-11-19 13:32:57 -05:00 committed by GitHub
commit accacbd4db
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 91 additions and 39 deletions

View File

@ -125,14 +125,14 @@ public interface CertificateRepository extends JpaRepository<Certificate, UUID>
/** /**
* Query that retrieves a list of issued attestation certificates using the provided device id, * Query that retrieves a list of issued attestation certificates using the provided device id,
* isLDevID value and sort value. * ldevID value and sort value.
* *
* @param deviceId device id * @param deviceId device id
* @param isLDevID is it a LDevId * @param ldevID is it a LDevId
* @param sort sort * @param sort sort
* @return a list of issued attestation certificates * @return a list of issued attestation certificates
*/ */
List<IssuedAttestationCertificate> findByDeviceIdAndIsLDevID(UUID deviceId, boolean isLDevID, Sort sort); List<IssuedAttestationCertificate> findByDeviceIdAndLdevID(UUID deviceId, boolean ldevID, Sort sort);
/** /**
* Query that retrieves a certificates using the provided certificate hash. * Query that retrieves a certificates using the provided certificate hash.
@ -142,3 +142,4 @@ public interface CertificateRepository extends JpaRepository<Certificate, UUID>
*/ */
Certificate findByCertificateHash(int certificateHash); Certificate findByCertificateHash(int certificateHash);
} }

View File

@ -29,7 +29,7 @@ public class IssuedAttestationCertificate extends DeviceAssociatedCertificate {
public static final String AIC_TYPE_LABEL = "TCPA Trusted Platform Identity"; public static final String AIC_TYPE_LABEL = "TCPA Trusted Platform Identity";
@Column @Column
private boolean isLDevID; private boolean ldevID;
@ManyToOne(fetch = FetchType.EAGER) @ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "ek_id") @JoinColumn(name = "ek_id")
@ -45,18 +45,18 @@ public class IssuedAttestationCertificate extends DeviceAssociatedCertificate {
* @param certificateBytes the issued certificate bytes * @param certificateBytes the issued certificate bytes
* @param endorsementCredential the endorsement credential * @param endorsementCredential the endorsement credential
* @param platformCredentials the platform credentials * @param platformCredentials the platform credentials
* @param isLDevID is LDevId * @param ldevID is LDevID
* @throws IOException if there is a problem extracting information from the certificate * @throws IOException if there is a problem extracting information from the certificate
*/ */
public IssuedAttestationCertificate(final byte[] certificateBytes, public IssuedAttestationCertificate(final byte[] certificateBytes,
final EndorsementCredential endorsementCredential, final EndorsementCredential endorsementCredential,
final List<PlatformCredential> platformCredentials, final List<PlatformCredential> platformCredentials,
final boolean isLDevID) final boolean ldevID)
throws IOException { throws IOException {
super(certificateBytes); super(certificateBytes);
this.endorsementCredential = endorsementCredential; this.endorsementCredential = endorsementCredential;
this.platformCredentials = new ArrayList<>(platformCredentials); this.platformCredentials = new ArrayList<>(platformCredentials);
this.isLDevID = isLDevID; this.ldevID = ldevID;
} }
/** /**
@ -65,14 +65,14 @@ public class IssuedAttestationCertificate extends DeviceAssociatedCertificate {
* @param certificatePath path to certificate * @param certificatePath path to certificate
* @param endorsementCredential the endorsement credential * @param endorsementCredential the endorsement credential
* @param platformCredentials the platform credentials * @param platformCredentials the platform credentials
* @param isLDevID is it an LDev ID * @param ldevID is it an LDevID
* @throws IOException if there is a problem extracting information from the certificate * @throws IOException if there is a problem extracting information from the certificate
*/ */
public IssuedAttestationCertificate(final Path certificatePath, public IssuedAttestationCertificate(final Path certificatePath,
final EndorsementCredential endorsementCredential, final EndorsementCredential endorsementCredential,
final List<PlatformCredential> platformCredentials, final List<PlatformCredential> platformCredentials,
final boolean isLDevID) final boolean ldevID)
throws IOException { throws IOException {
this(readBytes(certificatePath), endorsementCredential, platformCredentials, isLDevID); this(readBytes(certificatePath), endorsementCredential, platformCredentials, ldevID);
} }
} }

View File

@ -19,7 +19,9 @@ import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.Extension; import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder; import org.bouncycastle.cert.X509v3CertificateBuilder;
@ -85,7 +87,7 @@ public class AbstractProcessor {
expiry.add(Calendar.DAY_OF_YEAR, getValidDays()); expiry.add(Calendar.DAY_OF_YEAR, getValidDays());
X500Name issuer = X500Name issuer =
new X500Name(acaCertificate.getSubjectX500Principal().getName()); new X509CertificateHolder(acaCertificate.getEncoded()).getSubject();
Date notBefore = new Date(); Date notBefore = new Date();
Date notAfter = expiry.getTime(); Date notAfter = expiry.getTime();
BigInteger serialNumber = BigInteger.valueOf(System.currentTimeMillis()); BigInteger serialNumber = BigInteger.valueOf(System.currentTimeMillis());
@ -103,7 +105,7 @@ public class AbstractProcessor {
endorsementCredential, platformCredentials, deviceName); endorsementCredential, platformCredentials, deviceName);
Extension authKeyIdentifier = IssuedCertificateAttributeHelper Extension authKeyIdentifier = IssuedCertificateAttributeHelper
.buildAuthorityKeyIdentifier(endorsementCredential); .buildAuthorityKeyIdentifier(acaCertificate);
builder.addExtension(subjectAlternativeName); builder.addExtension(subjectAlternativeName);
if (authKeyIdentifier != null) { if (authKeyIdentifier != null) {
@ -118,6 +120,20 @@ public class AbstractProcessor {
+ "Unable to issue certificates"); + "Unable to issue certificates");
} }
// Add signing extension
builder.addExtension(
Extension.keyUsage,
true,
new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment)
);
// Basic constraints
builder.addExtension(
Extension.basicConstraints,
true,
new BasicConstraints(false)
);
ContentSigner signer = new JcaContentSignerBuilder("SHA256WithRSA") ContentSigner signer = new JcaContentSignerBuilder("SHA256WithRSA")
.setProvider("BC").build(getPrivateKey()); .setProvider("BC").build(getPrivateKey());
X509CertificateHolder holder = builder.build(signer); X509CertificateHolder holder = builder.build(signer);
@ -238,7 +254,7 @@ public class AbstractProcessor {
* @param endorsementCredential the endorsement credential used to generate the AC * @param endorsementCredential the endorsement credential used to generate the AC
* @param platformCredentials the platform credentials 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 * @param device the device to which the attestation certificate is tied
* @param isLDevID whether the certificate is a ldevid * @param ldevID whether the certificate is a ldevid
* @return whether the certificate was saved successfully * @return whether the certificate was saved successfully
* @throws {@link CertificateProcessingException} if error occurs in persisting the Attestation * @throws {@link CertificateProcessingException} if error occurs in persisting the Attestation
* Certificate * Certificate
@ -248,7 +264,7 @@ public class AbstractProcessor {
final EndorsementCredential endorsementCredential, final EndorsementCredential endorsementCredential,
final List<PlatformCredential> platformCredentials, final List<PlatformCredential> platformCredentials,
final Device device, final Device device,
final boolean isLDevID) { final boolean ldevID) {
List<IssuedAttestationCertificate> issuedAc; List<IssuedAttestationCertificate> issuedAc;
boolean generateCertificate = true; boolean generateCertificate = true;
PolicyRepository scp = getPolicyRepository(); PolicyRepository scp = getPolicyRepository();
@ -258,27 +274,27 @@ public class AbstractProcessor {
try { try {
// save issued certificate // save issued certificate
IssuedAttestationCertificate attCert = new IssuedAttestationCertificate( IssuedAttestationCertificate attCert = new IssuedAttestationCertificate(
derEncodedAttestationCertificate, endorsementCredential, platformCredentials, isLDevID); derEncodedAttestationCertificate, endorsementCredential, platformCredentials, ldevID);
if (scp != null) { if (scp != null) {
policySettings = scp.findByName("Default"); policySettings = scp.findByName("Default");
Sort sortCriteria = Sort.by(Sort.Direction.DESC, "endValidity"); Sort sortCriteria = Sort.by(Sort.Direction.DESC, "endValidity");
issuedAc = certificateRepository.findByDeviceIdAndIsLDevID(device.getId(), isLDevID, issuedAc = certificateRepository.findByDeviceIdAndLdevID(device.getId(), ldevID,
sortCriteria); sortCriteria);
generateCertificate = isLDevID ? policySettings.isIssueDevIdCertificate() generateCertificate = ldevID ? policySettings.isIssueDevIdCertificate()
: policySettings.isIssueAttestationCertificate(); : policySettings.isIssueAttestationCertificate();
if (issuedAc != null && issuedAc.size() > 0 if (issuedAc != null && issuedAc.size() > 0
&& (isLDevID ? policySettings.isDevIdExpirationFlag() && (ldevID ? policySettings.isDevIdExpirationFlag()
: policySettings.isGenerateOnExpiration())) { : policySettings.isGenerateOnExpiration())) {
if (issuedAc.get(0).getEndValidity().after(currentDate)) { if (issuedAc.get(0).getEndValidity().after(currentDate)) {
// so the issued AC is not expired // so the issued AC is not expired
// however are we within the threshold // however are we within the threshold
days = ProvisionUtils.daysBetween(currentDate, issuedAc.get(0).getEndValidity()); days = ProvisionUtils.daysBetween(currentDate, issuedAc.get(0).getEndValidity());
generateCertificate = generateCertificate =
days < Integer.parseInt(isLDevID ? policySettings.getDevIdReissueThreshold() days < Integer.parseInt(ldevID ? policySettings.getDevIdReissueThreshold()
: policySettings.getReissueThreshold()); : policySettings.getReissueThreshold());
} }
} }

View File

@ -21,6 +21,7 @@ import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames; import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.GeneralNamesBuilder; import org.bouncycastle.asn1.x509.GeneralNamesBuilder;
import org.bouncycastle.asn1.x509.KeyPurposeId; import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
import org.bouncycastle.asn1.x509.TBSCertificate; import org.bouncycastle.asn1.x509.TBSCertificate;
import java.io.IOException; import java.io.IOException;
@ -69,26 +70,28 @@ public final class IssuedCertificateAttributeHelper {
* This method builds the AKI extension that will be stored in the generated * This method builds the AKI extension that will be stored in the generated
* Attestation Issued Certificate. * Attestation Issued Certificate.
* *
* @param endorsementCredential EK object to pull AKI from. * @param acaCertificate ACA certificate to pull SKI from, that will be used to build matching AKI.
* @return the AKI extension. * @return the AKI extension.
* @throws IOException on bad get instance for AKI. * @throws IOException on bad get instance for SKI.
*/ */
public static Extension buildAuthorityKeyIdentifier( public static Extension buildAuthorityKeyIdentifier(
final EndorsementCredential endorsementCredential) throws IOException { final X509Certificate acaCertificate) throws IOException {
if (endorsementCredential == null || endorsementCredential.getX509Certificate() == null) { if (acaCertificate == null) {
return null; return null;
} }
byte[] extValue = endorsementCredential.getX509Certificate() byte[] extValue = acaCertificate
.getExtensionValue(Extension.authorityKeyIdentifier.getId()); .getExtensionValue(Extension.subjectKeyIdentifier.getId());
if (extValue == null) { if (extValue == null) {
return null; return null;
} }
byte[] authExtension = ASN1OctetString.getInstance(extValue).getOctets(); byte[] authExtension = ASN1OctetString.getInstance(extValue).getOctets();
AuthorityKeyIdentifier aki = AuthorityKeyIdentifier.getInstance(authExtension); SubjectKeyIdentifier ski = SubjectKeyIdentifier.getInstance(authExtension);
return new Extension(Extension.authorityKeyIdentifier, true, aki.getEncoded()); AuthorityKeyIdentifier aki = new AuthorityKeyIdentifier(ski.getKeyIdentifier());
return new Extension(Extension.authorityKeyIdentifier, false, aki.getEncoded());
} }
/** /**

View File

@ -19,7 +19,12 @@ import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.Extension; import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.GeneralNames; import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.TBSCertificate; import org.bouncycastle.asn1.x509.TBSCertificate;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Nested;
@ -33,7 +38,6 @@ import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.OAEPParameterSpec; import javax.crypto.spec.OAEPParameterSpec;
import javax.crypto.spec.PSource; import javax.crypto.spec.PSource;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import javax.security.auth.x500.X500Principal;
import java.io.IOException; import java.io.IOException;
import java.math.BigInteger; import java.math.BigInteger;
import java.net.URISyntaxException; import java.net.URISyntaxException;
@ -53,6 +57,7 @@ import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPublicKey; import java.security.interfaces.RSAPublicKey;
import java.security.spec.MGF1ParameterSpec; import java.security.spec.MGF1ParameterSpec;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -61,6 +66,7 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions;
@ -376,6 +382,37 @@ public class AttestationCertificateAuthorityTest {
verifyNoMoreInteractions(certificate, symmetricKey); verifyNoMoreInteractions(certificate, symmetricKey);
} }
/**
* Creates a self-signed X.509 public-key certificate.
*
* @param pair KeyPair to create the cert for
* @return self-signed X509Certificate
*/
private static X509Certificate createSelfSignedCertificate(final KeyPair pair) {
Security.addProvider(new BouncyCastleProvider());
final int timeRange = 10000;
X509Certificate cert = null;
try {
X500Name issuerName = new X500Name("CN=TEST2, OU=TEST2, O=TEST2, C=TEST2");
X500Name subjectName = new X500Name("CN=TEST, OU=TEST, O=TEST, C=TEST");
BigInteger serialNumber = BigInteger.ONE;
Date notBefore = new Date(System.currentTimeMillis() - timeRange);
Date notAfter = new Date(System.currentTimeMillis() + timeRange);
X509v3CertificateBuilder builder =
new JcaX509v3CertificateBuilder(issuerName, serialNumber, notBefore, notAfter,
subjectName, pair.getPublic());
ContentSigner signer =
new JcaContentSignerBuilder("SHA256WithRSA").setProvider("BC").build(
pair.getPrivate());
return new JcaX509CertificateConverter().setProvider("BC").getCertificate(
builder.build(signer));
} catch (Exception e) {
fail("Exception occurred while creating a cert", e);
}
return cert;
}
/** /**
* Tests {@link AttestationCertificateAuthority# * Tests {@link AttestationCertificateAuthority#
* AttestationCertificateAuthority(SupplyChainValidationService, PrivateKey, * AttestationCertificateAuthority(SupplyChainValidationService, PrivateKey,
@ -390,14 +427,13 @@ public class AttestationCertificateAuthorityTest {
final String identityProofLabelString = "label"; final String identityProofLabelString = "label";
byte[] identityProofLabel = identityProofLabelString.getBytes(StandardCharsets.UTF_8); byte[] identityProofLabel = identityProofLabelString.getBytes(StandardCharsets.UTF_8);
byte[] modulus = ((RSAPublicKey) keyPair.getPublic()).getModulus().toByteArray(); byte[] modulus = ((RSAPublicKey) keyPair.getPublic()).getModulus().toByteArray();
X500Principal principal = new X500Principal("CN=TEST, OU=TEST, O=TEST, C=TEST");
int validDays = 1; int validDays = 1;
// create mocks for testing // create mocks for testing
IdentityProof identityProof = mock(IdentityProof.class); IdentityProof identityProof = mock(IdentityProof.class);
AsymmetricPublicKey asymmetricPublicKey = mock(AsymmetricPublicKey.class); AsymmetricPublicKey asymmetricPublicKey = mock(AsymmetricPublicKey.class);
StorePubKey storePubKey = mock(StorePubKey.class); StorePubKey storePubKey = mock(StorePubKey.class);
X509Certificate acaCertificate = mock(X509Certificate.class); X509Certificate acaCertificate = createSelfSignedCertificate(keyPair);
// assign ACA fields // assign ACA fields
ReflectionTestUtils.setField(aca, "validDays", validDays); ReflectionTestUtils.setField(aca, "validDays", validDays);
@ -406,10 +442,6 @@ public class AttestationCertificateAuthorityTest {
// prepare identity proof interactions // prepare identity proof interactions
when(identityProof.getLabel()).thenReturn(identityProofLabel); when(identityProof.getLabel()).thenReturn(identityProofLabel);
// prepare other mocks
when(acaCertificate.getSubjectX500Principal()).thenReturn(principal);
when(acaCertificate.getIssuerX500Principal()).thenReturn(principal);
// perform the test // perform the test
X509Certificate certificate = abstractProcessor.accessGenerateCredential(keyPair.getPublic(), X509Certificate certificate = abstractProcessor.accessGenerateCredential(keyPair.getPublic(),
null, null,
@ -453,8 +485,7 @@ public class AttestationCertificateAuthorityTest {
assertEquals(tomorrow.get(Calendar.DATE), afterDate.get(Calendar.DATE)); assertEquals(tomorrow.get(Calendar.DATE), afterDate.get(Calendar.DATE));
// validate mock interactions // validate mock interactions
verify(acaCertificate).getSubjectX500Principal(); verifyNoMoreInteractions(identityProof, asymmetricPublicKey, storePubKey);
verifyNoMoreInteractions(identityProof, asymmetricPublicKey, storePubKey, acaCertificate);
} }
/** /**

View File

@ -74,7 +74,7 @@ public class PersistenceJPAConfig implements WebMvcConfigurer {
@Value("${server.ssl.key-store-password:''}") @Value("${server.ssl.key-store-password:''}")
private String keyStorePassword; private String keyStorePassword;
@Value("${server.ssl.key-alias}") @Value("${aca.certificates.signing-key-alias}")
private String keyAlias; private String keyAlias;
@Autowired @Autowired

View File

@ -33,6 +33,7 @@ server.ssl.key-alias=hirs_aca_tls_rsa_3k_sha384
server.ssl.enabled-protocols=TLSv1.2, TLSv1.3 server.ssl.enabled-protocols=TLSv1.2, TLSv1.3
server.ssl.ciphers=TLS_AES_256_GCM_SHA384, ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-RSA-AES256-GCM-SHA384, DHE-RSA-AES256-GCM-SHA384, AES256-GCM-SHA384 server.ssl.ciphers=TLS_AES_256_GCM_SHA384, ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-RSA-AES256-GCM-SHA384, DHE-RSA-AES256-GCM-SHA384, AES256-GCM-SHA384
# ACA specific default properties # ACA specific default properties
aca.certificates.signing-key-alias=HIRS_leaf_ca3_rsa_3k_sha384
aca.certificates.validity=3652 aca.certificates.validity=3652
# Compression settings # Compression settings
server.compression.enabled=true server.compression.enabled=true

View File

@ -53,7 +53,7 @@
} }
}, },
{ {
data: 'isLDevID', data: 'ldevID',
searchable:false, searchable:false,
render: function (data, type, full, meta) { render: function (data, type, full, meta) {
if (data === true) { if (data === true) {
@ -134,4 +134,4 @@
</script> </script>
</jsp:body> </jsp:body>
</my:page> </my:page>