This is an automated email from the ASF dual-hosted git repository.

mcgilman pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/main by this push:
     new 14b1776739 NIFI-13424 Switched to EdDSA for Application Bearer Tokens 
(#8986)
14b1776739 is described below

commit 14b1776739e5a1a6ca55854856341c51ce826762
Author: David Handermann <[email protected]>
AuthorDate: Fri Jun 21 07:49:45 2024 -0500

    NIFI-13424 Switched to EdDSA for Application Bearer Tokens (#8986)
    
    - Replaced PS512 algorithm based on RSASSA-PSS with EdDSA algorithm using 
Ed25519
    - Added Ed25519 Signer and Verifier implementations based on Java Signature 
processing
    
    This closes #8986
---
 .../src/main/asciidoc/administration-guide.adoc    |  4 +-
 .../configuration/JwtDecoderConfiguration.java     |  3 +
 .../nifi/web/security/jwt/key/Ed25519Signer.java   | 69 +++++++++++++++++++
 .../nifi/web/security/jwt/key/Ed25519Verifier.java | 79 ++++++++++++++++++++++
 .../security/jwt/key/Ed25519VerifierFactory.java   | 73 ++++++++++++++++++++
 .../jwt/key/command/KeyGenerationCommand.java      | 12 ++--
 .../jwt/key/command/KeyGenerationCommandTest.java  |  4 +-
 .../provider/StandardBearerTokenProviderTest.java  | 16 ++---
 8 files changed, 238 insertions(+), 22 deletions(-)

diff --git a/nifi-docs/src/main/asciidoc/administration-guide.adoc 
b/nifi-docs/src/main/asciidoc/administration-guide.adoc
index 2b9b5b9b46..f8d1a8a469 100644
--- a/nifi-docs/src/main/asciidoc/administration-guide.adoc
+++ b/nifi-docs/src/main/asciidoc/administration-guide.adoc
@@ -641,8 +641,8 @@ SAML authentication enables the following REST API 
resources for integration wit
 NiFi uses JSON Web Tokens to provide authenticated access after the initial 
login process. Generated JSON Web Tokens include the authenticated user identity
 as well as the issuer and expiration from the configured Login Identity 
Provider.
 
-NiFi uses generated RSA Key Pairs with a key size of 4096 bits to support the 
`PS512` algorithm for JSON Web Signatures. The system stores RSA
-Public Keys using the configured local State Provider and retains the RSA 
Private Key in memory. This approach supports signature verification
+NiFi uses generated Ed25519 Key Pairs to support the `EdDSA` algorithm for 
JSON Web Signatures. The system stores Ed25519
+Public Keys using the configured local State Provider and retains the Private 
Key in memory. This approach supports signature verification
 for the expiration configured in the Login Identity Provider without 
persisting the private key.
 
 JSON Web Token support includes revocation on logout using JSON Web Token 
Identifiers. The system denies access for expired tokens based on the
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/JwtDecoderConfiguration.java
 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/JwtDecoderConfiguration.java
index 8da3e70f7b..597b409637 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/JwtDecoderConfiguration.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/JwtDecoderConfiguration.java
@@ -27,6 +27,7 @@ import org.apache.nifi.components.state.StateManagerProvider;
 import org.apache.nifi.util.NiFiProperties;
 import org.apache.nifi.web.security.jwt.converter.StandardIssuerJwtDecoder;
 import org.apache.nifi.web.security.jwt.jws.StandardJWSKeySelector;
+import org.apache.nifi.web.security.jwt.key.Ed25519VerifierFactory;
 import org.apache.nifi.web.security.jwt.key.StandardVerificationKeySelector;
 import 
org.apache.nifi.web.security.jwt.key.service.StandardVerificationKeyService;
 import org.apache.nifi.web.security.jwt.key.service.VerificationKeyService;
@@ -124,6 +125,8 @@ public class JwtDecoderConfiguration {
 
         final JWTClaimsSetVerifier<SecurityContext> claimsSetVerifier = new 
DefaultJWTClaimsVerifier<>(null, REQUIRED_CLAIMS);
         jwtProcessor.setJWTClaimsSetVerifier(claimsSetVerifier);
+
+        jwtProcessor.setJWSVerifierFactory(new Ed25519VerifierFactory());
         return jwtProcessor;
     }
 
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/key/Ed25519Signer.java
 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/key/Ed25519Signer.java
new file mode 100644
index 0000000000..787407fe40
--- /dev/null
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/key/Ed25519Signer.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.security.jwt.key;
+
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jose.JWSSigner;
+import com.nimbusds.jose.crypto.impl.EdDSAProvider;
+import com.nimbusds.jose.util.Base64URL;
+
+import java.security.GeneralSecurityException;
+import java.security.PrivateKey;
+import java.security.Signature;
+import java.util.Objects;
+
+/**
+ * Ed25519 implementation of JSON Web Security Signer using Java cryptography
+ */
+public class Ed25519Signer extends EdDSAProvider implements JWSSigner {
+    private static final String SIGNING_ALGORITHM = "Ed25519";
+
+    private final PrivateKey privateKey;
+
+    public Ed25519Signer(final PrivateKey privateKey) {
+        this.privateKey = Objects.requireNonNull(privateKey, "Private Key 
required");
+    }
+
+    /**
+     * Sign bytes for EdDSA algorithm using configured Ed25519 Private Key
+     *
+     * @param jwsHeader JSON Web Security Header
+     * @param bytes Byte array to be signed
+     * @return Base64 encoded signature
+     * @throws JOSEException Thrown on failure produce signature
+     */
+    @Override
+    public Base64URL sign(final JWSHeader jwsHeader, final byte[] bytes) 
throws JOSEException {
+        final JWSAlgorithm algorithm = jwsHeader.getAlgorithm();
+        if (JWSAlgorithm.EdDSA.equals(algorithm)) {
+            try {
+                final Signature signature = 
Signature.getInstance(SIGNING_ALGORITHM);
+                signature.initSign(privateKey);
+                signature.update(bytes);
+
+                final byte[] jwsSignature = signature.sign();
+                return Base64URL.encode(jwsSignature);
+            } catch (final GeneralSecurityException e) {
+                throw new JOSEException("Ed25519 signing failed", e);
+            }
+        } else {
+            throw new JOSEException("JWS Algorithm EdDSA not found 
[%s]".formatted(algorithm));
+        }
+    }
+}
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/key/Ed25519Verifier.java
 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/key/Ed25519Verifier.java
new file mode 100644
index 0000000000..bc5cb8b483
--- /dev/null
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/key/Ed25519Verifier.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.security.jwt.key;
+
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jose.JWSVerifier;
+import com.nimbusds.jose.crypto.impl.EdDSAProvider;
+import com.nimbusds.jose.util.Base64URL;
+
+import java.security.GeneralSecurityException;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.util.Objects;
+
+/**
+ * Ed25519 implementation of JSON Web Security Verifier using Java cryptography
+ */
+public class Ed25519Verifier extends EdDSAProvider implements JWSVerifier {
+    private static final String SIGNING_ALGORITHM = "Ed25519";
+
+    private final PublicKey publicKey;
+
+    public Ed25519Verifier(final PublicKey publicKey) {
+        this.publicKey = Objects.requireNonNull(publicKey, "Public Key 
required");
+    }
+
+    /**
+     * Verify input bytes for EdDSA using configured Ed25519 Public Key
+     *
+     * @param jwsHeader JSON Web Security Header
+     * @param bytes Byte array for calculating signature to be verified
+     * @param jwsSignature Provided signature to be verified
+     * @return Signature verification status
+     * @throws JOSEException Thrown on verification failures
+     */
+    @Override
+    public boolean verify(final JWSHeader jwsHeader, final byte[] bytes, final 
Base64URL jwsSignature) throws JOSEException {
+        final JWSAlgorithm algorithm = jwsHeader.getAlgorithm();
+        if (JWSAlgorithm.EdDSA.equals(algorithm)) {
+            final byte[] signatureDecoded = jwsSignature.decode();
+
+            try {
+                final Signature signature = getInitializedSignature();
+                signature.update(bytes);
+                return signature.verify(signatureDecoded);
+            } catch (final GeneralSecurityException e) {
+                return false;
+            }
+        } else {
+            throw new JOSEException("JWS Algorithm EdDSA not found 
[%s]".formatted(algorithm));
+        }
+    }
+
+    private Signature getInitializedSignature() throws JOSEException {
+        try {
+            final Signature signature = 
Signature.getInstance(SIGNING_ALGORITHM);
+            signature.initVerify(publicKey);
+            return signature;
+        } catch (final GeneralSecurityException e) {
+            throw new JOSEException("Ed25519 signature initialization failed", 
e);
+        }
+    }
+}
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/key/Ed25519VerifierFactory.java
 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/key/Ed25519VerifierFactory.java
new file mode 100644
index 0000000000..eb20bd75ef
--- /dev/null
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/key/Ed25519VerifierFactory.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.security.jwt.key;
+
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jose.JWSVerifier;
+import com.nimbusds.jose.KeyTypeException;
+import com.nimbusds.jose.jca.JCAContext;
+import com.nimbusds.jose.proc.JWSVerifierFactory;
+
+import java.security.Key;
+import java.security.PublicKey;
+import java.util.Set;
+
+/**
+ * Ed25519 implementation of Verifier Factory
+ */
+public class Ed25519VerifierFactory implements JWSVerifierFactory {
+    private static final Set<JWSAlgorithm> SUPPORTED_ALGORITHMS = 
Set.of(JWSAlgorithm.EdDSA);
+
+    private final JCAContext jcaContext = new JCAContext();
+
+    /**
+     * Create JSON Web Security Verifier for EdDSA using Ed25519 Public Key
+     *
+     * @param jwsHeader JSON Web Security Header
+     * @param key Ed25519 Public Key required
+     * @return JSON Web Security Verifier
+     * @throws JOSEException Thrown on failure to create verifier
+     */
+    @Override
+    public JWSVerifier createJWSVerifier(final JWSHeader jwsHeader, final Key 
key) throws JOSEException {
+        final JWSAlgorithm algorithm = jwsHeader.getAlgorithm();
+
+        if (SUPPORTED_ALGORITHMS.contains(algorithm)) {
+           if (key instanceof PublicKey publicKey) {
+                final Ed25519Verifier verifier = new 
Ed25519Verifier(publicKey);
+                verifier.getJCAContext().setProvider(jcaContext.getProvider());
+                return verifier;
+           } else {
+               throw new KeyTypeException(PublicKey.class);
+           }
+        } else {
+            throw new JOSEException("JWS Algorithm [%s] not 
supported".formatted(algorithm));
+        }
+    }
+
+    @Override
+    public Set<JWSAlgorithm> supportedJWSAlgorithms() {
+        return SUPPORTED_ALGORITHMS;
+    }
+
+    @Override
+    public JCAContext getJCAContext() {
+        return jcaContext;
+    }
+}
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/key/command/KeyGenerationCommand.java
 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/key/command/KeyGenerationCommand.java
index d1bf809af0..75c939cd71 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/key/command/KeyGenerationCommand.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/key/command/KeyGenerationCommand.java
@@ -18,9 +18,9 @@ package org.apache.nifi.web.security.jwt.key.command;
 
 import com.nimbusds.jose.JWSAlgorithm;
 import com.nimbusds.jose.JWSSigner;
-import com.nimbusds.jose.crypto.RSASSASigner;
 import org.apache.nifi.web.security.jwt.jws.JwsSignerContainer;
 import org.apache.nifi.web.security.jwt.jws.SignerListener;
+import org.apache.nifi.web.security.jwt.key.Ed25519Signer;
 import org.apache.nifi.web.security.jwt.key.VerificationKeyListener;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -28,7 +28,6 @@ import org.slf4j.LoggerFactory;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
 import java.util.Objects;
 import java.util.UUID;
 
@@ -38,11 +37,9 @@ import java.util.UUID;
 public class KeyGenerationCommand implements Runnable {
     private static final Logger LOGGER = 
LoggerFactory.getLogger(KeyGenerationCommand.class);
 
-    private static final String KEY_ALGORITHM = "RSA";
+    private static final String KEY_ALGORITHM = "Ed25519";
 
-    private static final int KEY_SIZE = 4096;
-
-    private static final JWSAlgorithm JWS_ALGORITHM = JWSAlgorithm.PS512;
+    private static final JWSAlgorithm JWS_ALGORITHM = JWSAlgorithm.EdDSA;
 
     private final KeyPairGenerator keyPairGenerator;
 
@@ -55,7 +52,6 @@ public class KeyGenerationCommand implements Runnable {
         this.verificationKeyListener = 
Objects.requireNonNull(verificationKeyListener, "Verification Key Listener 
required");
         try {
             keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM);
-            keyPairGenerator.initialize(KEY_SIZE, new SecureRandom());
         } catch (final NoSuchAlgorithmException e) {
             throw new IllegalArgumentException(e);
         }
@@ -72,7 +68,7 @@ public class KeyGenerationCommand implements Runnable {
 
         verificationKeyListener.onVerificationKeyGenerated(keyIdentifier, 
keyPair.getPublic());
 
-        final JWSSigner jwsSigner = new RSASSASigner(keyPair.getPrivate());
+        final JWSSigner jwsSigner = new Ed25519Signer(keyPair.getPrivate());
         signerListener.onSignerUpdated(new JwsSignerContainer(keyIdentifier, 
JWS_ALGORITHM, jwsSigner));
     }
 }
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/key/command/KeyGenerationCommandTest.java
 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/key/command/KeyGenerationCommandTest.java
index 6ebf55e9c1..eb327d3082 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/key/command/KeyGenerationCommandTest.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/key/command/KeyGenerationCommandTest.java
@@ -35,9 +35,9 @@ import static org.mockito.Mockito.verify;
 
 @ExtendWith(MockitoExtension.class)
 public class KeyGenerationCommandTest {
-    private static final String KEY_ALGORITHM = "RSA";
+    private static final String KEY_ALGORITHM = "EdDSA";
 
-    private static final JWSAlgorithm JWS_ALGORITHM = JWSAlgorithm.PS512;
+    private static final JWSAlgorithm JWS_ALGORITHM = JWSAlgorithm.EdDSA;
 
     @Mock
     private SignerListener signerListener;
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/provider/StandardBearerTokenProviderTest.java
 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/provider/StandardBearerTokenProviderTest.java
index 1cce46dce4..df7c55b4dd 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/provider/StandardBearerTokenProviderTest.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/provider/StandardBearerTokenProviderTest.java
@@ -20,12 +20,12 @@ import com.nimbusds.jose.JOSEException;
 import com.nimbusds.jose.JWSAlgorithm;
 import com.nimbusds.jose.JWSSigner;
 import com.nimbusds.jose.JWSVerifier;
-import com.nimbusds.jose.crypto.RSASSASigner;
-import com.nimbusds.jose.crypto.RSASSAVerifier;
 import com.nimbusds.jwt.JWTClaimsSet;
 import com.nimbusds.jwt.SignedJWT;
 import org.apache.nifi.web.security.jwt.jws.JwsSignerContainer;
 import org.apache.nifi.web.security.jwt.jws.JwsSignerProvider;
+import org.apache.nifi.web.security.jwt.key.Ed25519Signer;
+import org.apache.nifi.web.security.jwt.key.Ed25519Verifier;
 import org.apache.nifi.web.security.token.LoginAuthenticationToken;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
@@ -40,7 +40,6 @@ import java.net.URI;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.NoSuchAlgorithmException;
-import java.security.interfaces.RSAPublicKey;
 import java.text.ParseException;
 import java.time.Duration;
 import java.time.Instant;
@@ -70,11 +69,9 @@ public class StandardBearerTokenProviderTest {
 
     private static final URI ISSUER = URI.create("https://localhost:8443";);
 
-    private static final String KEY_ALGORITHM = "RSA";
+    private static final String KEY_ALGORITHM = "Ed25519";
 
-    private static final int KEY_SIZE = 4096;
-
-    private static final JWSAlgorithm JWS_ALGORITHM = JWSAlgorithm.PS512;
+    private static final JWSAlgorithm JWS_ALGORITHM = JWSAlgorithm.EdDSA;
 
     private static final String GROUP = "ProviderGroup";
 
@@ -93,7 +90,6 @@ public class StandardBearerTokenProviderTest {
     @BeforeAll
     public static void setKeyPair() throws NoSuchAlgorithmException {
         final KeyPairGenerator keyPairGenerator = 
KeyPairGenerator.getInstance(KEY_ALGORITHM);
-        keyPairGenerator.initialize(KEY_SIZE);
         keyPair = keyPairGenerator.generateKeyPair();
     }
 
@@ -101,8 +97,8 @@ public class StandardBearerTokenProviderTest {
     public void setProvider() {
         provider = new StandardBearerTokenProvider(jwsSignerProvider, 
issuerProvider);
 
-        jwsVerifier = new RSASSAVerifier((RSAPublicKey) keyPair.getPublic());
-        final JWSSigner jwsSigner = new RSASSASigner(keyPair.getPrivate());
+        jwsVerifier = new Ed25519Verifier(keyPair.getPublic());
+        final JWSSigner jwsSigner = new Ed25519Signer(keyPair.getPrivate());
 
         final String keyIdentifier = UUID.randomUUID().toString();
         final JwsSignerContainer jwsSignerContainer = new 
JwsSignerContainer(keyIdentifier, JWS_ALGORITHM, jwsSigner);

Reply via email to