/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: MIT-0
*/
package com.sample;
import lombok.NonNull;
import lombok.val;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.jce.X509KeyUsage;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.services.acmpca.AcmPcaClient;
import software.amazon.awssdk.services.acmpca.model.*;
import java.io.IOException;
import java.io.StringReader;
import java.time.Duration;
import java.time.Period;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Adaptation of code in PCA documentation
*/
public class IssueDeviceAttestationCertificate {
//protected final DefaultCredentialsProvider credentialsProvider = DefaultCredentialsProvider.create();
protected final AcmPcaClient client;
public IssueDeviceAttestationCertificate(@NonNull final AcmPcaClient client) {
this.client = client;
}
private static String generateKeyUsageValue() throws IOException {
KeyUsage keyUsage = new KeyUsage(X509KeyUsage.digitalSignature);
byte[] kuBytes = keyUsage.getEncoded();
return Base64.getEncoder().encodeToString(kuBytes);
}
public @NonNull String run(@NonNull final String paiArn,
@NonNull final String pid,
@NonNull Map paiSubjDic,
@NonNull final String csr,
final long validityInDays) throws IOException, InterruptedException {
// Parse the PAI CA certificate.
if (paiSubjDic.containsKey("1.3.6.1.4.1.37244.2.2") && !paiSubjDic.get("1.3.6.1.4.1.37244.2.2").equals(pid)) {
throw new IllegalArgumentException("Cannot sign as PAI is product specific and supplied PID " + pid +
" is different from the one of the PAI - " + paiSubjDic.get("1.3.6.1.4.1.37244.2.2"));
}
// Parse CSR to obtain the Subject.
val csrParser = new PEMParser(new StringReader(csr));
val csrSubject = ((PKCS10CertificationRequest)csrParser.readObject()).getSubject();
// Set the validity period for the certificate to be issued.
val validity = Validity.builder()
.type(ValidityPeriodType.DAYS)
.value(validityInDays)
.build();
// Define custom attributes
final List customAttributes = Stream.concat(
Arrays.stream(csrSubject.getRDNs()).map(rdn -> CustomAttribute.builder()
.objectIdentifier(rdn.getFirst().getType().getId())
.value(rdn.getFirst().getValue().toString())
.build()),
Stream.of(
CustomAttribute.builder()
.objectIdentifier("1.3.6.1.4.1.37244.2.1") // matter-oid-vid
.value(paiSubjDic.get("1.3.6.1.4.1.37244.2.1")) // Must coincide with the one on PAI.
.build(),
CustomAttribute.builder()
.objectIdentifier("1.3.6.1.4.1.37244.2.2") // matter-oid-pid
.value(pid)
.build()
))
.collect(Collectors.toList());
// Define a cert subject.
ASN1Subject subject = ASN1Subject.builder()
.customAttributes(customAttributes)
.build();
// Generate Base64 encoded extension value for ExtendedKeyUsage
String base64EncodedKUValue = generateKeyUsageValue();
// Generate custom extension
CustomExtension customKeyUsageExtension = CustomExtension.builder()
.objectIdentifier("2.5.29.15") // KeyUsage Extension OID
.value(base64EncodedKUValue)
.critical(true)
.build();
Extensions extensions = Extensions.builder()
.customExtensions(List.of(customKeyUsageExtension))
.build();
val apiPassthrough = ApiPassthrough.builder()
.subject(subject)
.extensions(extensions)
.build();
// Create a certificate request:
final IssueCertificateRequest req =
IssueCertificateRequest.builder()
// Set the CA ARN.
.certificateAuthorityArn(paiArn)
// Specify the certificate signing request (CSR) for the certificate to be signed and issued.
.csr(SdkBytes.fromUtf8String(csr))
// Specify the template for the issued certificate.
.templateArn("arn:aws:acm-pca:::template/BlankEndEntityCertificate_CriticalBasicConstraints_APIPassthrough/V1")
// Set the signing algorithm.
.signingAlgorithm(SigningAlgorithm.SHA256_WITHECDSA)
// Set the validity period for the certificate to be issued.
.validity(validity)
// Set the idempotency token.
.idempotencyToken("1234")
// Set the custom extensions.
.apiPassthrough(apiPassthrough)
.build();
// Issue the certificate.
IssueCertificateResponse result = client.issueCertificate(req);
// Retrieve the certificate.
final String certArn = result.certificateArn();
final GetCertificateRequest certReq = GetCertificateRequest.builder()
.certificateAuthorityArn(paiArn)
.certificateArn(certArn)
.build();
do {
try {
return client.getCertificate(certReq).certificate();
} catch (RequestInProgressException ignore) {
// Not ready yet, let's wait longer
Thread.sleep(Duration.ofSeconds(1L).toMillis());
}
} while (true);
}
}