Skip to content

Commit

Permalink
refactoring JWT and KeyStore
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexander Schwarz committed Sep 16, 2021
1 parent fe69c44 commit 882f6fd
Show file tree
Hide file tree
Showing 9 changed files with 91 additions and 148 deletions.
Binary file modified certs/dev-decorator.jks
Binary file not shown.
Binary file removed certs/dev-test.jks
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -48,14 +47,16 @@ public class DgcProperties {

private String activeSignKey;

private List<String> encAliases;
private List<String> encAliases = new ArrayList<>();

private List<String> signAliases;
private List<String> signAliases = new ArrayList<>();

private List<String> keyAliases = new ArrayList<>();

private TokenProperties token;

private List<ServiceProperties> services = new ArrayList<>();

private List<ServiceProperties> endpoints = new ArrayList<>();

@Data
Expand All @@ -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 {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ public static final class PublicKeyJwkIdentityResponse {
private String kid;

private String alg;

private String use;
}

@Data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,7 @@ public enum KeyType {

VALIDATION_DECORATOR_ENC_KEY,

VALIDATION_DECORATOR_SIGN_KEY;
VALIDATION_DECORATOR_SIGN_KEY,

VALIDATION_DECORATOR_KEY;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -103,8 +91,11 @@ public String buildAccessToken(final String subject) {
*/
public Map<String, String> 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<Claims> parsedToken = Jwts.parser()
.setSigningKey(this.getPublicKey())
.setSigningKey(publicKey)
.requireIssuer(properties.getToken().getIssuer())
.parseClaimsJws(tokenContent);
final Claims body = parsedToken.getBody();
Expand Down Expand Up @@ -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()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -114,15 +114,16 @@ private List<ServiceIdentityResponse> 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -146,9 +151,13 @@ public List<String> 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));
Expand Down
30 changes: 17 additions & 13 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down

0 comments on commit 882f6fd

Please sign in to comment.