Avoid MRJAR in keycloak-core

Closes #34630

Signed-off-by: rmartinc <rmartinc@redhat.com>
This commit is contained in:
rmartinc 2024-11-13 12:27:38 +01:00 committed by Marek Posolda
parent ca1c10f7ba
commit c1d4dad4dc
11 changed files with 463 additions and 530 deletions

View File

@ -88,7 +88,7 @@ jobs:
SEP=","
done
./mvnw install -pl "$PROJECTS" -am
./mvnw test -pl "$PROJECTS" -am
- name: Upload JVM Heapdumps
if: always()

View File

@ -77,9 +77,9 @@
<profiles>
<profile>
<id>jdk-16</id>
<id>jdk-15</id>
<activation>
<jdk>[16,)</jdk>
<jdk>[15,)</jdk>
</activation>
<build>
<plugins>
@ -87,17 +87,16 @@
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<id>compile-java16</id>
<id>compile-java15</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<release>16</release>
<release>15</release>
<compileSourceRoots>
<compileSourceRoot>${project.basedir}/src/main/java16</compileSourceRoot>
<compileSourceRoot>${project.basedir}/src/main/java15</compileSourceRoot>
</compileSourceRoots>
<multiReleaseOutput>true</multiReleaseOutput>
</configuration>
</execution>
</executions>
@ -117,13 +116,6 @@
<plugins>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestEntries>
<Multi-Release>true</Multi-Release>
</manifestEntries>
</archive>
</configuration>
<executions>
<execution>
<goals>

View File

@ -1,150 +0,0 @@
/*
* 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:sthorger@redhat.com">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) {
return this.ec(key, null, keyUse);
}
public JWK ec(Key key, List<X509Certificate> certificates, 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)));
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 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;
}
}

View File

@ -1,121 +0,0 @@
/*
* 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 java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.RSAPublicKeySpec;
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.common.util.Base64Url;
import org.keycloak.crypto.KeyType;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public abstract class AbstractJWKParser {
protected JWK jwk;
public JWK getJwk() {
return jwk;
}
public PublicKey toPublicKey() {
if (jwk == null) {
throw new IllegalStateException("Not possible to convert to the publicKey. The jwk is not set");
}
String keyType = jwk.getKeyType();
if (KeyType.RSA.equals(keyType)) {
return createRSAPublicKey();
} else if (KeyType.EC.equals(keyType)) {
return createECPublicKey();
} else {
throw new RuntimeException("Unsupported keyType " + keyType);
}
}
protected PublicKey createECPublicKey() {
/* Check if jwk.getOtherClaims return an empty map */
if (jwk.getOtherClaims().size() == 0) {
throw new RuntimeException("JWK Otherclaims map is empty.");
}
/* Try retrieving the necessary fields */
String crv = (String) jwk.getOtherClaims().get(ECPublicJWK.CRV);
String xStr = (String) jwk.getOtherClaims().get(ECPublicJWK.X);
String yStr = (String) jwk.getOtherClaims().get(ECPublicJWK.Y);
/* Check if the retrieving of necessary fields success */
if (crv == null || xStr == null || yStr == null) {
throw new RuntimeException("Fail to retrieve ECPublicJWK.CRV, ECPublicJWK.X or ECPublicJWK.Y field.");
}
BigInteger x = new BigInteger(1, Base64Url.decode(xStr));
BigInteger y = new BigInteger(1, Base64Url.decode(yStr));
String name;
switch (crv) {
case "P-256" :
name = "secp256r1";
break;
case "P-384" :
name = "secp384r1";
break;
case "P-521" :
name = "secp521r1";
break;
default :
throw new RuntimeException("Unsupported curve");
}
try {
ECPoint point = new ECPoint(x, y);
ECParameterSpec params = CryptoIntegration.getProvider().createECParams(name);
ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(point, params);
KeyFactory kf = CryptoIntegration.getProvider().getKeyFactory("ECDSA");
return kf.generatePublic(pubKeySpec);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
protected PublicKey createRSAPublicKey() {
BigInteger modulus = new BigInteger(1, Base64Url.decode(jwk.getOtherClaims().get(RSAPublicJWK.MODULUS).toString()));
BigInteger publicExponent = new BigInteger(1, Base64Url.decode(jwk.getOtherClaims().get(RSAPublicJWK.PUBLIC_EXPONENT).toString()));
try {
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePublic(new RSAPublicKeySpec(modulus, publicExponent));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public boolean isKeyTypeSupported(String keyType) {
return (RSAPublicJWK.RSA.equals(keyType) || ECPublicJWK.EC.equals(keyType));
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright 2024 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 java.security.Key;
import java.security.PublicKey;
import org.keycloak.crypto.KeyUse;
/**
* <p>Interface for the EdECUtils that will be implemented only for JDK 15+.</p>
*
* @author rmartinc
*/
interface EdECUtils {
boolean isEdECSupported();
JWK okp(String kid, String algorithm, Key key, KeyUse keyUse);
PublicKey createOKPPublicKey(JWK jwk);
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2024 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 java.security.Key;
import java.security.PublicKey;
import org.keycloak.crypto.KeyUse;
/**
* <p>Unsupported implementation for old jdk versions.</p>
*
* @author rmartinc
*/
class EdECUtilsUnsupportedImpl implements EdECUtils {
@Override
public boolean isEdECSupported() {
return false;
}
@Override
public JWK okp(String kid, String algorithm, Key key, KeyUse keyUse) {
throw new UnsupportedOperationException("EdDSA algorithms not supported in this JDK version");
}
@Override
public PublicKey createOKPPublicKey(JWK jwk) {
throw new UnsupportedOperationException("EdDSA algorithms not supported in this JDK version");
}
}

View File

@ -17,14 +17,49 @@
package org.keycloak.jose.jwk;
import org.keycloak.crypto.KeyUse;
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:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class JWKBuilder extends AbstractJWKBuilder {
public class JWKBuilder {
// internal util class only loaded for jdk versions with EdEC support
protected static final EdECUtils EdEC_UTILS;
static {
EdECUtils tmp;
try {
// check if the impl class for EdEC can be loaded in the runtime
tmp = (EdECUtils) Class.forName("org.keycloak.jose.jwk.EdECUtilsImpl")
.getDeclaredConstructor().newInstance();
} catch(Throwable e) {
// not supported implementation
tmp = new EdECUtilsUnsupportedImpl();
}
EdEC_UTILS = tmp;
}
public static final KeyUse DEFAULT_PUBLIC_KEY_USE = KeyUse.SIG;
protected String kid;
protected String algorithm;
private JWKBuilder() {
}
@ -43,15 +78,95 @@ public class JWKBuilder extends AbstractJWKBuilder {
return this;
}
@Override
public JWK okp(Key key) {
// not supported if jdk vesion < 17
throw new UnsupportedOperationException("EdDSA algorithms not supported in this JDK version");
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) {
return this.ec(key, null, keyUse);
}
public JWK ec(Key key, List<X509Certificate> certificates, 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)));
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 okp(Key key) {
return okp(key, DEFAULT_PUBLIC_KEY_USE);
}
@Override
public JWK okp(Key key, KeyUse keyUse) {
// not supported if jdk version < 17
throw new UnsupportedOperationException("EdDSA algorithms not supported in this JDK version");
return EdEC_UTILS.okp(kid, algorithm, key, keyUse);
}
}

98
core/src/main/java/org/keycloak/jose/jwk/JWKParser.java Executable file → Normal file
View File

@ -17,12 +17,25 @@
package org.keycloak.jose.jwk;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.RSAPublicKeySpec;
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.common.util.Base64Url;
import org.keycloak.crypto.KeyType;
import org.keycloak.util.JsonSerialization;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class JWKParser extends AbstractJWKParser {
public class JWKParser {
protected JWK jwk;
private JWKParser() {
}
@ -48,4 +61,87 @@ public class JWKParser extends AbstractJWKParser {
}
}
public JWK getJwk() {
return jwk;
}
public PublicKey toPublicKey() {
if (jwk == null) {
throw new IllegalStateException("Not possible to convert to the publicKey. The jwk is not set");
}
String keyType = jwk.getKeyType();
if (KeyType.RSA.equals(keyType)) {
return createRSAPublicKey();
} else if (KeyType.EC.equals(keyType)) {
return createECPublicKey();
} else if (KeyType.OKP.equals(keyType)) {
return JWKBuilder.EdEC_UTILS.createOKPPublicKey(jwk);
} else {
throw new RuntimeException("Unsupported keyType " + keyType);
}
}
protected PublicKey createECPublicKey() {
/* Check if jwk.getOtherClaims return an empty map */
if (jwk.getOtherClaims().size() == 0) {
throw new RuntimeException("JWK Otherclaims map is empty.");
}
/* Try retrieving the necessary fields */
String crv = (String) jwk.getOtherClaims().get(ECPublicJWK.CRV);
String xStr = (String) jwk.getOtherClaims().get(ECPublicJWK.X);
String yStr = (String) jwk.getOtherClaims().get(ECPublicJWK.Y);
/* Check if the retrieving of necessary fields success */
if (crv == null || xStr == null || yStr == null) {
throw new RuntimeException("Fail to retrieve ECPublicJWK.CRV, ECPublicJWK.X or ECPublicJWK.Y field.");
}
BigInteger x = new BigInteger(1, Base64Url.decode(xStr));
BigInteger y = new BigInteger(1, Base64Url.decode(yStr));
String name;
switch (crv) {
case "P-256" :
name = "secp256r1";
break;
case "P-384" :
name = "secp384r1";
break;
case "P-521" :
name = "secp521r1";
break;
default :
throw new RuntimeException("Unsupported curve");
}
try {
ECPoint point = new ECPoint(x, y);
ECParameterSpec params = CryptoIntegration.getProvider().createECParams(name);
ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(point, params);
KeyFactory kf = CryptoIntegration.getProvider().getKeyFactory("ECDSA");
return kf.generatePublic(pubKeySpec);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
protected PublicKey createRSAPublicKey() {
BigInteger modulus = new BigInteger(1, Base64Url.decode(jwk.getOtherClaims().get(RSAPublicJWK.MODULUS).toString()));
BigInteger publicExponent = new BigInteger(1, Base64Url.decode(jwk.getOtherClaims().get(RSAPublicJWK.PUBLIC_EXPONENT).toString()));
try {
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePublic(new RSAPublicKeySpec(modulus, publicExponent));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public boolean isKeyTypeSupported(String keyType) {
return (RSAPublicJWK.RSA.equals(keyType) || ECPublicJWK.EC.equals(keyType)
|| (JWKBuilder.EdEC_UTILS.isEdECSupported() && OKPPublicJWK.OKP.equals(keyType)));
}
}

View File

@ -0,0 +1,157 @@
/*
* Copyright 2024 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 java.math.BigInteger;
import java.security.Key;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.interfaces.EdECPublicKey;
import java.security.spec.EdECPoint;
import java.security.spec.EdECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.NamedParameterSpec;
import java.util.Optional;
import org.keycloak.common.util.Base64Url;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyType;
import org.keycloak.crypto.KeyUse;
/**
* <p>Class that uses Java 15+ EdEC classes and implements the EdECUtils interface.</p>
*
* @author rmartinc
*/
class EdECUtilsImpl implements EdECUtils {
public EdECUtilsImpl() {
}
@Override
public boolean isEdECSupported() {
return true;
}
@Override
public JWK okp(String kid, String algorithm, Key key, KeyUse keyUse) {
EdECPublicKey eddsaPublicKey = (EdECPublicKey) key;
OKPPublicJWK k = new OKPPublicJWK();
kid = kid != null ? kid : KeyUtils.createKeyId(key);
k.setKeyId(kid);
k.setKeyType(KeyType.OKP);
k.setAlgorithm(algorithm);
k.setPublicKeyUse(keyUse == null ? JWKBuilder.DEFAULT_PUBLIC_KEY_USE.getSpecName() : keyUse.getSpecName());
k.setCrv(eddsaPublicKey.getParams().getName());
Optional<String> x = edPublicKeyInJwkRepresentation(eddsaPublicKey);
k.setX(x.orElse(""));
return k;
}
@Override
public PublicKey createOKPPublicKey(JWK jwk) {
String x = (String) jwk.getOtherClaims().get(OKPPublicJWK.X);
String crv = (String) jwk.getOtherClaims().get(OKPPublicJWK.CRV);
// JWK representation "x" of a public key
int bytesLength = 0;
if (Algorithm.Ed25519.equals(crv)) {
bytesLength = 32;
} else if (Algorithm.Ed448.equals(crv)) {
bytesLength = 57;
} else {
throw new RuntimeException("Invalid JWK representation of OKP type algorithm");
}
byte[] decodedX = Base64Url.decode(x);
if (decodedX.length != bytesLength) {
throw new RuntimeException("Invalid JWK representation of OKP type public key");
}
// x-coordinate's parity check shown by MSB(bit) of MSB(byte) of decoded "x": 1 is odd, 0 is even
boolean isOddX = false;
if ((decodedX[decodedX.length - 1] & -128) != 0) { // 0b10000000
isOddX = true;
}
// MSB(bit) of MSB(byte) showing x-coodinate's parity is set to 0
decodedX[decodedX.length - 1] &= 127; // 0b01111111
// both x and y-coordinate in twisted Edwards curve are always 0 or natural number
BigInteger y = new BigInteger(1, reverseBytes(decodedX));
NamedParameterSpec spec = new NamedParameterSpec(crv);
EdECPoint ep = new EdECPoint(isOddX, y);
EdECPublicKeySpec keySpec = new EdECPublicKeySpec(spec, ep);
PublicKey publicKey = null;
try {
publicKey = KeyFactory.getInstance(crv).generatePublic(keySpec);
} catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
return publicKey;
}
private static Optional<String> edPublicKeyInJwkRepresentation(EdECPublicKey eddsaPublicKey) {
EdECPoint edEcPoint = eddsaPublicKey.getPoint();
BigInteger yCoordinate = edEcPoint.getY();
// JWK representation "x" of a public key
int bytesLength = 0;
if (Algorithm.Ed25519.equals(eddsaPublicKey.getParams().getName())) {
bytesLength = 32;
} else if (Algorithm.Ed448.equals(eddsaPublicKey.getParams().getName())) {
bytesLength = 57;
} else {
return Optional.ofNullable(null);
}
// consider the case where yCoordinate.toByteArray() is less than bytesLength due to relatively small value of y-coordinate.
byte[] yCoordinateLittleEndianBytes = new byte[bytesLength];
// convert big endian representation of BigInteger to little endian representation of JWK representation (RFC 8032,8027)
byte[] yCoordinateLittleEndian = reverseBytes(yCoordinate.toByteArray());
System.arraycopy(yCoordinateLittleEndian, 0, yCoordinateLittleEndianBytes, 0, yCoordinateLittleEndian.length);
// set a parity of x-coordinate to the most significant bit of the last octet (RFC 8032, 8037)
if (edEcPoint.isXOdd()) {
yCoordinateLittleEndianBytes[yCoordinateLittleEndianBytes.length - 1] |= -128; // 0b10000000
}
return Optional.ofNullable(Base64Url.encode(yCoordinateLittleEndianBytes));
}
private 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;
}
}

View File

@ -1,108 +0,0 @@
/*
* 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 java.math.BigInteger;
import java.security.Key;
import java.security.interfaces.EdECPublicKey;
import java.security.spec.EdECPoint;
import java.util.Arrays;
import java.util.Optional;
import org.keycloak.common.util.Base64Url;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyType;
import org.keycloak.crypto.KeyUse;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class JWKBuilder extends AbstractJWKBuilder {
private JWKBuilder() {
}
public static JWKBuilder create() {
return new JWKBuilder();
}
public JWKBuilder kid(String kid) {
this.kid = kid;
return this;
}
public JWKBuilder algorithm(String algorithm) {
this.algorithm = algorithm;
return this;
}
@Override
public JWK okp(Key key) {
return okp(key, DEFAULT_PUBLIC_KEY_USE);
}
@Override
public JWK okp(Key key, KeyUse keyUse) {
EdECPublicKey eddsaPublicKey = (EdECPublicKey) key;
OKPPublicJWK k = new OKPPublicJWK();
String kid = this.kid != null ? this.kid : KeyUtils.createKeyId(key);
k.setKeyId(kid);
k.setKeyType(KeyType.OKP);
k.setAlgorithm(algorithm);
k.setPublicKeyUse(keyUse == null ? DEFAULT_PUBLIC_KEY_USE.getSpecName() : keyUse.getSpecName());
k.setCrv(eddsaPublicKey.getParams().getName());
Optional<String> x = edPublicKeyInJwkRepresentation(eddsaPublicKey);
k.setX(x.orElse(""));
return k;
}
private Optional<String> edPublicKeyInJwkRepresentation(EdECPublicKey eddsaPublicKey) {
EdECPoint edEcPoint = eddsaPublicKey.getPoint();
BigInteger yCoordinate = edEcPoint.getY();
// JWK representation "x" of a public key
int bytesLength = 0;
if (Algorithm.Ed25519.equals(eddsaPublicKey.getParams().getName())) {
bytesLength = 32;
} else if (Algorithm.Ed448.equals(eddsaPublicKey.getParams().getName())) {
bytesLength = 57;
} else {
return Optional.ofNullable(null);
}
// consider the case where yCoordinate.toByteArray() is less than bytesLength due to relatively small value of y-coordinate.
byte[] yCoordinateLittleEndianBytes = new byte[bytesLength];
// convert big endian representation of BigInteger to little endian representation of JWK representation (RFC 8032,8027)
yCoordinateLittleEndianBytes = Arrays.copyOf(reverseBytes(yCoordinate.toByteArray()), bytesLength);
// set a parity of x-coordinate to the most significant bit of the last octet (RFC 8032, 8037)
if (edEcPoint.isXOdd()) {
yCoordinateLittleEndianBytes[yCoordinateLittleEndianBytes.length - 1] |= -128; // 0b10000000
}
return Optional.ofNullable(Base64Url.encode(yCoordinateLittleEndianBytes));
}
}

View File

@ -1,127 +0,0 @@
/*
* 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 java.math.BigInteger;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.EdECPoint;
import java.security.spec.EdECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.NamedParameterSpec;
import org.keycloak.common.util.Base64Url;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyType;
import org.keycloak.util.JsonSerialization;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class JWKParser extends AbstractJWKParser {
private JWKParser() {
}
public static JWKParser create() {
return new JWKParser();
}
public JWKParser(JWK jwk) {
this.jwk = jwk;
}
public static JWKParser create(JWK jwk) {
return new JWKParser(jwk);
}
public JWKParser parse(String jwk) {
try {
this.jwk = JsonSerialization.mapper.readValue(jwk, JWK.class);
return this;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public PublicKey toPublicKey() {
if (jwk == null) {
throw new IllegalStateException("Not possible to convert to the publicKey. The jwk is not set");
}
String keyType = jwk.getKeyType();
if (KeyType.RSA.equals(keyType)) {
return createRSAPublicKey();
} else if (KeyType.EC.equals(keyType)) {
return createECPublicKey();
} else if (KeyType.OKP.equals(keyType)) {
return createOKPPublicKey();
} else {
throw new RuntimeException("Unsupported keyType " + keyType);
}
}
private PublicKey createOKPPublicKey() {
String x = (String) jwk.getOtherClaims().get(OKPPublicJWK.X);
String crv = (String) jwk.getOtherClaims().get(OKPPublicJWK.CRV);
// JWK representation "x" of a public key
int bytesLength = 0;
if (Algorithm.Ed25519.equals(crv)) {
bytesLength = 32;
} else if (Algorithm.Ed448.equals(crv)) {
bytesLength = 57;
} else {
throw new RuntimeException("Invalid JWK representation of OKP type algorithm");
}
byte[] decodedX = Base64Url.decode(x);
if (decodedX.length != bytesLength) {
throw new RuntimeException("Invalid JWK representation of OKP type public key");
}
// x-coordinate's parity check shown by MSB(bit) of MSB(byte) of decoded "x": 1 is odd, 0 is even
boolean isOddX = false;
if ((decodedX[decodedX.length - 1] & -128) != 0) { // 0b10000000
isOddX = true;
}
// MSB(bit) of MSB(byte) showing x-coodinate's parity is set to 0
decodedX[decodedX.length - 1] &= 127; // 0b01111111
// both x and y-coordinate in twisted Edwards curve are always 0 or natural number
BigInteger y = new BigInteger(1, JWKBuilder.reverseBytes(decodedX));
NamedParameterSpec spec = new NamedParameterSpec(crv);
EdECPoint ep = new EdECPoint(isOddX, y);
EdECPublicKeySpec keySpec = new EdECPublicKeySpec(spec, ep);
PublicKey publicKey = null;
try {
publicKey = KeyFactory.getInstance(crv).generatePublic(keySpec);
} catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
return publicKey;
}
@Override
public boolean isKeyTypeSupported(String keyType) {
return (RSAPublicJWK.RSA.equals(keyType) || ECPublicJWK.EC.equals(keyType) || OKPPublicJWK.OKP.equals(keyType));
}
}