Skip to content

Commit

Permalink
Supporting EdDSA
Browse files Browse the repository at this point in the history
closes keycloak#15714

Signed-off-by: Takashi Norimatsu <[email protected]>

Co-authored-by: Muhammad Zakwan Bin Mohd Zahid <[email protected]>
Co-authored-by: rmartinc <[email protected]>
  • Loading branch information
3 people authored and mposolda committed Jan 24, 2024
1 parent 3b3eef2 commit b99f45e
Show file tree
Hide file tree
Showing 67 changed files with 2,729 additions and 1,131 deletions.
2 changes: 2 additions & 0 deletions adapters/oidc/spring-boot2/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/.apt_generated/
/.apt_generated_tests/
2 changes: 2 additions & 0 deletions adapters/saml/wildfly/wildfly-jakarta-subsystem/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/.apt_generated/
/.apt_generated_tests/
2 changes: 2 additions & 0 deletions adapters/saml/wildfly/wildfly-subsystem/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/.apt_generated/
/.apt_generated_tests/
45 changes: 45 additions & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,48 @@
<scope>test</scope>
</dependency>
</dependencies>

<profiles>
<profile>
<id>jdk-16</id>
<activation>
<jdk>[16,)</jdk>
</activation>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<id>compile-java16</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<release>16</release>
<compileSourceRoots>
<compileSourceRoot>${project.basedir}/src/main/java16</compileSourceRoot>
</compileSourceRoots>
<multiReleaseOutput>true</multiReleaseOutput>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<configuration>
<instructions>
<_fixupmessages>"Classes found in the wrong directory";is:=warning</_fixupmessages>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>

<build>
<resources>
<resource>
Expand All @@ -83,6 +125,9 @@
<configuration>
<archive>
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
<manifestEntries>
<Multi-Release>true</Multi-Release>
</manifestEntries>
</archive>
</configuration>
<executions>
Expand Down
14 changes: 11 additions & 3 deletions core/src/main/java/org/keycloak/crypto/Algorithm.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,21 @@ public interface Algorithm {
String RS256 = "RS256";
String RS384 = "RS384";
String RS512 = "RS512";
String ES256 = "ES256";
String ES384 = "ES384";
String ES512 = "ES512";
String PS256 = "PS256";
String PS384 = "PS384";
String PS512 = "PS512";

/* ECDSA signing algorithms */
String ES256 = "ES256";
String ES384 = "ES384";
String ES512 = "ES512";

/* EdDSA signing algorithms */
String EdDSA = "EdDSA";
/* EdDSA Curve */
String Ed25519 = "Ed25519";
String Ed448 = "Ed448";

/* RSA Encryption Algorithms */
String RSA1_5 = CryptoConstants.RSA1_5;
String RSA_OAEP = CryptoConstants.RSA_OAEP;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,13 @@ public String getAlgorithm() {

@Override
public String getHashAlgorithm() {
return JavaAlgorithm.getJavaAlgorithmForHash(key.getAlgorithmOrDefault());
return JavaAlgorithm.getJavaAlgorithmForHash(key.getAlgorithmOrDefault(), key.getCurve());
}

@Override
public byte[] sign(byte[] data) throws SignatureException {
try {
Signature signature = Signature.getInstance(JavaAlgorithm.getJavaAlgorithm(key.getAlgorithmOrDefault()));
Signature signature = Signature.getInstance(JavaAlgorithm.getJavaAlgorithm(key.getAlgorithmOrDefault(), key.getCurve()));
signature.initSign((PrivateKey) key.getPrivateKey());
signature.update(data);
return signature.sign();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public String getAlgorithm() {
@Override
public boolean verify(byte[] data, byte[] signature) throws VerificationException {
try {
Signature verifier = Signature.getInstance(JavaAlgorithm.getJavaAlgorithm(key.getAlgorithmOrDefault()));
Signature verifier = Signature.getInstance(JavaAlgorithm.getJavaAlgorithm(key.getAlgorithmOrDefault(), key.getCurve()));
verifier.initVerify((PublicKey) key.getPublicKey());
verifier.update(data);
return verifier.verify(signature);
Expand Down
33 changes: 32 additions & 1 deletion core/src/main/java/org/keycloak/crypto/JavaAlgorithm.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,20 @@ public class JavaAlgorithm {
public static final String PS256 = "SHA256withRSAandMGF1";
public static final String PS384 = "SHA384withRSAandMGF1";
public static final String PS512 = "SHA512withRSAandMGF1";
public static final String Ed25519 = "Ed25519";
public static final String Ed448 = "Ed448";
public static final String AES = "AES";

public static final String SHA256 = "SHA-256";
public static final String SHA384 = "SHA-384";
public static final String SHA512 = "SHA-512";
public static final String SHAKE256 = "SHAKE-256";

public static String getJavaAlgorithm(String algorithm) {
return getJavaAlgorithm(algorithm, null);
}

public static String getJavaAlgorithm(String algorithm, String curve) {
switch (algorithm) {
case Algorithm.RS256:
return RS256;
Expand All @@ -62,15 +69,23 @@ public static String getJavaAlgorithm(String algorithm) {
return PS384;
case Algorithm.PS512:
return PS512;
case Algorithm.EdDSA:
if (curve != null) {
return curve;
}
return Ed25519;
case Algorithm.AES:
return AES;
default:
throw new IllegalArgumentException("Unknown algorithm " + algorithm);
}
}


public static String getJavaAlgorithmForHash(String algorithm) {
return getJavaAlgorithmForHash(algorithm, null);
}

public static String getJavaAlgorithmForHash(String algorithm, String curve) {
switch (algorithm) {
case Algorithm.RS256:
return SHA256;
Expand All @@ -96,6 +111,18 @@ public static String getJavaAlgorithmForHash(String algorithm) {
return SHA384;
case Algorithm.PS512:
return SHA512;
case Algorithm.EdDSA:
if (curve != null) {
switch (curve) {
case Algorithm.Ed25519:
return SHA512;
case Algorithm.Ed448:
return SHAKE256;
default:
throw new IllegalArgumentException("Unknown curve for EdDSA " + curve);
}
}
return SHA512;
case Algorithm.AES:
return AES;
default:
Expand All @@ -111,6 +138,10 @@ public static boolean isECJavaAlgorithm(String algorithm) {
return getJavaAlgorithm(algorithm).contains("ECDSA");
}

public static boolean isEddsaJavaAlgorithm(String algorithm) {
return getJavaAlgorithm(algorithm).contains("Ed");
}

public static boolean isHMACJavaAlgorithm(String algorithm) {
return getJavaAlgorithm(algorithm).contains("HMAC");
}
Expand Down
1 change: 1 addition & 0 deletions core/src/main/java/org/keycloak/crypto/KeyType.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@ public interface KeyType {
String EC = "EC";
String RSA = "RSA";
String OCT = "OCT";
String OKP = "OKP";

}
10 changes: 10 additions & 0 deletions core/src/main/java/org/keycloak/crypto/KeyWrapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public class KeyWrapper {
private X509Certificate certificate;
private List<X509Certificate> certificateChain;
private boolean isDefaultClientCertificate;
private String curve;

public String getProviderId() {
return providerId;
Expand Down Expand Up @@ -176,6 +177,14 @@ public void setIsDefaultClientCertificate(boolean isDefaultClientCertificate) {
this.isDefaultClientCertificate = isDefaultClientCertificate;
}

public void setCurve(String curve) {
this.curve = curve;
}

public String getCurve() {
return curve;
}

public KeyWrapper cloneKey() {
KeyWrapper key = new KeyWrapper();
key.providerId = this.providerId;
Expand All @@ -189,6 +198,7 @@ public KeyWrapper cloneKey() {
key.publicKey = this.publicKey;
key.privateKey = this.privateKey;
key.certificate = this.certificate;
key.curve = this.curve;
if (this.certificateChain != null) {
key.certificateChain = new ArrayList<>(this.certificateChain);
}
Expand Down
138 changes: 138 additions & 0 deletions core/src/main/java/org/keycloak/jose/jwk/AbstractJWKBuilder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed 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.keycloak.jose.jwk;

import static org.keycloak.jose.jwk.JWKUtil.toIntegerBytes;

import java.security.Key;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Collections;
import java.util.List;

import org.keycloak.common.util.Base64Url;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.common.util.PemUtils;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyType;
import org.keycloak.crypto.KeyUse;

/**
* @author <a href="mailto:[email protected]">Stian Thorgersen</a>
*/
public abstract class AbstractJWKBuilder {

public static final KeyUse DEFAULT_PUBLIC_KEY_USE = KeyUse.SIG;

protected String kid;

protected String algorithm;

public JWK rs256(PublicKey key) {
this.algorithm = Algorithm.RS256;
return rsa(key);
}

public JWK rsa(Key key) {
return rsa(key, null, KeyUse.SIG);
}

public JWK rsa(Key key, X509Certificate certificate) {
return rsa(key, Collections.singletonList(certificate), KeyUse.SIG);
}

public JWK rsa(Key key, List<X509Certificate> certificates) {
return rsa(key, certificates, null);
}

public JWK rsa(Key key, List<X509Certificate> certificates, KeyUse keyUse) {
RSAPublicKey rsaKey = (RSAPublicKey) key;

RSAPublicJWK k = new RSAPublicJWK();

String kid = this.kid != null ? this.kid : KeyUtils.createKeyId(key);
k.setKeyId(kid);
k.setKeyType(KeyType.RSA);
k.setAlgorithm(algorithm);
k.setPublicKeyUse(keyUse == null ? KeyUse.SIG.getSpecName() : keyUse.getSpecName());
k.setModulus(Base64Url.encode(toIntegerBytes(rsaKey.getModulus())));
k.setPublicExponent(Base64Url.encode(toIntegerBytes(rsaKey.getPublicExponent())));

if (certificates != null && !certificates.isEmpty()) {
String[] certificateChain = new String[certificates.size()];
for (int i = 0; i < certificates.size(); i++) {
certificateChain[i] = PemUtils.encodeCertificate(certificates.get(i));
}
k.setX509CertificateChain(certificateChain);
}

return k;
}

public JWK rsa(Key key, KeyUse keyUse) {
JWK k = rsa(key);
String keyUseString = keyUse == null ? DEFAULT_PUBLIC_KEY_USE.getSpecName() : keyUse.getSpecName();
if (KeyUse.ENC == keyUse) keyUseString = "enc";
k.setPublicKeyUse(keyUseString);
return k;
}

public JWK ec(Key key) {
return ec(key, DEFAULT_PUBLIC_KEY_USE);
}

public JWK ec(Key key, KeyUse keyUse) {
ECPublicKey ecKey = (ECPublicKey) key;

ECPublicJWK k = new ECPublicJWK();

String kid = this.kid != null ? this.kid : KeyUtils.createKeyId(key);
int fieldSize = ecKey.getParams().getCurve().getField().getFieldSize();

k.setKeyId(kid);
k.setKeyType(KeyType.EC);
k.setAlgorithm(algorithm);
k.setPublicKeyUse(keyUse == null ? DEFAULT_PUBLIC_KEY_USE.getSpecName() : keyUse.getSpecName());
k.setCrv("P-" + fieldSize);
k.setX(Base64Url.encode(toIntegerBytes(ecKey.getW().getAffineX(), fieldSize)));
k.setY(Base64Url.encode(toIntegerBytes(ecKey.getW().getAffineY(), fieldSize)));

return k;
}

public abstract JWK okp(Key key);

public abstract JWK okp(Key key, KeyUse keyUse);

public static byte[] reverseBytes(byte[] array) {
if (array == null || array.length == 0) {
return null;
}

int length = array.length;
byte[] reversedArray = new byte[length];

for (int i = 0; i < length; i++) {
reversedArray[length - 1 - i] = array[i];
}

return reversedArray;
}
}
Loading

0 comments on commit b99f45e

Please sign in to comment.