Skip to content
Draft
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 @@ -22,6 +22,7 @@
* the JWS Protected Header and JWS Payload.
*
* @author Joe Grandja
* @author Andrey Litvitski
* @since 5.0
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7518">JSON Web Algorithms
* (JWA)</a>
Expand Down Expand Up @@ -93,6 +94,21 @@ public final class JwsAlgorithms {
*/
public static final String PS512 = "PS512";

/**
* EdDSA signature algorithms (optional).
*/
public static final String EdDSA = "EdDSA";

/**
* EdDSA signature algorithms using Ed448 curve (optional).
*/
public static final String ED448 = "Ed448";

/**
* EdDSA signature algorithms using Ed25519 curve (optional).
*/
public static final String ED25519 = "Ed25519";

private JwsAlgorithms() {
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,22 @@ public enum SignatureAlgorithm implements JwsAlgorithm {
/**
* RSASSA-PSS using SHA-512 and MGF1 with SHA-512 (Optional)
*/
PS512(JwsAlgorithms.PS512);
PS512(JwsAlgorithms.PS512),

/**
* EdDSA signature algorithms (optional).
*/
EdDSA(JwsAlgorithms.EdDSA),

/**
* EdDSA signature algorithms using Ed448 curve (optional).
*/
ED448(JwsAlgorithms.ED448),
Comment on lines +89 to +92
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should support this too.


/**
* EdDSA signature algorithms using Ed25519 curve (optional).
*/
ED25519(JwsAlgorithms.ED25519);

private final String name;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import com.nimbusds.jose.jwk.ECKey;
import com.nimbusds.jose.jwk.KeyOperation;
import com.nimbusds.jose.jwk.KeyUse;
import com.nimbusds.jose.jwk.OctetKeyPair;
import com.nimbusds.jose.jwk.OctetSequenceKey;
import com.nimbusds.jose.jwk.RSAKey;

Expand Down Expand Up @@ -84,4 +85,15 @@ static RSAKey.Builder signingWithRsa(RSAPublicKey pub, RSAPrivateKey key) throws
.notBeforeTime(issued);
}

static OctetKeyPair.Builder signingWithOkp(OctetKeyPair octetKeyPair) throws JOSEException {
Date issued = new Date();
return new OctetKeyPair.Builder(octetKeyPair.getCurve(), octetKeyPair.getX()).d(octetKeyPair.getD())
.keyOperations(Set.of(KeyOperation.SIGN))
.keyUse(KeyUse.SIGNATURE)
.algorithm(JWSAlgorithm.EdDSA)
.keyIDFromThumbprint()
.issueTime(issued)
.notBeforeTime(issued);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.KeyType;
import com.nimbusds.jose.jwk.KeyUse;
import com.nimbusds.jose.jwk.OctetKeyPair;
import com.nimbusds.jose.jwk.OctetSequenceKey;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
Expand Down Expand Up @@ -84,6 +85,7 @@
* @author Joe Grandja
* @author Josh Cummings
* @author Suraj Bhadrike
* @author Andrey Litvitski
* @since 5.6
* @see JwtEncoder
* @see com.nimbusds.jose.jwk.source.JWKSource
Expand Down Expand Up @@ -250,6 +252,16 @@ else if (JWSAlgorithm.Family.HMAC_SHA.contains(jwsAlgorithm)) {
.build();
// @formatter:on
}
else if (JWSAlgorithm.Family.ED.contains(jwsAlgorithm)) {
// @formatter:off
return new JWKMatcher.Builder()
.keyType(KeyType.forAlgorithm(jwsAlgorithm))
.keyID(headers.getKeyId())
.keyUses(KeyUse.SIGNATURE, null)
.algorithms(jwsAlgorithm, null)
.build();
// @formatter:on
}

return null;
}
Expand Down Expand Up @@ -445,6 +457,16 @@ public static EcKeyPairJwtEncoderBuilder withKeyPair(ECPublicKey publicKey, ECPr
return new EcKeyPairJwtEncoderBuilder(publicKey, privateKey);
}

/**
* Creates a builder for constructing a {@link NimbusJwtEncoder} using the provided
* @param keyPair the {@link OctetKeyPair} to use for signing JWTs
* @return a {@link OctetKeyPairJwtEncoderBuilder}
* @since 7.0
*/
public static OctetKeyPairJwtEncoderBuilder withKeyPair(OctetKeyPair keyPair) {
return new OctetKeyPairJwtEncoderBuilder(keyPair);
}

/**
* Creates a builder for constructing a {@link NimbusJwtEncoder} using the provided
* @param secretKey
Expand Down Expand Up @@ -625,4 +647,46 @@ public NimbusJwtEncoder build() {

}

/**
* A builder for creating {@link NimbusJwtEncoder} instances configured with a
* {@link OctetKeyPair}.
* <p>
* This builder is used to create a {@link NimbusJwtEncoder}
*
* @since 7.0
*/
public static final class OctetKeyPairJwtEncoderBuilder {

private static final ThrowingFunction<OctetKeyPair, OctetKeyPair.Builder> defaultJwk = JWKS::signingWithOkp;

private final OctetKeyPair.Builder builder;

private OctetKeyPairJwtEncoderBuilder(OctetKeyPair keyPair) {
Assert.notNull(keyPair, "keyPair cannot be null");
Assert.isTrue(keyPair.isPrivate(), "keyPair must contain a private key");
this.builder = defaultJwk.apply(keyPair);
}

/**
* Post-process the {@link JWK} using the given {@link Consumer}. For example, you
* may use this to override the default {@code jwk}
* @param jwkPostProcessor the post-processor to use
* @return this builder instance for method chaining
*/
public OctetKeyPairJwtEncoderBuilder jwkPostProcessor(Consumer<OctetKeyPair.Builder> jwkPostProcessor) {
Assert.notNull(jwkPostProcessor, "jwkPostProcessor cannot be null");
jwkPostProcessor.accept(this.builder);
return this;
}

/**
* Builds the {@link NimbusJwtEncoder} instance.
* @return the configured {@link NimbusJwtEncoder}
*/
public NimbusJwtEncoder build() {
return new NimbusJwtEncoder(this.builder.build());
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,13 @@

import com.nimbusds.jose.jwk.Curve;
import com.nimbusds.jose.jwk.ECKey;
import com.nimbusds.jose.jwk.OctetKeyPair;
import com.nimbusds.jose.jwk.OctetSequenceKey;
import com.nimbusds.jose.jwk.RSAKey;

/**
* @author Joe Grandja
* @author Andrey Litvitski
*/
public final class TestJwks {

Expand Down Expand Up @@ -70,6 +72,13 @@ public final class TestJwks {
).build();
// @formatter:on

// @formatter:off
public static final OctetKeyPair DEFAULT_OKP_JWK =
jwk(
TestKeys.DEFAULT_OKP_KEY_PAIR
).build();
// @formatter:on

private TestJwks() {
}

Expand Down Expand Up @@ -111,4 +120,12 @@ public static OctetSequenceKey.Builder jwk(SecretKey secretKey) {
// @formatter:on
}

public static OctetKeyPair.Builder jwk(OctetKeyPair octetKeyPair) {
// @formatter:off
return new OctetKeyPair.Builder(octetKeyPair.getCurve(), octetKeyPair.getX())
.d(octetKeyPair.getD())
.keyID("okp-jwk-kid");
// @formatter:on
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,14 @@
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.jwk.Curve;
import com.nimbusds.jose.jwk.OctetKeyPair;
import com.nimbusds.jose.jwk.gen.OctetKeyPairGenerator;

/**
* @author Joe Grandja
* @author Andrey Litvitski
* @since 5.2
*/
public final class TestKeys {
Expand Down Expand Up @@ -120,6 +126,8 @@ public final class TestKeys {

public static final KeyPair DEFAULT_EC_KEY_PAIR = generateEcKeyPair();

public static final OctetKeyPair DEFAULT_OKP_KEY_PAIR = generateOkpKeyPair();

static KeyPair generateEcKeyPair() {
EllipticCurve ellipticCurve = new EllipticCurve(
new ECFieldFp(new BigInteger(
Expand All @@ -144,6 +152,15 @@ static KeyPair generateEcKeyPair() {
return keyPair;
}

static OctetKeyPair generateOkpKeyPair() {
try {
return new OctetKeyPairGenerator(Curve.Ed25519).generate();
}
catch (JOSEException ex) {
throw new IllegalStateException(ex);
}
}

private TestKeys() {
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
* Tests for {@link SignatureAlgorithm}
*
* @author Joe Grandja
* @author Andrey Litvitski
* @since 5.2
*/
public class SignatureAlgorithmTests {
Expand All @@ -39,6 +40,9 @@ public void fromWhenAlgorithmValidThenResolves() {
assertThat(SignatureAlgorithm.from(JwsAlgorithms.PS256)).isEqualTo(SignatureAlgorithm.PS256);
assertThat(SignatureAlgorithm.from(JwsAlgorithms.PS384)).isEqualTo(SignatureAlgorithm.PS384);
assertThat(SignatureAlgorithm.from(JwsAlgorithms.PS512)).isEqualTo(SignatureAlgorithm.PS512);
assertThat(SignatureAlgorithm.from(JwsAlgorithms.EdDSA)).isEqualTo(SignatureAlgorithm.EdDSA);
assertThat(SignatureAlgorithm.from(JwsAlgorithms.ED448)).isEqualTo(SignatureAlgorithm.ED448);
assertThat(SignatureAlgorithm.from(JwsAlgorithms.ED25519)).isEqualTo(SignatureAlgorithm.ED25519);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,11 @@
import com.nimbusds.jose.jwk.JWKSelector;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.KeyUse;
import com.nimbusds.jose.jwk.OctetKeyPair;
import com.nimbusds.jose.jwk.OctetSequenceKey;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.gen.ECKeyGenerator;
import com.nimbusds.jose.jwk.gen.OctetKeyPairGenerator;
import com.nimbusds.jose.jwk.gen.RSAKeyGenerator;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
Expand Down Expand Up @@ -72,6 +74,7 @@
* Tests for {@link NimbusJwtEncoder}.
*
* @author Joe Grandja
* @author Andrey Litvitski
*/
public class NimbusJwtEncoderTests {

Expand Down Expand Up @@ -109,6 +112,25 @@ public void encodeWhenClaimsNullThenThrowIllegalArgumentException() {
.withMessage("claims cannot be null");
}

@Test
void keyPairBuilderWithOkpWhenPublicKeyOnlyThenThrowIllegalArgumentException() throws JOSEException {
OctetKeyPair key = new OctetKeyPairGenerator(Curve.Ed25519).generate();

assertThatIllegalArgumentException().isThrownBy(() -> NimbusJwtEncoder.withKeyPair(key.toPublicJWK()))
.withMessage("keyPair must contain a private key");
}

@Test
void encodeWhenEdDsaAlgorithmThenSuccess() throws JOSEException {
OctetKeyPair key = new OctetKeyPairGenerator(Curve.Ed25519).generate();
this.jwkList.add(key);
JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.EdDSA).build();
JwtClaimsSet claims = buildClaims();
Jwt jwt = this.jwtEncoder.encode(JwtEncoderParameters.from(jwsHeader, claims));
assertJwt(jwt);
assertThat(jwt.getHeaders()).containsEntry(JoseHeaderNames.ALG, SignatureAlgorithm.EdDSA);
}

@Test
public void encodeWhenJwkSelectFailedThenThrowJwtEncodingException() throws Exception {
this.jwkSource = mock(JWKSource.class);
Expand Down Expand Up @@ -389,6 +411,17 @@ void keyPairBuilderWithSecretKeyDefaultAlgorithm() {
assertThat(jwt.getHeaders()).containsKey(JoseHeaderNames.KID);
}

@Test
void keyPairBuilderWithOkpDefaultAlgorithm() throws JOSEException {
OctetKeyPair key = new OctetKeyPairGenerator(Curve.Ed25519).generate();
NimbusJwtEncoder jwtEncoder = NimbusJwtEncoder.withKeyPair(key).build();
JwtClaimsSet claims = buildClaims();
Jwt jwt = jwtEncoder.encode(JwtEncoderParameters.from(claims));
assertJwt(jwt);
assertThat(jwt.getHeaders()).containsEntry(JoseHeaderNames.ALG, SignatureAlgorithm.EdDSA);
assertThat(jwt.getHeaders()).containsKey(JoseHeaderNames.KID);
}

// With custom algorithm
@Test
void keyPairBuilderWithRsaWithAlgorithm() throws JOSEException {
Expand Down Expand Up @@ -490,6 +523,20 @@ void keyPairBuilderWithSecretKeyWithAlgorithmAndJwkSource() {
assertThat(jwt.getHeaders()).containsEntry(JoseHeaderNames.KID, keyId);
}

@Test
void keyPairBuilderWithOkpAlgorithmAndJwkSource() throws JOSEException {
OctetKeyPair key = new OctetKeyPairGenerator(Curve.Ed25519).generate();
String keyId = UUID.randomUUID().toString();
NimbusJwtEncoder jwtEncoder = NimbusJwtEncoder.withKeyPair(key)
.jwkPostProcessor((builder) -> builder.keyID(keyId))
.build();
JwtClaimsSet claims = buildClaims();
Jwt jwt = jwtEncoder.encode(JwtEncoderParameters.from(claims));
assertJwt(jwt);
assertThat(jwt.getHeaders()).containsEntry(JoseHeaderNames.ALG, SignatureAlgorithm.EdDSA);
assertThat(jwt.getHeaders()).containsEntry(JoseHeaderNames.KID, keyId);
}

private JwtClaimsSet buildClaims() {
Instant now = Instant.now();
return JwtClaimsSet.builder()
Expand Down Expand Up @@ -558,8 +605,11 @@ private void init() {
OctetSequenceKey secretJwk = TestJwks.jwk(TestKeys.DEFAULT_SECRET_KEY)
.keyID("secret-jwk-" + this.keyId++)
.build();
OctetKeyPair okpJwk = TestJwks.jwk(TestKeys.DEFAULT_OKP_KEY_PAIR)
.keyID("okp-jwk-" + this.keyId++)
.build();
// @formatter:on
this.jwkSet = new JWKSet(Arrays.asList(rsaJwk, ecJwk, secretJwk));
this.jwkSet = new JWKSet(Arrays.asList(rsaJwk, ecJwk, secretJwk, okpJwk));
}

private void rotate() {
Expand Down
Loading