Remove keycloak-core and keycloak-crypto-default from SAML galleon feature pack and upgrade them to Java 17
closes #32586 Signed-off-by: mposolda <mposolda@gmail.com>
This commit is contained in:
parent
29c8060bda
commit
dad4477995
14 changed files with 294 additions and 374 deletions
|
@ -61,11 +61,6 @@
|
|||
<artifactId>keycloak-common</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-crypto-default</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* 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.adapters.saml.config;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
|
||||
import org.keycloak.common.util.Base64;
|
||||
import org.keycloak.common.util.BouncyIntegration;
|
||||
import org.keycloak.common.util.PemException;
|
||||
|
||||
/**
|
||||
* Fork of the PemUtils from common module to avoid dependency on keycloak-crypto-default
|
||||
*/
|
||||
public class PemUtils {
|
||||
|
||||
/**
|
||||
* Decode a X509 Certificate from a PEM string
|
||||
*
|
||||
* @param cert
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public static X509Certificate decodeCertificate(String cert) {
|
||||
if (cert == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
byte[] der = pemToDer(cert);
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(der);
|
||||
return decodeCertificate(bis);
|
||||
} catch (Exception e) {
|
||||
throw new PemException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Decode a Public Key from a PEM string
|
||||
*
|
||||
* @param pem
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public static PublicKey decodePublicKey(String pem) {
|
||||
if (pem == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
byte[] der = pemToDer(pem);
|
||||
return decodePublicKey(der, "RSA");
|
||||
} catch (Exception e) {
|
||||
throw new PemException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a Private Key from a PEM string
|
||||
*
|
||||
* @param pem
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public static PrivateKey decodePrivateKey(String pem){
|
||||
if (pem == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
byte[] der = pemToDer(pem);
|
||||
return decodePrivateKey(der);
|
||||
} catch (Exception e) {
|
||||
throw new PemException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] pemToDer(String pem) {
|
||||
try {
|
||||
pem = removeBeginEnd(pem);
|
||||
return Base64.decode(pem);
|
||||
} catch (IOException ioe) {
|
||||
throw new PemException(ioe);
|
||||
}
|
||||
}
|
||||
|
||||
private static String removeBeginEnd(String pem) {
|
||||
pem = pem.replaceAll("-----BEGIN (.*)-----", "");
|
||||
pem = pem.replaceAll("-----END (.*)----", "");
|
||||
pem = pem.replaceAll("\r\n", "");
|
||||
pem = pem.replaceAll("\n", "");
|
||||
return pem.trim();
|
||||
}
|
||||
|
||||
private static PrivateKey decodePrivateKey(byte[] der) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException {
|
||||
PKCS8EncodedKeySpec spec =
|
||||
new PKCS8EncodedKeySpec(der);
|
||||
KeyFactory kf = KeyFactory.getInstance("RSA", BouncyIntegration.PROVIDER);
|
||||
return kf.generatePrivate(spec);
|
||||
}
|
||||
|
||||
private static X509Certificate decodeCertificate(InputStream is) throws Exception {
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509", BouncyIntegration.PROVIDER);
|
||||
X509Certificate cert = (X509Certificate) cf.generateCertificate(is);
|
||||
is.close();
|
||||
return cert;
|
||||
}
|
||||
|
||||
private static PublicKey decodePublicKey(byte[] der, String type) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException {
|
||||
X509EncodedKeySpec spec =
|
||||
new X509EncodedKeySpec(der);
|
||||
KeyFactory kf = KeyFactory.getInstance("RSA", BouncyIntegration.PROVIDER);
|
||||
return kf.generatePublic(spec);
|
||||
}
|
||||
}
|
|
@ -24,10 +24,9 @@ import org.keycloak.adapters.saml.SamlDeployment;
|
|||
import org.keycloak.adapters.saml.config.IDP;
|
||||
import org.keycloak.adapters.saml.config.Key;
|
||||
import org.keycloak.adapters.saml.config.KeycloakSamlAdapter;
|
||||
import org.keycloak.adapters.saml.config.PemUtils;
|
||||
import org.keycloak.adapters.saml.config.SP;
|
||||
import org.keycloak.common.crypto.CryptoIntegration;
|
||||
import org.keycloak.common.enums.SslRequired;
|
||||
import org.keycloak.common.util.PemUtils;
|
||||
import org.keycloak.saml.SignatureAlgorithm;
|
||||
import org.keycloak.saml.common.exceptions.ParsingException;
|
||||
|
||||
|
@ -58,7 +57,6 @@ public class DeploymentBuilder {
|
|||
protected static Logger log = Logger.getLogger(DeploymentBuilder.class);
|
||||
|
||||
public SamlDeployment build(InputStream xml, ResourceLoader resourceLoader) throws ParsingException {
|
||||
CryptoIntegration.init(DeploymentBuilder.class.getClassLoader());
|
||||
DefaultSamlDeployment deployment = new DefaultSamlDeployment();
|
||||
DefaultSamlDeployment.DefaultIDP defaultIDP = new DefaultSamlDeployment.DefaultIDP();
|
||||
DefaultSamlDeployment.DefaultSingleSignOnService sso = new DefaultSamlDeployment.DefaultSingleSignOnService();
|
||||
|
|
34
core/pom.xml
34
core/pom.xml
|
@ -32,8 +32,6 @@
|
|||
<description/>
|
||||
|
||||
<properties>
|
||||
<!-- We still need to support EAP 8, set the Java version to 11. -->
|
||||
<maven.compiler.release>11</maven.compiler.release>
|
||||
<timestamp>${maven.build.timestamp}</timestamp>
|
||||
<maven.build.timestamp.format>yyyy-MM-dd HH:mm</maven.build.timestamp.format>
|
||||
</properties>
|
||||
|
@ -74,38 +72,6 @@
|
|||
</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>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
|
|
|
@ -41,10 +41,13 @@ public abstract class AbstractJWKParser {
|
|||
}
|
||||
|
||||
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.equals(KeyType.RSA)) {
|
||||
if (KeyType.RSA.equals(keyType)) {
|
||||
return createRSAPublicKey();
|
||||
} else if (keyType.equals(KeyType.EC)) {
|
||||
} else if (KeyType.EC.equals(keyType)) {
|
||||
return createECPublicKey();
|
||||
|
||||
} else {
|
||||
|
@ -87,7 +90,7 @@ public abstract class AbstractJWKParser {
|
|||
}
|
||||
|
||||
try {
|
||||
|
||||
|
||||
ECPoint point = new ECPoint(x, y);
|
||||
ECParameterSpec params = CryptoIntegration.getProvider().createECParams(name);
|
||||
ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(point, params);
|
||||
|
|
|
@ -17,15 +17,27 @@
|
|||
|
||||
package org.keycloak.jose.jwk;
|
||||
|
||||
import org.keycloak.crypto.KeyUse;
|
||||
|
||||
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();
|
||||
}
|
||||
|
@ -42,13 +54,55 @@ public class JWKBuilder extends AbstractJWKBuilder {
|
|||
|
||||
@Override
|
||||
public JWK okp(Key key) {
|
||||
// not supported if jdk vesion < 17
|
||||
throw new UnsupportedOperationException("EdDSA algorithms not supported in this JDK version");
|
||||
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");
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,6 +17,18 @@
|
|||
|
||||
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;
|
||||
|
||||
/**
|
||||
|
@ -24,17 +36,17 @@ import org.keycloak.util.JsonSerialization;
|
|||
*/
|
||||
public class JWKParser extends AbstractJWKParser {
|
||||
|
||||
protected JWKParser() {
|
||||
}
|
||||
|
||||
public JWKParser(JWK jwk) {
|
||||
this.jwk = jwk;
|
||||
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);
|
||||
}
|
||||
|
@ -48,4 +60,68 @@ public class JWKParser extends AbstractJWKParser {
|
|||
}
|
||||
}
|
||||
|
||||
@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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,108 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 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:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</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));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,124 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 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:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</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() {
|
||||
String keyType = jwk.getKeyType();
|
||||
if (keyType.equals(KeyType.RSA)) {
|
||||
return createRSAPublicKey();
|
||||
} else if (keyType.equals(KeyType.EC)) {
|
||||
return createECPublicKey();
|
||||
} else if (keyType.equals(KeyType.OKP)) {
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
|
@ -30,11 +30,6 @@
|
|||
<name>Keycloak Crypto Default</name>
|
||||
<description/>
|
||||
|
||||
<properties>
|
||||
<!-- We still need to support EAP 8, set the Java version to 11. -->
|
||||
<maven.compiler.release>11</maven.compiler.release>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
|
||||
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<module xmlns="urn:jboss:module:1.3" name="org.keycloak.keycloak-core">
|
||||
<resources>
|
||||
<artifact name="${org.keycloak:keycloak-core}"/>
|
||||
</resources>
|
||||
<dependencies>
|
||||
<module name="com.fasterxml.jackson.core.jackson-annotations"/>
|
||||
<module name="com.fasterxml.jackson.core.jackson-core"/>
|
||||
<module name="com.fasterxml.jackson.core.jackson-databind"/>
|
||||
<module name="com.fasterxml.jackson.jaxrs.jackson-jaxrs-json-provider"/>
|
||||
<module name="org.keycloak.keycloak-common" />
|
||||
<module name="org.jboss.logging"/>
|
||||
<module name="javax.api"/>
|
||||
<module name="jakarta.activation.api"/>
|
||||
<module name="sun.jdk" optional="true" />
|
||||
</dependencies>
|
||||
|
||||
</module>
|
|
@ -1,40 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
|
||||
|
||||
<!--
|
||||
~ Copyright 2022 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.
|
||||
-->
|
||||
|
||||
<module xmlns="urn:jboss:module:1.3" name="org.keycloak.keycloak-crypto-default">
|
||||
<resources>
|
||||
<artifact name="${org.keycloak:keycloak-crypto-default}"/>
|
||||
</resources>
|
||||
<dependencies>
|
||||
<module name="com.fasterxml.jackson.core.jackson-annotations"/>
|
||||
<module name="com.fasterxml.jackson.core.jackson-core"/>
|
||||
<module name="com.fasterxml.jackson.core.jackson-databind"/>
|
||||
<module name="com.fasterxml.jackson.jaxrs.jackson-jaxrs-json-provider"/>
|
||||
<module name="org.keycloak.keycloak-common" />
|
||||
<module name="org.keycloak.keycloak-core" />
|
||||
<module name="org.bouncycastle" />
|
||||
<module name="org.jboss.logging"/>
|
||||
<module name="javax.api"/>
|
||||
<module name="jakarta.activation.api"/>
|
||||
<module name="sun.jdk" optional="true" />
|
||||
</dependencies>
|
||||
|
||||
</module>
|
|
@ -35,10 +35,9 @@
|
|||
<module name="org.keycloak.keycloak-saml-core-public"/>
|
||||
<module name="org.keycloak.keycloak-saml-core"/>
|
||||
<module name="org.keycloak.keycloak-common"/>
|
||||
<module name="org.keycloak.keycloak-core"/>
|
||||
<module name="org.keycloak.keycloak-crypto-default" services="import"/>
|
||||
<module name="org.apache.httpcomponents"/>
|
||||
<module name="org.apache.santuario.xmlsec"/>
|
||||
<module name="org.bouncycastle" />
|
||||
</dependencies>
|
||||
|
||||
</module>
|
||||
|
|
|
@ -42,7 +42,7 @@ import org.keycloak.util.JsonSerialization;
|
|||
*
|
||||
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
|
||||
*/
|
||||
public abstract class ServerJWKTest {
|
||||
public class ServerJWKTest {
|
||||
|
||||
@ClassRule
|
||||
public static CryptoInitRule cryptoInitRule = new CryptoInitRule();
|
||||
|
|
Loading…
Reference in a new issue