diff --git a/certs/dev-decorator.jks b/certs/dev-decorator.jks index f815f53..12591c9 100644 Binary files a/certs/dev-decorator.jks and b/certs/dev-decorator.jks differ diff --git a/certs/dev-test.jks b/certs/dev-test.jks deleted file mode 100644 index b344179..0000000 Binary files a/certs/dev-test.jks and /dev/null differ diff --git a/src/main/java/eu/europa/ec/dgc/validation/decorator/config/DgcProperties.java b/src/main/java/eu/europa/ec/dgc/validation/decorator/config/DgcProperties.java index cb92e6f..f6a32ae 100644 --- a/src/main/java/eu/europa/ec/dgc/validation/decorator/config/DgcProperties.java +++ b/src/main/java/eu/europa/ec/dgc/validation/decorator/config/DgcProperties.java @@ -20,7 +20,6 @@ package eu.europa.ec.dgc.validation.decorator.config; -import io.jsonwebtoken.SignatureAlgorithm; import java.time.Duration; import java.time.temporal.ChronoUnit; import java.util.ArrayList; @@ -48,14 +47,16 @@ public class DgcProperties { private String activeSignKey; - private List encAliases; + private List encAliases = new ArrayList<>(); - private List signAliases; + private List signAliases = new ArrayList<>(); + + private List keyAliases = new ArrayList<>(); private TokenProperties token; - + private List services = new ArrayList<>(); - + private List endpoints = new ArrayList<>(); @Data @@ -69,33 +70,19 @@ public static final class GatewayDownload { @Data public static final class TokenProperties { - public static final String ALGORITHM_ELLIPTIC_CURVE = "ES"; - - // use "ks" (keyStore) or "config" - private String provider; - private String issuer; private String type; - private String algorithm; + private TokenInitializeProperties initialize; + } - private int keysize; + @Data + public static final class TokenInitializeProperties { private int validity; - - private String publicKey; - - private String privateKey; - - private String keyAlgorithm; - - public SignatureAlgorithm getSignatureAlgorithm() { - final String name = String.format("%s%d", this.algorithm.toUpperCase(), this.keysize); - return SignatureAlgorithm.forName(name); - } } - + @Data public static final class ServiceProperties { diff --git a/src/main/java/eu/europa/ec/dgc/validation/decorator/dto/IdentityResponse.java b/src/main/java/eu/europa/ec/dgc/validation/decorator/dto/IdentityResponse.java index f6bc0ee..721adb5 100644 --- a/src/main/java/eu/europa/ec/dgc/validation/decorator/dto/IdentityResponse.java +++ b/src/main/java/eu/europa/ec/dgc/validation/decorator/dto/IdentityResponse.java @@ -60,6 +60,8 @@ public static final class PublicKeyJwkIdentityResponse { private String kid; private String alg; + + private String use; } @Data diff --git a/src/main/java/eu/europa/ec/dgc/validation/decorator/entity/KeyType.java b/src/main/java/eu/europa/ec/dgc/validation/decorator/entity/KeyType.java index fc8b258..8d38637 100644 --- a/src/main/java/eu/europa/ec/dgc/validation/decorator/entity/KeyType.java +++ b/src/main/java/eu/europa/ec/dgc/validation/decorator/entity/KeyType.java @@ -26,5 +26,7 @@ public enum KeyType { VALIDATION_DECORATOR_ENC_KEY, - VALIDATION_DECORATOR_SIGN_KEY; + VALIDATION_DECORATOR_SIGN_KEY, + + VALIDATION_DECORATOR_KEY; } diff --git a/src/main/java/eu/europa/ec/dgc/validation/decorator/service/AccessTokenService.java b/src/main/java/eu/europa/ec/dgc/validation/decorator/service/AccessTokenService.java index 35cb3aa..bac69c5 100644 --- a/src/main/java/eu/europa/ec/dgc/validation/decorator/service/AccessTokenService.java +++ b/src/main/java/eu/europa/ec/dgc/validation/decorator/service/AccessTokenService.java @@ -22,22 +22,14 @@ import eu.europa.ec.dgc.validation.decorator.config.DgcProperties; import eu.europa.ec.dgc.validation.decorator.exception.DccException; -import eu.europa.ec.dgc.validation.decorator.exception.NotImplementedException; -import eu.europa.ec.dgc.validation.decorator.exception.UncheckedInvalidKeySpecException; -import eu.europa.ec.dgc.validation.decorator.exception.UncheckedNoSuchAlgorithmException; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jws; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; -import java.security.KeyFactory; -import java.security.NoSuchAlgorithmException; +import io.jsonwebtoken.SignatureAlgorithm; import java.security.PrivateKey; import java.security.PublicKey; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.X509EncodedKeySpec; import java.time.Instant; -import java.util.Base64; import java.util.Collections; import java.util.Date; import java.util.Map; @@ -50,10 +42,6 @@ public class AccessTokenService { public static final String TOKEN_PREFIX = "Bearer "; - private static final String KEY_PROVIDER = "ks"; - - private static final String CONFIG_PROVIDER = "config"; - private final DgcProperties properties; private final KeyProvider keyProvider; @@ -103,8 +91,11 @@ public String buildAccessToken(final String subject) { */ public Map parseAccessToken(String token) { final String tokenContent = token.startsWith(TOKEN_PREFIX) ? token.replace(TOKEN_PREFIX, "") : token; + final String activeSignKey = this.keyProvider.getActiveSignKey(); + final PublicKey publicKey = this.keyProvider.receiveCertificate(activeSignKey).getPublicKey(); + final Jws parsedToken = Jwts.parser() - .setSigningKey(this.getPublicKey()) + .setSigningKey(publicKey) .requireIssuer(properties.getToken().getIssuer()) .parseClaimsJws(tokenContent); final Claims body = parsedToken.getBody(); @@ -135,71 +126,18 @@ public boolean isValid(final String token) { } private JwtBuilder getAccessTokenBuilder() { + final String activeSignKey = this.keyProvider.getActiveSignKey(); + final PrivateKey privateKey = this.keyProvider.receivePrivateKey(activeSignKey); + final String algorithm = this.keyProvider.getAlg(activeSignKey); + final SignatureAlgorithm signatureAlgorithm = io.jsonwebtoken.SignatureAlgorithm.valueOf(algorithm); + final String keyId = this.keyProvider.getKid(activeSignKey); + final int validity = properties.getToken().getInitialize().getValidity(); + return Jwts.builder() - .signWith(properties.getToken().getSignatureAlgorithm(), this.getPrivateKey()) + .signWith(signatureAlgorithm, privateKey) .setHeaderParam("typ", properties.getToken().getType()) - .setHeaderParam("kid", this.getKeyId()) + .setHeaderParam("kid", keyId) .setIssuer(properties.getToken().getIssuer()) - .setExpiration(new Date(Instant.now().plusSeconds(properties.getToken().getValidity()).toEpochMilli())); - } - - private PublicKey parsePublicKey(String privateKeyBase64) { - try { - final byte[] keyBytes = Base64.getDecoder().decode(privateKeyBase64); - final X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes); - final KeyFactory kf = KeyFactory.getInstance(this.getKeyAlgorithm()); - return kf.generatePublic(spec); - } catch (NoSuchAlgorithmException e) { - throw new UncheckedNoSuchAlgorithmException(e); - } catch (InvalidKeySpecException e) { - throw new UncheckedInvalidKeySpecException(e); - } - } - - private PrivateKey parsePrivateKey(String privateKeyBase64) { - try { - final byte[] keyBytes = Base64.getDecoder().decode(privateKeyBase64); - final PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes); - final KeyFactory kf = KeyFactory.getInstance(this.getKeyAlgorithm()); - return kf.generatePrivate(spec); - } catch (NoSuchAlgorithmException e) { - throw new UncheckedNoSuchAlgorithmException(e); - } catch (InvalidKeySpecException e) { - throw new UncheckedInvalidKeySpecException(e); - } - } - - private String getKeyAlgorithm() { - return properties.getToken().getKeyAlgorithm(); - } - - private String getKeyId() { - if (CONFIG_PROVIDER.equalsIgnoreCase(this.properties.getToken().getProvider())) { - return "config"; - } else if (KEY_PROVIDER.equalsIgnoreCase(this.properties.getToken().getProvider())) { - return keyProvider.getKid(keyProvider.getActiveSignKey()); - } - throw new NotImplementedException(String - .format("Token provider '%s' not implemented", this.properties.getToken().getProvider())); - } - - private PublicKey getPublicKey() { - if (CONFIG_PROVIDER.equalsIgnoreCase(this.properties.getToken().getProvider())) { - return this.parsePublicKey(this.properties.getToken().getPublicKey()); - } else if (KEY_PROVIDER.equalsIgnoreCase(this.properties.getToken().getProvider())) { - return keyProvider.receiveCertificate(keyProvider.getActiveSignKey()).getPublicKey(); - } - throw new NotImplementedException(String - .format("Token provider '%s' not implemented", this.properties.getToken().getProvider())); - } - - private PrivateKey getPrivateKey() { - if (CONFIG_PROVIDER.equalsIgnoreCase(this.properties.getToken().getProvider())) { - return this.parsePrivateKey(this.properties.getToken().getPrivateKey()); - } else if (KEY_PROVIDER.equalsIgnoreCase(this.properties.getToken().getProvider())) { - return keyProvider.receivePrivateKey(keyProvider.getActiveSignKey()); - } - throw new NotImplementedException(String - .format("Token provider '%s' not implemented", this.properties.getToken().getProvider())); + .setExpiration(new Date(Instant.now().plusSeconds(validity).toEpochMilli())); } } diff --git a/src/main/java/eu/europa/ec/dgc/validation/decorator/service/IdentityService.java b/src/main/java/eu/europa/ec/dgc/validation/decorator/service/IdentityService.java index 46e4ca9..fd1c6ca 100644 --- a/src/main/java/eu/europa/ec/dgc/validation/decorator/service/IdentityService.java +++ b/src/main/java/eu/europa/ec/dgc/validation/decorator/service/IdentityService.java @@ -47,7 +47,7 @@ public class IdentityService { private static final String VERIFICATION_TYPE = "JsonWebKey2020"; - private static final String PUBLIC_KEY_ALGORITM = "ES256"; + private final DgcProperties dgcProperties; @@ -114,15 +114,16 @@ private List getServices(final String element, final St } private PublicKeyJwkIdentityResponse buildPublicKey(String keyName) { - final Certificate certificate = keyProvider.receiveCertificate(keyName); - final PublicKeyJwkIdentityResponse publicKeyJwk = new PublicKeyJwkIdentityResponse(); + final Certificate certificate = keyProvider.receiveCertificate(keyName); try { + final PublicKeyJwkIdentityResponse publicKeyJwk = new PublicKeyJwkIdentityResponse(); publicKeyJwk.setX5c(Base64.getEncoder().encodeToString(certificate.getEncoded())); publicKeyJwk.setKid(keyProvider.getKid(keyName)); - publicKeyJwk.setAlg(PUBLIC_KEY_ALGORITM); + publicKeyJwk.setAlg(keyProvider.getAlg(keyName)); + publicKeyJwk.setUse(keyProvider.getKeyUse(keyName).name().toLowerCase()); + return publicKeyJwk; } catch (CertificateEncodingException e) { throw new DccException("Can not encode certificate", e); } - return publicKeyJwk; } } diff --git a/src/main/java/eu/europa/ec/dgc/validation/decorator/service/KeyStoreKeyProvider.java b/src/main/java/eu/europa/ec/dgc/validation/decorator/service/KeyStoreKeyProvider.java index 67c0a52..df90cb9 100644 --- a/src/main/java/eu/europa/ec/dgc/validation/decorator/service/KeyStoreKeyProvider.java +++ b/src/main/java/eu/europa/ec/dgc/validation/decorator/service/KeyStoreKeyProvider.java @@ -85,47 +85,52 @@ public void createKeys() throws NoSuchAlgorithmException, IOException, Certifica final Path filePath = Path.of(dgcConfigProperties.getKeyStoreFile()); if (!Files.exists(filePath)) { - log.error("keyfile not found on: {} please adapt the configuration property: issuance.keyStoreFile", + final String msg = String.format( + "keyfile not found on '%s' please adapt the configuration property: issuance.keyStoreFile", filePath); - throw new DccException("keyfile not found on: " + filePath - + " please adapt the configuration property: issuance.keyStoreFile"); + log.error(msg); + throw new DccException(msg); } - final CertificateUtils certificateUtils = new CertificateUtils(); final KeyStore keyStore = KeyStore.getInstance("JKS"); final char[] keyStorePassword = dgcConfigProperties.getKeyStorePassword().toCharArray(); try (InputStream is = new FileInputStream(dgcConfigProperties.getKeyStoreFile())) { final char[] privateKeyPassword = dgcConfigProperties.getPrivateKeyPassword().toCharArray(); keyStore.load(is, privateKeyPassword); - KeyStore.PasswordProtection keyPassword = new KeyStore.PasswordProtection(keyStorePassword); - - for (String alias : this.getKeyNames(KeyType.ALL)) { - final PrivateKeyEntry privateKeyEntry = (PrivateKeyEntry) keyStore.getEntry(alias, keyPassword); - - if (privateKeyEntry != null) { - final X509Certificate cert = (X509Certificate) keyStore.getCertificate(alias); - PrivateKey privateKey = privateKeyEntry.getPrivateKey(); - certificates.put(alias, cert); - privateKeys.put(alias, privateKey); - - final String kid = certificateUtils.getCertKid((X509Certificate) cert); - kids.put(alias, kid); - kidToName.put(kid, alias); - - if (cert.getSigAlgOID().contains("1.2.840.113549.1.1.1")) { - algs.put(alias, "RS256"); - } else if (cert.getSigAlgOID().contains("1.2.840.113549.1.1.10")) { - algs.put(alias, "PS256"); - } else if (cert.getSigAlgOID().contains("1.2.840.10045.4.3.2")) { - algs.put(alias, "ES256"); - } else { - throw new NotImplementedException(String.format("SigAlg OID '{}'", cert.getSigAlgOID())); + final KeyStore.PasswordProtection keyPassword = new KeyStore.PasswordProtection(keyStorePassword); + + for (final String alias : this.getKeyNames(KeyType.ALL)) { + if (keyStore.isKeyEntry(alias)) { + final PrivateKeyEntry privateKeyEntry = (PrivateKeyEntry) keyStore.getEntry(alias, keyPassword); + if (privateKeyEntry != null) { + final PrivateKey privateKey = privateKeyEntry.getPrivateKey(); + privateKeys.put(alias, privateKey); } - } + } + final X509Certificate cert = (X509Certificate) keyStore.getCertificate(alias); + this.handleCertificate(alias, cert); } } } + private void handleCertificate(final String alias, final X509Certificate cert) { + this.certificates.put(alias, cert); + + final String kid = new CertificateUtils().getCertKid((X509Certificate) cert); + this.kids.put(alias, kid); + this.kidToName.put(kid, alias); + + if (cert.getSigAlgOID().contains("1.2.840.113549.1.1.1")) { + this.algs.put(alias, "RS256"); + } else if (cert.getSigAlgOID().contains("1.2.840.113549.1.1.10")) { + this.algs.put(alias, "PS256"); + } else if (cert.getSigAlgOID().contains("1.2.840.10045.4.3.2")) { + this.algs.put(alias, "ES256"); + } else { + throw new NotImplementedException(String.format("SigAlg OID '{}'", cert.getSigAlgOID())); + } + } + @Override public Certificate receiveCertificate(String keyName) { return certificates.get(keyName); @@ -146,9 +151,13 @@ public List getKeyNames(KeyType type) { case VALIDATION_DECORATOR_SIGN_KEY: keyNames.addAll(dgcConfigProperties.getSignAliases()); break; + case VALIDATION_DECORATOR_KEY: + keyNames.addAll(dgcConfigProperties.getKeyAliases()); + break; case ALL: keyNames.addAll(dgcConfigProperties.getEncAliases()); keyNames.addAll(dgcConfigProperties.getSignAliases()); + keyNames.addAll(dgcConfigProperties.getKeyAliases()); break; default: throw new NotImplementedException(String.format("Key type '%s'", type)); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 1440429..4235278 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -20,22 +20,26 @@ dgc: serviceUrl: http://localhost:8080 keyStoreFile: certs/dev-decorator.jks keyStorePassword: changeMe - privateKeyPassword: changeMe - activeSignKey: validationdecoratorsignkey + privateKeyPassword: changeMe + #### activeSignKey must be one of signAliases + activeSignKey: ValidationDecoratorSignKey-1 encAliases: - - validationdecoratorenckey + - ValidationDecoratorEncKey-1 signAliases: - - validationdecoratorsignkey + - ValidationDecoratorSignKey-1 + keyAliases: + - AccessTokenServiceKey-1 + - ServiceProviderKey-1 + - CancellationServiceKey-1 + - StatusServiceKey-1 + - DccValidatorServiceKey-1 + token: - provider: config #### use "ks" (keyStore) or "config" issuer: Validation Decorator Dervice type: JWT - algorithm: ES - keysize: 256 - validity: 3600 #### in seconds. Expiration is calculated - publicKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIPrtYsW9+Juwp/mt7h8FJ3LgFRIUl2Vlmcl1DUm5gNHl0LnHIL4Jff6mg6yVhehdQiMvkhUtTvmFIUWONSJEnw== - privateKey: MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCBSuPIbykwH24sjQsTneeN6EyjiA1NK5W7uca+HxmGmWw== - keyAlgorithm: EC + initialize: + #### in seconds. Expiration is calculated + validity: 3600 #### Validation Service services: - id: ${dgc.serviceUrl}/identity/ValidationService @@ -44,8 +48,8 @@ dgc: name: Validation Service #### Validation Decorator endpoints: - - id: ${dgc.serviceUrl}/identity/AccessCredentialService - type: AccessCredentialService + - id: ${dgc.serviceUrl}/identity/AccessTokenService + type: AccessTokenService serviceEndpoint: ${dgc.serviceUrl}/token name: Validation Decorator Token - id: ${dgc.serviceUrl}/identity/ServiceProvider