How to use Ed25519 / Curve25519 with SpringBoot for JWK, JWE, JWS and JWT security?

174 Views Asked by At

tl;dr

  • Resource needed which ensures and shows how to use the elliptic curve called Ed25519 / Curve25519 along Spring-Boot authorization server.

Objectives

Setup

  • Java : 21
  • Spring Boot : v3.2.x
  • spring-security-oauth2-authorization-server : 1.2.x

Pre-Research

  • Spring-Boot uses the com.nimbusds.jose library internally (Since ~2020, ~v5.1).
    • Likewise questions have been asked 'before 2018' (afaik)
    • RSA is supported
    • Elliptic Curves are supported (generally)
      • Use of 'Ed25519 / Curve25519' - unknown

Still there are multiple classes that offer EC-functionality around 'Ed25519 / Curve25519'

Key generation, compatible to java.security.KeyPair

    static KeyPair generateEcKeyPairUsingEd25519() {
        KeyPair keyPair;
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("Ed25519");
            keyPair = keyPairGenerator.generateKeyPair();
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException("JVM does not support Ed25519 algorithm", e);
        }
        return keyPair;
    }

Interfaces are provided as well

        import java.security.interfaces.EdECPrivateKey;
        import java.security.interfaces.EdECPublicKey;

        KeyPair ecKeyPair = generateEcKeyPairUsingEd25519()

        EdECPublicKey edEcPublicKey = (EdECPublicKey) ecKeyPair.getPublic();
        EdECPrivateKey edEcPrivateKey = (EdECPrivateKey) ecKeyPair.getPrivate();

Re-assuring

  • Is this already too deep into the rabbit hole?
  • Is Spring-Boot already using Ed25519 for JWK, JWE, JWS and JWT without me noticing?
  • Is there a suitable Ed25519 / Curve25519 reference imlementation?

Problem

Spring Boot : v3.2.x

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
    <version>${spring-boot.version}</version>
</dependency>

To my knowledge the used com.nimbusds.jose.* supports JWK, JWE, JWS and JWT with RSA and for EC genearlly.

Keypairs as seen above can be generated, but they can - to my knowledge - not be used in the example below.

Example

    @Bean
    public JwtDecoder jwtDecoder() {
        KeyPair keyPair = this.getKeyPair();
        RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
        return NimbusJwtDecoder.withPublicKey(rsaPublicKey).build();
    }

    @Bean
    JwtEncoder jwtEncoder() {
        KeyPair keyPair = this.getKeyPair();
        RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();

        JWK jwk = new RSAKey.Builder(rsaPublicKey)
                .privateKey(rsaPrivateKey).build();

        JWKSource<SecurityContext> jwks = new ImmutableJWKSet<>(new JWKSet(jwk));
        return new NimbusJwtEncoder(jwks);
    }

Finally

  • How to use Ed25519 / Curve25519 with SpringBoot for JWK, JWE, JWS and JWT security?
1

There are 1 best solutions below

3
Ricardo Gellman On

You could apply the integration by adding a configuration for Ed25519

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.EdECPublicKey;
import java.security.interfaces.EdECPrivateKey;
import java.security.spec.EdECPublicKeySpec;
import java.security.spec.EdECPrivateKeySpec;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.JWKSource;
import org.springframework.security.oauth2.jwt.JWKSet;
import org.springframework.security.oauth2.jwt.JWK;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.oauth2.jose.jwk.JWKSource;
import org.springframework.security.oauth2.jose.jwk.JWKSet;
import org.springframework.security.oauth2.jose.jwk.JWK;
import org.springframework.security.core.context.SecurityContext;
import java.security.spec.InvalidKeySpecException;

@Configuration
public class JwtConfig {

    @Bean
    public JwtDecoder jwtDecoder() {
        KeyPair keyPair = Ed25519KeyPairGenerator.generateEcKeyPairUsingEd25519();
        EdECPublicKey edEcPublicKey = (EdECPublicKey) keyPair.getPublic();
        return NimbusJwtDecoder.withPublicKey(edEcPublicKey).build();
    }

    @Bean
    public JwtEncoder jwtEncoder() {
        KeyPair keyPair = Ed25519KeyPairGenerator.generateEcKeyPairUsingEd25519();
        EdECPublicKey edEcPublicKey = (EdECPublicKey) keyPair.getPublic();
        EdECPrivateKey edEcPrivateKey = (EdECPrivateKey) keyPair.getPrivate();

        try {
            EdECPublicKeySpec publicKeySpec = new EdECPublicKeySpec(edEcPublicKey.getEncoded());
            EdECPrivateKeySpec privateKeySpec = new EdECPrivateKeySpec(edEcPrivateKey.getS(), publicKeySpec);
            
            JWK jwk = new JWK.Builder(publicKeySpec).privateKey(privateKeySpec).build();
            JWKSource<SecurityContext> jwks = new ImmutableJWKSet<>(new JWKSet(jwk));
            
            return new NimbusJwtEncoder(jwks);
        } catch (InvalidKeySpecException e) {
            throw new IllegalStateException("Error while creating EdDSA key spec", e);
        }
    }
}

And generate KeyPairs and integrate with beans. I remember there was a risk with NullPointerExceptions, but I could not find the resource now.

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;

public class Ed25519KeyPairGenerator {
    public static KeyPair generateEcKeyPairUsingEd25519() {
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("Ed25519");
            return keyPairGenerator.generateKeyPair();
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException("JVM does not support Ed25519 algorithm", e);
        }
    }
}