Skip to content

Commit

Permalink
Merge pull request #70 from WorldHealthOrganization/support-for-rsa-keys
Browse files Browse the repository at this point in the history
Feat: Support for rsa keys in DID
  • Loading branch information
f11h authored Sep 15, 2023
2 parents 02e13c2 + 7f8b9e3 commit d50c338
Show file tree
Hide file tree
Showing 5 changed files with 224 additions and 90 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,16 @@
package eu.europa.ec.dgc.gateway.restapi.dto.did;

import com.fasterxml.jackson.annotation.JsonProperty;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPublicKey;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import lombok.Data;
import lombok.experimental.SuperBuilder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.bouncycastle.jce.spec.ECNamedCurveSpec;

@Data
public class DidTrustListEntryDto {
Expand All @@ -36,18 +43,24 @@ public class DidTrustListEntryDto {

private PublicKeyJwk publicKeyJwk;

@Data
@SuperBuilder
private abstract static class PublicKeyJwk {
@NoArgsConstructor
@Setter
@Getter
public abstract static class PublicKeyJwk {
@JsonProperty("kty")
private String keyType;

@JsonProperty("x5c")
private List<String> encodedX509Certificates;

private PublicKeyJwk(String keyType, List<String> encodedX509Certificates) {
this.keyType = keyType;
this.encodedX509Certificates = new ArrayList<>(encodedX509Certificates);
}
}

@Data
@SuperBuilder
@Getter
@Setter
public static class EcPublicKeyJwk extends PublicKeyJwk {

@JsonProperty("crv")
Expand All @@ -58,6 +71,51 @@ public static class EcPublicKeyJwk extends PublicKeyJwk {

@JsonProperty("y")
private String valueY;

/**
* Instantiate EC PublicKey JWK Class.
*
* @param ecPublicKey EC Public Key that should be wrapped.
* @param base64EncodedCertificates List of Base64 encoded Certificates assigned to provided Public Key.
* They will be added within x5c property of JWK.
*/
public EcPublicKeyJwk(ECPublicKey ecPublicKey, List<String> base64EncodedCertificates) {
super("EC", base64EncodedCertificates);
valueX = Base64.getEncoder().encodeToString(ecPublicKey.getW().getAffineX().toByteArray());
valueY = Base64.getEncoder().encodeToString(ecPublicKey.getW().getAffineY().toByteArray());

ECNamedCurveSpec curveSpec = (ECNamedCurveSpec) ecPublicKey.getParams();
switch (curveSpec.getName()) {
case "prime256v1" -> curve = "P-256";
case "prime384v1" -> curve = "P-384";
case "prime521v1" -> curve = "P-521";
default -> curve = "UNKNOWN CURVE";
}
}
}

@Getter
@Setter
public static class RsaPublicKeyJwk extends PublicKeyJwk {

@JsonProperty("e")
private String valueE;

@JsonProperty("n")
private String valueN;

/**
* Instantiate RSA PublicKey JWK Class.
*
* @param rsaPublicKey RSA Public Key that should be wrapped.
* @param base64EncodedCertificates List of Base64 encoded Certificates assigned to provided Public Key.
* They will be added within x5c property of JWK.
*/
public RsaPublicKeyJwk(RSAPublicKey rsaPublicKey, List<String> base64EncodedCertificates) {
super("RSA", base64EncodedCertificates);
valueN = Base64.getEncoder().encodeToString(rsaPublicKey.getModulus().toByteArray());
valueE = Base64.getEncoder().encodeToString(rsaPublicKey.getPublicExponent().toByteArray());
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@
import java.io.InputStream;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.security.PublicKey;
import java.security.cert.CertificateEncodingException;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPublicKey;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Date;
Expand All @@ -50,7 +53,7 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
import org.jetbrains.annotations.NotNull;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
Expand Down Expand Up @@ -117,56 +120,22 @@ private String generateTrustList() throws Exception {
);

for (TrustedCertificateTrustList cert : certs) {
DidTrustListEntryDto.EcPublicKeyJwk.EcPublicKeyJwkBuilder<?, ?> jwkBuilder =
DidTrustListEntryDto.EcPublicKeyJwk.builder();

if (cert.getParsedCertificate().getPublicKey() instanceof ECPublicKey publicKey) {
PublicKey publicKey = cert.getParsedCertificate().getPublicKey();

jwkBuilder.valueX(Base64.getEncoder().encodeToString(publicKey.getW().getAffineX().toByteArray()));
jwkBuilder.valueY(Base64.getEncoder().encodeToString(publicKey.getW().getAffineY().toByteArray()));
if (publicKey instanceof RSAPublicKey rsaPublicKey) {
addTrustListEntry(trustList, cert,
new DidTrustListEntryDto.RsaPublicKeyJwk(rsaPublicKey, List.of(cert.getCertificate())));

ECNamedCurveSpec curveSpec = (ECNamedCurveSpec) publicKey.getParams();
if (curveSpec.getName().equals("prime256v1")) {
jwkBuilder.curve("P-256");
} else if (curveSpec.getName().equals("prime384v1")) {
jwkBuilder.curve("P-384");
} else if (curveSpec.getName().equals("prime521v1")) {
jwkBuilder.curve("P-521");
}

jwkBuilder.keyType("EC");
jwkBuilder.encodedX509Certificates(new ArrayList<>(List.of(cert.getCertificate())));
DidTrustListEntryDto.EcPublicKeyJwk jwk = jwkBuilder.build();

// Search for Issuer of DSC
Optional<TrustedCertificateTrustList> csca = trustListService.getTrustedCertificateTrustList(
List.of(TrustedPartyEntity.CertificateType.CSCA.name()),
List.of(cert.getCountry()),
List.of(cert.getDomain()),
configProperties.getDid().getIncludeFederated()).stream()
.filter(tp -> tp.getParsedCertificate().getSubjectX500Principal()
.equals(cert.getParsedCertificate().getIssuerX500Principal()))
.findFirst();

if (csca.isPresent()) {
jwk.getEncodedX509Certificates()
.add(Base64.getEncoder().encodeToString(csca.get().getParsedCertificate().getEncoded()));
}

DidTrustListEntryDto trustListEntry = new DidTrustListEntryDto();
trustListEntry.setType("JsonWebKey2020");
trustListEntry.setId(configProperties.getDid().getTrustListIdPrefix() + cert.getKid());
trustListEntry.setController(configProperties.getDid().getTrustListControllerPrefix());
trustListEntry.setPublicKeyJwk(jwk);

trustList.getVerificationMethod().add(trustListEntry);
} else if (publicKey instanceof ECPublicKey ecPublicKey) {
addTrustListEntry(trustList, cert,
new DidTrustListEntryDto.EcPublicKeyJwk(ecPublicKey, List.of(cert.getCertificate())));

} else {
log.error("Public Key is not EC Public Key for cert {} of country {}",
log.error("Public Key is not RSA or EC Public Key for cert {} of country {}",
cert.getThumbprint(),
cert.getCountry());
}

}

// Add TrustedIssuer
Expand All @@ -190,7 +159,6 @@ private String generateTrustList() throws Exception {

if (didContextFile == null) {
log.error("Failed to load DID-Context Document for {}: No Mapping to local JSON-File.", didContext);

}

try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(
Expand All @@ -210,4 +178,36 @@ private String generateTrustList() throws Exception {

return jsonLdObject.toJson();
}

private void addTrustListEntry(DidTrustListDto trustList,
TrustedCertificateTrustList cert,
DidTrustListEntryDto.PublicKeyJwk publicKeyJwk) throws CertificateEncodingException {
Optional<TrustedCertificateTrustList> csca = searchForIssuer(cert);

if (csca.isPresent()) {
publicKeyJwk.getEncodedX509Certificates()
.add(Base64.getEncoder().encodeToString(csca.get().getParsedCertificate().getEncoded()));
}

DidTrustListEntryDto trustListEntry = new DidTrustListEntryDto();
trustListEntry.setType("JsonWebKey2020");
trustListEntry.setId(configProperties.getDid().getTrustListIdPrefix() + cert.getKid());
trustListEntry.setController(configProperties.getDid().getTrustListControllerPrefix());
trustListEntry.setPublicKeyJwk(publicKeyJwk);

trustList.getVerificationMethod().add(trustListEntry);
}

@NotNull
private Optional<TrustedCertificateTrustList> searchForIssuer(TrustedCertificateTrustList cert) {
// Search for Issuer of DSC
return trustListService.getTrustedCertificateTrustList(
List.of(TrustedPartyEntity.CertificateType.CSCA.name()),
List.of(cert.getCountry()),
List.of(cert.getDomain()),
configProperties.getDid().getIncludeFederated()).stream()
.filter(tp -> tp.getParsedCertificate().getSubjectX500Principal()
.equals(cert.getParsedCertificate().getIssuerX500Principal()))
.findFirst();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPublicKey;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.util.ArrayList;
Expand All @@ -56,6 +57,8 @@
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.ArgumentCaptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
Expand All @@ -64,7 +67,6 @@
@SpringBootTest
public class DidTrustListServiceTest {


@Autowired
ObjectMapper objectMapper;

Expand Down Expand Up @@ -111,34 +113,45 @@ public void cleanUp() {
signerInformationRepository.deleteAll();
federationGatewayRepository.deleteAll();
trustedIssuerRepository.deleteAll();
trustedPartyTestHelper.clear(TrustedPartyEntity.CertificateType.UPLOAD, "DE");
trustedPartyTestHelper.clear(TrustedPartyEntity.CertificateType.UPLOAD, "EU");
trustedPartyTestHelper.clear(TrustedPartyEntity.CertificateType.CSCA, "DE");
trustedPartyTestHelper.clear(TrustedPartyEntity.CertificateType.CSCA, "EU");
trustedPartyTestHelper.clear(TrustedPartyEntity.CertificateType.AUTHENTICATION, "DE");
trustedPartyTestHelper.clear(TrustedPartyEntity.CertificateType.AUTHENTICATION, "EU");
}

@BeforeEach
void testData() throws Exception {
void testData(CertificateTestUtils.SignerType signerType) throws Exception {
cleanUp();

federationGateway =
new FederationGatewayEntity(null, ZonedDateTime.now(), "gw-id", "endpoint", "kid", "pk", "impl",
FederationGatewayEntity.DownloadTarget.FEDERATION, FederationGatewayEntity.Mode.APPEND, "sig", -1L,
null, null, 0L, null, null);
new FederationGatewayEntity(null, ZonedDateTime.now(), "gw-id", "endpoint",
"kid", "pk", "impl",
FederationGatewayEntity.DownloadTarget.FEDERATION, FederationGatewayEntity.Mode.APPEND, "sig",
-1L, null, null, 0L, null,
null);
federationGateway = federationGatewayRepository.save(federationGateway);

certUploadDe = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, "DE");
certUploadEu = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, "EU");
certCscaDe = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.CSCA, "DE");
certCscaEu = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.CSCA, "EU");
certAuthDe = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.AUTHENTICATION, "DE");
certAuthEu = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.AUTHENTICATION, "EU");
certUploadDe = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, "DE", signerType);
certUploadEu = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, "EU", signerType);
certCscaDe = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.CSCA, "DE", signerType);
certCscaEu = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.CSCA, "EU", signerType);
certAuthDe = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.AUTHENTICATION, "DE", signerType);
certAuthEu = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.AUTHENTICATION, "EU", signerType);

KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("ec");
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(signerType.getSigningAlgorithm());
certDscDe =
CertificateTestUtils.generateCertificate(keyPairGenerator.generateKeyPair(), "DE", "Test", certCscaDe,
trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.AUTHENTICATION, "DE"));
CertificateTestUtils.generateCertificate(keyPairGenerator.generateKeyPair(), "DE",
"Test", certCscaDe,
trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.AUTHENTICATION,
"DE", signerType), signerType);

certDscDeKid = certificateUtils.getCertKid(certDscDe);
certDscEu =
CertificateTestUtils.generateCertificate(keyPairGenerator.generateKeyPair(), "EU", "Test", certCscaEu,
trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.AUTHENTICATION, "EU"));
CertificateTestUtils.generateCertificate(keyPairGenerator.generateKeyPair(), "EU",
"Test", certCscaEu,
trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.AUTHENTICATION, "EU", signerType),
signerType);

signerInformationRepository.save(new SignerInformationEntity(
null,
Expand All @@ -164,7 +177,8 @@ void testData() throws Exception {
null
));

federatedCertDscEx = CertificateTestUtils.generateCertificate(keyPairGenerator.generateKeyPair(), "EX", "Test");
federatedCertDscEx = CertificateTestUtils.generateCertificate(keyPairGenerator.generateKeyPair(), "EX",
"Test", signerType);
SignerInformationEntity federatedDscEntity = new SignerInformationEntity(
null,
ZonedDateTime.now(),
Expand All @@ -184,8 +198,14 @@ void testData() throws Exception {
trustedIssuerRepository.save(trustedIssuerTestHelper.createTrustedIssuer("XY", "DCC"));
}

@Test
void testTrustList() throws IOException, CertificateEncodingException {
@ParameterizedTest
@ValueSource(booleans = {true, false})
void testTrustList(boolean isEcAlgorithm) throws Exception {
if (isEcAlgorithm) {
testData(CertificateTestUtils.SignerType.EC);
} else {
testData(CertificateTestUtils.SignerType.RSA);
}
ArgumentCaptor<byte[]> uploadArgumentCaptor = ArgumentCaptor.forClass(byte[].class);
doNothing().when(didUploaderMock).uploadDid(uploadArgumentCaptor.capture());

Expand Down Expand Up @@ -237,12 +257,22 @@ private void assertVerificationMethod(Object in, String kid, X509Certificate dsc

LinkedHashMap publicKeyJwk = (LinkedHashMap) jsonNode.get("publicKeyJwk");

Assertions.assertEquals(((ECPublicKey) dsc.getPublicKey()).getW().getAffineX(),
new BigInteger(Base64.getDecoder().decode(publicKeyJwk.get("x").toString())));
Assertions.assertEquals(((ECPublicKey) dsc.getPublicKey()).getW().getAffineY(),
new BigInteger(Base64.getDecoder().decode(publicKeyJwk.get("y").toString())));
Assertions.assertEquals("EC", publicKeyJwk.get("kty").toString());
Assertions.assertEquals("P-256", publicKeyJwk.get("crv").toString());
if (dsc.getPublicKey().getAlgorithm().equals(CertificateTestUtils.SignerType.EC.getSigningAlgorithm())) {
Assertions.assertEquals(((ECPublicKey) dsc.getPublicKey()).getW().getAffineX(),
new BigInteger(Base64.getDecoder().decode(publicKeyJwk.get("x").toString())));
Assertions.assertEquals(((ECPublicKey) dsc.getPublicKey()).getW().getAffineY(),
new BigInteger(Base64.getDecoder().decode(publicKeyJwk.get("y").toString())));
Assertions.assertEquals(CertificateTestUtils.SignerType.EC.getSigningAlgorithm(),
publicKeyJwk.get("kty").toString());
Assertions.assertEquals("P-256", publicKeyJwk.get("crv").toString());
} else {
Assertions.assertEquals(((RSAPublicKey) dsc.getPublicKey()).getPublicExponent(),
new BigInteger(Base64.getDecoder().decode(publicKeyJwk.get("e").toString())));
Assertions.assertEquals(((RSAPublicKey) dsc.getPublicKey()).getModulus(),
new BigInteger(Base64.getDecoder().decode(publicKeyJwk.get("n").toString())));
Assertions.assertEquals(CertificateTestUtils.SignerType.RSA.getSigningAlgorithm(),
publicKeyJwk.get("kty").toString());
}
ArrayList<String> x5c = ((ArrayList<String>) publicKeyJwk.get("x5c"));
Assertions.assertEquals(Base64.getEncoder().encodeToString(dsc.getEncoded()), x5c.get(0));
if (csca != null) {
Expand Down
Loading

0 comments on commit d50c338

Please sign in to comment.