Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -296,12 +296,21 @@ public static JsonWebKey loadJsonWebKey(Message m, Properties props, KeyOperatio
public static JsonWebKey loadJsonWebKey(Message m, Properties props, KeyOperation keyOper, String inHeaderKid) {
PrivateKeyPasswordProvider cb = KeyManagementUtils.loadPasswordProvider(m, props, keyOper);
JsonWebKeys jwkSet = loadJwkSet(m, props, cb);
final String kid;
if (inHeaderKid != null
&& MessageUtils.getContextualBoolean(m, JoseConstants.RSSEC_ACCEPT_PUBLIC_KEY, false)) {
kid = inHeaderKid;
} else {
kid = KeyManagementUtils.getKeyId(m, props, JoseConstants.RSSEC_KEY_STORE_ALIAS, keyOper);
String kid = KeyManagementUtils.getKeyId(m, props, JoseConstants.RSSEC_KEY_STORE_ALIAS, keyOper);
// A configured alias pins the key, unless accepting public keys has been explicitly enabled,
// in which case the key id from the incoming headers selects the key as before.
// Otherwise the key id selects a key from the configured key set, which lets verification follow
// key rotation (e.g. of a JWKS published by an identity provider), but only a key explicitly
// marked for the requested operation, so that a key set containing other keys can't widen trust.
if (inHeaderKid != null) {
boolean acceptPublicKey =
MessageUtils.getContextualBoolean(m, JoseConstants.RSSEC_ACCEPT_PUBLIC_KEY, false);
if (acceptPublicKey || kid == null) {
JsonWebKey jwk = jwkSet.getKey(inHeaderKid);
if (jwk != null && (acceptPublicKey || isKeyMarkedFor(jwk, keyOper))) {
return jwk;
}
}
}
if (kid != null) {
return jwkSet.getKey(kid);
Expand All @@ -314,6 +323,22 @@ public static JsonWebKey loadJsonWebKey(Message m, Properties props, KeyOperatio
return null;
}

private static boolean isKeyMarkedFor(JsonWebKey jwk, KeyOperation keyOper) {
if (keyOper == null) {
return false;
}
List<KeyOperation> ops = jwk.getKeyOperation();
if (ops != null) {
return ops.contains(keyOper);
}
PublicKeyUse use = jwk.getPublicKeyUse();
if (use == null) {
return false;
}
boolean sigOper = keyOper == KeyOperation.SIGN || keyOper == KeyOperation.VERIFY;
return sigOper == (use == PublicKeyUse.SIGN);
}

public static List<JsonWebKey> loadJsonWebKeys(Message m,
Properties props,
KeyOperation keyOper) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,8 @@ public static JwsSignatureVerifier loadSignatureVerifier(Message m,
JwsHeaders inHeaders) {
JwsSignatureVerifier theVerifier = null;
String inHeaderKid = null;
boolean jwkStore =
JoseConstants.HEADER_JSON_WEB_KEY.equals(props.get(JoseConstants.RSSEC_KEY_STORE_TYPE));
if (inHeaders != null) {
inHeaderKid = inHeaders.getKeyId();
//TODO: optionally validate inHeaders.getAlgorithm against a property in props
Expand All @@ -413,6 +415,11 @@ public static JwsSignatureVerifier loadSignatureVerifier(Message m,
}
return getSignatureVerifier(publicJwk,
inHeaders.getSignatureAlgorithm());
} else if (jwkStore) {
// The x5c, x5t and x5t#S256 headers are resolved against a Java KeyStore,
// which can not be loaded when the configured store type is "jwk".
// Fall through to loading the verification key from the JWK set below.
LOG.fine("Ignoring X.509 headers as the configured key store type is jwk");
} else if (inHeaders.getHeader(JoseConstants.HEADER_X509_CHAIN) != null) {
List<X509Certificate> chain = KeyManagementUtils.toX509CertificateChain(inHeaders.getX509Chain());
KeyManagementUtils.validateCertificateChain(props, chain);
Expand All @@ -439,7 +446,7 @@ public static JwsSignatureVerifier loadSignatureVerifier(Message m,
}
}

if (JoseConstants.HEADER_JSON_WEB_KEY.equals(props.get(JoseConstants.RSSEC_KEY_STORE_TYPE))) {
if (jwkStore) {
JsonWebKey jwk = JwkUtils.loadJsonWebKey(m, props, KeyOperation.VERIFY, inHeaderKid);
if (jwk != null) {
SignatureAlgorithm signatureAlgo = getSignatureAlgorithm(m, props,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
*/
package org.apache.cxf.rs.security.jose.jws;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPublicKey;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;

Expand All @@ -32,7 +36,10 @@
import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm;
import org.apache.cxf.rs.security.jose.jwk.JsonWebKey;
import org.apache.cxf.rs.security.jose.jwk.JsonWebKeys;
import org.apache.cxf.rs.security.jose.jwk.JwkUtils;
import org.apache.cxf.rs.security.jose.jwk.KeyOperation;
import org.apache.cxf.rs.security.jose.jwk.KeyType;
import org.apache.cxf.rs.security.jose.jwk.PublicKeyUse;

import org.junit.Test;

Expand Down Expand Up @@ -86,6 +93,22 @@ public void testLoadSignatureVerifierFromJKS() throws Exception {
assertNotNull(jws);
}
@Test
public void testLoadSignatureVerifierFromJwkStoreWithX509Thumbprint() throws Exception {
Properties p = new Properties();
p.put(JoseConstants.RSSEC_KEY_STORE_TYPE, "jwk");
p.put(JoseConstants.RSSEC_KEY_STORE_FILE, "jwk/pubKeys.jwks");
p.put(JoseConstants.RSSEC_KEY_STORE_ALIAS, "rsas001");
p.put(JoseConstants.RSSEC_SIGNATURE_ALGORITHM, "RS256");

JwsHeaders headers = new JwsHeaders(SignatureAlgorithm.RS256);
headers.setX509Thumbprint("dGh1bWJwcmludA");
assertNotNull(JwsUtils.loadSignatureVerifier(createMessage(), p, headers));

headers = new JwsHeaders(SignatureAlgorithm.RS256);
headers.setX509ThumbprintSHA256("dGh1bWJwcmludA");
assertNotNull(JwsUtils.loadSignatureVerifier(createMessage(), p, headers));
}
@Test
public void testLoadSignatureVerifierFromProperties() throws Exception {
JwsSignatureVerifier jws = JwsUtils.loadSignatureVerifier("classpath:/jws/signature.properties", null);
assertEquals(SignatureAlgorithm.NONE, jws.getAlgorithm());
Expand Down Expand Up @@ -132,6 +155,95 @@ public void testLoadVerificationKeyWithCert() throws Exception {
assertEquals(2, chain.size());
}

@Test
public void testLoadSignatureVerifierFromJwkStoreUsesHeaderKeyId() throws Exception {
KeyPair oldKey = createRsaKeyPair();
KeyPair newKey = createRsaKeyPair();
KeyPair encKey = createRsaKeyPair();
KeyPair opsKey = createRsaKeyPair();
KeyPair unmarkedKey = createRsaKeyPair();
JsonWebKey opsJwk = createPublicJwk(opsKey, "key-ops", null);
opsJwk.setKeyOperation(Arrays.asList(KeyOperation.VERIFY));
JsonWebKeys jwks = new JsonWebKeys(Arrays.asList(
createPublicJwk(oldKey, "key-old", PublicKeyUse.SIGN),
createPublicJwk(newKey, "key-new", PublicKeyUse.SIGN),
createPublicJwk(encKey, "key-enc", PublicKeyUse.ENCRYPT),
opsJwk,
createPublicJwk(unmarkedKey, "key-unmarked", null)));

Properties p = new Properties();
p.put(JoseConstants.RSSEC_KEY_STORE_TYPE, "jwk");
p.put(JoseConstants.RSSEC_KEY_STORE_JWKSET, JwkUtils.jwkSetToJson(jwks));
p.put(JoseConstants.RSSEC_SIGNATURE_ALGORITHM, "RS256");

// Without an alias, the key id in the headers selects the key, so a rotated key is picked up
assertTrue(verifyWithLoadedVerifier(p, sign(oldKey, "key-old"), false));
assertTrue(verifyWithLoadedVerifier(p, sign(newKey, "key-new"), false));
assertTrue(verifyWithLoadedVerifier(p, sign(opsKey, "key-ops"), false));
// Only a key explicitly marked for signatures is selected by the key id, otherwise
// the single key marked for verification ("key-ops") is used
assertFalse(verifyWithLoadedVerifier(p, sign(encKey, "key-enc"), false));
assertFalse(verifyWithLoadedVerifier(p, sign(unmarkedKey, "key-unmarked"), false));
}

@Test
public void testLoadSignatureVerifierFromJwkStoreAliasPinsKey() throws Exception {
KeyPair oldKey = createRsaKeyPair();
KeyPair newKey = createRsaKeyPair();
KeyPair unmarkedKey = createRsaKeyPair();
JsonWebKeys jwks = new JsonWebKeys(Arrays.asList(
createPublicJwk(oldKey, "key-old", PublicKeyUse.SIGN),
createPublicJwk(newKey, "key-new", PublicKeyUse.SIGN),
createPublicJwk(unmarkedKey, "key-unmarked", null)));

Properties p = new Properties();
p.put(JoseConstants.RSSEC_KEY_STORE_TYPE, "jwk");
p.put(JoseConstants.RSSEC_KEY_STORE_JWKSET, JwkUtils.jwkSetToJson(jwks));
p.put(JoseConstants.RSSEC_KEY_STORE_ALIAS, "key-old");
p.put(JoseConstants.RSSEC_SIGNATURE_ALGORITHM, "RS256");

// A configured alias pins the key, whatever the key id in the headers
assertTrue(verifyWithLoadedVerifier(p, sign(oldKey, null), false));
assertTrue(verifyWithLoadedVerifier(p, sign(oldKey, "key-unknown"), false));
assertFalse(verifyWithLoadedVerifier(p, sign(newKey, "key-new"), false));
assertFalse(verifyWithLoadedVerifier(p, sign(unmarkedKey, "key-unmarked"), false));
// unless accepting public keys has been explicitly enabled, which selects any key by key id as before
assertTrue(verifyWithLoadedVerifier(p, sign(newKey, "key-new"), true));
assertTrue(verifyWithLoadedVerifier(p, sign(unmarkedKey, "key-unmarked"), true));
}

private static KeyPair createRsaKeyPair() throws Exception {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
return kpg.generateKeyPair();
}

private static JsonWebKey createPublicJwk(KeyPair keyPair, String kid, PublicKeyUse use) {
JsonWebKey jwk = JwkUtils.fromRSAPublicKey((RSAPublicKey)keyPair.getPublic(), "RS256", kid);
if (use != null) {
jwk.setPublicKeyUse(use);
}
return jwk;
}

private static String sign(KeyPair keyPair, String kid) {
JwsHeaders headers = new JwsHeaders(SignatureAlgorithm.RS256);
if (kid != null) {
headers.setKeyId(kid);
}
JwsCompactProducer producer = new JwsCompactProducer(headers, "payload");
return producer.signWith(
JwsUtils.getPrivateKeySignatureProvider(keyPair.getPrivate(), SignatureAlgorithm.RS256));
}

private static boolean verifyWithLoadedVerifier(Properties props, String jws, boolean acceptPublicKey) {
JwsCompactConsumer consumer = new JwsCompactConsumer(jws);
Message m = createMessage();
m.put(JoseConstants.RSSEC_ACCEPT_PUBLIC_KEY, acceptPublicKey);
JwsSignatureVerifier verifier = JwsUtils.loadSignatureVerifier(m, props, consumer.getJwsHeaders());
return consumer.verifySignatureWith(verifier);
}

private static Message createMessage() {
Message m = new MessageImpl();
Exchange e = new ExchangeImpl();
Expand Down
Loading