diff --git a/implementation/jwt-build/src/main/java/io/smallrye/jwt/build/Jwt.java b/implementation/jwt-build/src/main/java/io/smallrye/jwt/build/Jwt.java
index 7a110d42..b8ebc2b3 100644
--- a/implementation/jwt-build/src/main/java/io/smallrye/jwt/build/Jwt.java
+++ b/implementation/jwt-build/src/main/java/io/smallrye/jwt/build/Jwt.java
@@ -1,5 +1,6 @@
package io.smallrye.jwt.build;
+import java.security.PublicKey;
import java.time.Instant;
import java.util.Collection;
import java.util.Map;
@@ -102,12 +103,16 @@ public static JwtClaimsBuilder claims(JsonWebToken jwt) {
/**
* Creates a new instance of {@link JwtClaimsBuilder} with a specified claim.
*
- * Simple claim value are converted to {@link String} unless it is an instance of {@link Boolean}, {@link Number} or
- * {@link Instant}. {@link Instant} values have their number of seconds from the epoch converted to long.
- *
+ * Simple claim value are converted to {@link String} unless it is an instance of {@link Boolean}, {@link Number},
+ * {@link Instant} or {@link PublicKey}.
+ *
+ * {@link Instant} values have their number of seconds from the epoch converted to long.
+ *
+ * {@link PublicKey} values are converted to JSON Web Key (JWK) representations.
+ *
* Array claims can be set as {@link Collection} or {@link JsonArray}, complex claims can be set as {@link Map} or
* {@link JsonObject}. The members of the array claims can be complex claims.
- *
+ *
* Types of the claims directly supported by this builder are enforced.
* The 'iss' (issuer), 'sub' (subject), 'upn', 'preferred_username' and 'jti' (token identifier) claims must be of
* {@link String} type.
@@ -126,12 +131,16 @@ public static JwtClaimsBuilder claim(Claims name, Object value) {
/**
* Creates a new instance of {@link JwtClaimsBuilder} with a specified claim.
*
- * Simple claim value are converted to {@link String} unless it is an instance of {@link Boolean}, {@link Number} or
- * {@link Instant}. {@link Instant} values have their number of seconds from the epoch converted to long.
- *
+ * Simple claim value are converted to {@link String} unless it is an instance of {@link Boolean}, {@link Number},
+ * {@link Instant} or {@linkplain PublicKey}.
+ *
+ * {@link Instant} values have their number of seconds from the epoch converted to long.
+ *
+ * {@link PublicKey} values are converted to JSON Web Key (JWK) representations.
+ *
* Array claims can be set as {@link Collection} or {@link JsonArray}, complex claims can be set as {@link Map} or
* {@link JsonObject}. The members of the array claims can be complex claims.
- *
+ *
* Types of the claims directly supported by this builder are enforced.
* The 'iss' (issuer), 'sub' (subject), 'upn', 'preferred_username' and 'jti' (token identifier) claims must be of
* {@link String} type.
diff --git a/implementation/jwt-build/src/main/java/io/smallrye/jwt/build/JwtClaimsBuilder.java b/implementation/jwt-build/src/main/java/io/smallrye/jwt/build/JwtClaimsBuilder.java
index 18326071..f5083edb 100644
--- a/implementation/jwt-build/src/main/java/io/smallrye/jwt/build/JwtClaimsBuilder.java
+++ b/implementation/jwt-build/src/main/java/io/smallrye/jwt/build/JwtClaimsBuilder.java
@@ -1,5 +1,6 @@
package io.smallrye.jwt.build;
+import java.security.PublicKey;
import java.time.Duration;
import java.time.Instant;
import java.util.Collection;
@@ -182,12 +183,16 @@ default JwtClaimsBuilder scope(String scope) {
/**
* Set a claim.
*
- * Simple claim value are converted to {@link String} unless it is an instance of {@link Boolean}, {@link Number} or
- * {@link Instant}. {@link Instant} values have their number of seconds from the epoch converted to long.
- *
- * Array claims can be set as {@link Collection} or {@link JsonArray} and complex claims can be set as {@link Map} or
+ * Simple claim value are converted to {@link String} unless it is an instance of {@link Boolean}, {@link Number},
+ * {@link Instant} or {@link PublicKey}.
+ *
+ * {@link Instant} values have their number of seconds from the epoch converted to long.
+ *
+ * {@link PublicKey} values are converted to JSON Web Key (JWK) representations.
+ *
+ * Array claims can be set as {@link Collection} or {@link JsonArray}, complex claims can be set as {@link Map} or
* {@link JsonObject}. The members of the array claims can be complex claims.
- *
+ *
* Types of claims directly supported by this builder are enforced.
* The 'iss' (issuer), 'sub' (subject), 'upn', 'preferred_username' and 'jti' (token identifier) claims must be of
* {@link String} type.
@@ -206,12 +211,16 @@ default JwtClaimsBuilder claim(Claims name, Object value) {
/**
* Set a claim.
*
- * Simple claim value are converted to {@link String} unless it is an instance of {@link Boolean}, {@link Number} or
- * {@link Instant}. {@link Instant} values have their number of seconds from the epoch converted to long.
- *
+ * Simple claim value are converted to {@link String} unless it is an instance of {@link Boolean}, {@link Number},
+ * {@link Instant} or {@link PublicKey}.
+ *
+ * {@link Instant} values have their number of seconds from the epoch converted to long.
+ *
+ * {@link PublicKey} values are converted to JSON Web Key (JWK) representations.
+ *
* Array claims can be set as {@link Collection} or {@link JsonArray}, complex claims can be set as {@link Map} or
* {@link JsonObject}. The members of the array claims can be complex claims.
- *
+ *
* Types of the claims directly supported by this builder are enforced.
* The 'iss' (issuer), 'sub' (subject), 'upn', 'preferred_username' and 'jti' (token identifier) claims must be of
* {@link String} type.
diff --git a/implementation/jwt-build/src/main/java/io/smallrye/jwt/build/JwtSignatureBuilder.java b/implementation/jwt-build/src/main/java/io/smallrye/jwt/build/JwtSignatureBuilder.java
index 86fca2c5..6b296153 100644
--- a/implementation/jwt-build/src/main/java/io/smallrye/jwt/build/JwtSignatureBuilder.java
+++ b/implementation/jwt-build/src/main/java/io/smallrye/jwt/build/JwtSignatureBuilder.java
@@ -1,5 +1,6 @@
package io.smallrye.jwt.build;
+import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.List;
@@ -100,6 +101,14 @@ default JwtSignatureBuilder chain(X509Certificate cert) {
*/
JwtSignatureBuilder chain(List chain);
+ /**
+ * Set JSON Web Key 'jwk' key.
+ *
+ * @param key the public key
+ * @return JwtSignatureBuilder
+ */
+ JwtSignatureBuilder jwk(PublicKey key);
+
/**
* Custom JWT signature header.
*
diff --git a/implementation/jwt-build/src/main/java/io/smallrye/jwt/build/impl/JwtClaimsBuilderImpl.java b/implementation/jwt-build/src/main/java/io/smallrye/jwt/build/impl/JwtClaimsBuilderImpl.java
index 768fe8e5..151f7903 100644
--- a/implementation/jwt-build/src/main/java/io/smallrye/jwt/build/impl/JwtClaimsBuilderImpl.java
+++ b/implementation/jwt-build/src/main/java/io/smallrye/jwt/build/impl/JwtClaimsBuilderImpl.java
@@ -1,6 +1,7 @@
package io.smallrye.jwt.build.impl;
import java.security.PrivateKey;
+import java.security.PublicKey;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.time.Instant;
@@ -23,10 +24,13 @@
import org.eclipse.microprofile.jwt.Claims;
import org.jose4j.base64url.Base64;
+import org.jose4j.jwk.JsonWebKey.OutputControlLevel;
+import org.jose4j.jwk.PublicJsonWebKey;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.NumericDate;
import org.jose4j.jwx.HeaderParameterNames;
import org.jose4j.keys.X509Util;
+import org.jose4j.lang.JoseException;
import io.smallrye.jwt.algorithm.SignatureAlgorithm;
import io.smallrye.jwt.build.JwtClaimsBuilder;
@@ -259,6 +263,15 @@ public JwtSignatureBuilder chain(List chain) {
return this;
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public JwtSignatureBuilder jwk(PublicKey key) {
+ headers.put(HeaderParameterNames.JWK, convertPublicKeyToJwk(key));
+ return this;
+ }
+
/**
* {@inheritDoc}
*/
@@ -323,6 +336,10 @@ private static Object prepareValue(Object value) {
return ((Instant) value).getEpochSecond();
}
+ if (value instanceof PublicKey) {
+ return convertPublicKeyToJwk((PublicKey) value);
+ }
+
return value.toString();
}
@@ -404,6 +421,14 @@ public Object verify(String name, Object value) {
}
}
+ static Map convertPublicKeyToJwk(PublicKey key) {
+ try {
+ return PublicJsonWebKey.Factory.newPublicJwk(key).toParams(OutputControlLevel.PUBLIC_ONLY);
+ } catch (JoseException ex) {
+ throw ImplMessages.msg.signatureException(ex);
+ }
+ }
+
@Override
public JwtClaimsBuilder remove(String name) {
claims.unsetClaim(name);
diff --git a/implementation/jwt-build/src/test/java/io/smallrye/jwt/build/JwtSignTest.java b/implementation/jwt-build/src/test/java/io/smallrye/jwt/build/JwtSignTest.java
index d9af4e0b..8fb8c0ff 100644
--- a/implementation/jwt-build/src/test/java/io/smallrye/jwt/build/JwtSignTest.java
+++ b/implementation/jwt-build/src/test/java/io/smallrye/jwt/build/JwtSignTest.java
@@ -593,19 +593,34 @@ private void doTestSignedExistingClaims(String jwt) throws Exception {
@Test
void signClaimsEllipticCurve() throws Exception {
- EllipticCurveJsonWebKey jwk = createECJwk();
+ EllipticCurveJsonWebKey ecJwk = createECJwk();
String jwt = Jwt.claims()
.claim("customClaim", "custom-value")
- .sign(jwk.getEcPrivateKey());
+ .claim("evidence", ecJwk.getECPublicKey())
+ .jws().jwk(ecJwk.getECPublicKey())
+ .sign(ecJwk.getEcPrivateKey());
- JsonWebSignature jws = getVerifiedJws(jwt, jwk.getECPublicKey());
+ JsonWebSignature jws = getVerifiedJws(jwt, ecJwk.getECPublicKey());
JwtClaims claims = JwtClaims.parse(jws.getPayload());
+ assertEquals(5, claims.getClaimsMap().size());
- assertEquals(4, claims.getClaimsMap().size());
- checkDefaultClaimsAndHeaders(getJwsHeaders(jwt, 2), claims, "ES256", 300);
+ Map headers = getJwsHeaders(jwt, 3);
+ checkDefaultClaimsAndHeaders(headers, claims, "ES256", 300);
assertEquals("custom-value", claims.getClaimValue("customClaim"));
+
+ @SuppressWarnings("unchecked")
+ Map jwk = (Map) headers.get("jwk");
+ assertEquals(4, jwk.size());
+ assertEquals("EC", jwk.get("kty"));
+ assertEquals("P-256", jwk.get("crv"));
+ assertNotNull(jwk.get("x"));
+ assertNotNull(jwk.get("y"));
+
+ @SuppressWarnings("unchecked")
+ Map evidence = (Map) claims.getClaimValue("evidence");
+ assertEquals(evidence, jwk);
}
@Test
@@ -617,17 +632,25 @@ void signClaimsEd25519() throws Exception {
String jwt = Jwt.claims()
.claim("customClaim", "custom-value")
+ .jws().jwk(keyPairEd25519.getPublic())
.sign(keyPairEd25519.getPrivate());
JsonWebSignature jws = getVerifiedJws(jwt, keyPairEd25519.getPublic());
JwtClaims claims = JwtClaims.parse(jws.getPayload());
assertEquals(4, claims.getClaimsMap().size());
- Map headers = getJwsHeaders(jwt, 2);
+ Map headers = getJwsHeaders(jwt, 3);
checkDefaultClaimsAndHeaders(headers, claims, "EdDSA", 300);
assertEquals("custom-value", claims.getClaimValue("customClaim"));
+ @SuppressWarnings("unchecked")
+ Map jwk = (Map) headers.get("jwk");
+ assertEquals(3, jwk.size());
+ assertEquals("OKP", jwk.get("kty"));
+ assertEquals("Ed25519", jwk.get("crv"));
+ assertNotNull(jwk.get("x"));
+
JwtConsumerBuilder builder = new JwtConsumerBuilder();
builder.setVerificationKey(keyPairEd448.getPublic());
builder.setJwsAlgorithmConstraints(
@@ -733,19 +756,28 @@ void signWithKeyStore() throws Exception {
configSource.setUseKeyStore(true);
configSource.setSigningKeyLocation("/keystore.p12");
try {
+ KeyStore keyStore = KeyUtils.loadKeyStore("keystore.p12", "password", Optional.of("PKCS12"), Optional.empty());
+ PublicKey verificationKey = keyStore.getCertificate("server").getPublicKey();
+
String jwt = Jwt.claims()
.claim("customClaim", "custom-value")
+ .jws().jwk(verificationKey)
.sign();
- KeyStore keyStore = KeyUtils.loadKeyStore("keystore.p12", "password", Optional.of("PKCS12"), Optional.empty());
- PublicKey verificationKey = keyStore.getCertificate("server").getPublicKey();
-
JsonWebSignature jws = getVerifiedJws(jwt, verificationKey);
JwtClaims claims = JwtClaims.parse(jws.getPayload());
assertEquals(4, claims.getClaimsMap().size());
- checkDefaultClaimsAndHeaders(getJwsHeaders(jwt, 2), claims, "RS256", 300);
+ Map headers = getJwsHeaders(jwt, 3);
+ checkDefaultClaimsAndHeaders(headers, claims, "RS256", 300);
assertEquals("custom-value", claims.getClaimValue("customClaim"));
+
+ @SuppressWarnings("unchecked")
+ Map jwk = (Map) headers.get("jwk");
+ assertEquals(3, jwk.size());
+ assertEquals("RSA", jwk.get("kty"));
+ assertNotNull(jwk.get("n"));
+ assertNotNull(jwk.get("e"));
} finally {
configSource.setUseKeyStore(false);
configSource.setSigningKeyLocation("/privateKey.pem");