KEYCLOAK-7560 Refactor token signature SPI PR

Also incorporates:
KEYCLOAK-6770 ES256/384/512 providers
KEYCLOAK-4622 Use HS256 for refresh tokens
KEYCLOAK-4623 Use HS256 for client reg tokens
This commit is contained in:
stianst 2018-08-20 13:14:33 +02:00 committed by Stian Thorgersen
parent 5b6036525c
commit 24e60747b6
190 changed files with 3240 additions and 2076 deletions

View file

@ -27,6 +27,7 @@ import java.security.PublicKey;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
@Deprecated
public class RSATokenVerifier {
private final TokenVerifier<AccessToken> tokenVerifier;

View file

@ -0,0 +1,26 @@
/*
* 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;
import com.fasterxml.jackson.annotation.JsonIgnore;
public interface Token {
@JsonIgnore
TokenCategory getCategory();
}

View file

@ -0,0 +1,25 @@
/*
* 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;
public enum TokenCategory {
INTERNAL,
ACCESS,
ID,
ADMIN,
USERINFO
}

View file

@ -24,7 +24,7 @@ import org.keycloak.jose.jws.AlgorithmType;
import org.keycloak.jose.jws.JWSHeader;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.jose.jws.JWSSignatureProvider;
import org.keycloak.crypto.SignatureVerifierContext;
import org.keycloak.jose.jws.crypto.HMACProvider;
import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.representations.JsonWebToken;
@ -32,7 +32,6 @@ import org.keycloak.util.TokenUtil;
import javax.crypto.SecretKey;
import java.security.Key;
import java.security.PublicKey;
import java.util.*;
import java.util.logging.Level;
@ -147,15 +146,10 @@ public class TokenVerifier<T extends JsonWebToken> {
private JWSInput jws;
private T token;
// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
private Key verifyKey = null;
private JWSSignatureProvider signatureProvider = null;
public TokenVerifier<T> verifyKey(Key verifyKey) {
this.verifyKey = verifyKey;
return this;
}
public TokenVerifier<T> signatureProvider(JWSSignatureProvider signatureProvider) {
this.signatureProvider = signatureProvider;
private SignatureVerifierContext verifier = null;
public TokenVerifier<T> verifierContext(SignatureVerifierContext verifier) {
this.verifier = verifier;
return this;
}
@ -352,40 +346,39 @@ public class TokenVerifier<T extends JsonWebToken> {
}
public void verifySignature() throws VerificationException {
// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
if (this.signatureProvider != null && this.verify() != null) {
verifySignatureByProvider();
return;
}
AlgorithmType algorithmType = getHeader().getAlgorithm().getType();
if (null == algorithmType) {
throw new VerificationException("Unknown or unsupported token algorithm");
} else switch (algorithmType) {
case RSA:
if (publicKey == null) {
throw new VerificationException("Public key not set");
}
if (!RSAProvider.verify(jws, publicKey)) {
if (this.verifier != null) {
try {
if (!verifier.verify(jws.getEncodedSignatureInput().getBytes("UTF-8"), jws.getSignature())) {
throw new TokenSignatureInvalidException(token, "Invalid token signature");
} break;
case HMAC:
if (secretKey == null) {
throw new VerificationException("Secret key not set");
}
if (!HMACProvider.verify(jws, secretKey)) {
throw new TokenSignatureInvalidException(token, "Invalid token signature");
} break;
default:
} catch (Exception e) {
throw new VerificationException(e);
}
} else {
AlgorithmType algorithmType = getHeader().getAlgorithm().getType();
if (null == algorithmType) {
throw new VerificationException("Unknown or unsupported token algorithm");
}
}
// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
private void verifySignatureByProvider() throws VerificationException {
if (!signatureProvider.verify(jws, verifyKey)) {
throw new TokenSignatureInvalidException(token, "Invalid token signature");
} else switch (algorithmType) {
case RSA:
if (publicKey == null) {
throw new VerificationException("Public key not set");
}
if (!RSAProvider.verify(jws, publicKey)) {
throw new TokenSignatureInvalidException(token, "Invalid token signature");
}
break;
case HMAC:
if (secretKey == null) {
throw new VerificationException("Secret key not set");
}
if (!HMACProvider.verify(jws, secretKey)) {
throw new TokenSignatureInvalidException(token, "Invalid token signature");
}
break;
default:
throw new VerificationException("Unknown or unsupported token algorithm");
}
}
}
@ -440,7 +433,7 @@ public class TokenVerifier<T extends JsonWebToken> {
public static <T extends JsonWebToken> Predicate<T> alternative(final Predicate<? super T>... predicates) {
return new Predicate<T>() {
@Override
public boolean test(T t) throws VerificationException {
public boolean test(T t) {
for (Predicate<? super T> predicate : predicates) {
try {
if (predicate.test(t)) {

View file

@ -0,0 +1,52 @@
/*
* 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.crypto;
import java.security.PrivateKey;
import java.security.Signature;
public class AsymmetricSignatureSignerContext implements SignatureSignerContext {
private final KeyWrapper key;
public AsymmetricSignatureSignerContext(KeyWrapper key) throws SignatureException {
this.key = key;
}
@Override
public String getKid() {
return key.getKid();
}
@Override
public String getAlgorithm() {
return key.getAlgorithm();
}
@Override
public byte[] sign(byte[] data) throws SignatureException {
try {
Signature signature = Signature.getInstance(JavaAlgorithm.getJavaAlgorithm(key.getAlgorithm()));
signature.initSign((PrivateKey) key.getSignKey());
signature.update(data);
return signature.sign();
} catch (Exception e) {
throw new SignatureException("Signing failed", e);
}
}
}

View file

@ -0,0 +1,54 @@
/*
* 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.crypto;
import org.keycloak.common.VerificationException;
import java.security.PublicKey;
import java.security.Signature;
public class AsymmetricSignatureVerifierContext implements SignatureVerifierContext {
private final KeyWrapper key;
public AsymmetricSignatureVerifierContext(KeyWrapper key) {
this.key = key;
}
@Override
public String getKid() {
return key.getKid();
}
@Override
public String getAlgorithm() {
return key.getAlgorithm();
}
@Override
public boolean verify(byte[] data, byte[] signature) throws VerificationException {
try {
Signature verifier = Signature.getInstance(JavaAlgorithm.getJavaAlgorithm(key.getAlgorithm()));
verifier.initVerify((PublicKey) key.getVerifyKey());
verifier.update(data);
return verifier.verify(signature);
} catch (Exception e) {
throw new VerificationException("Signing failed", e);
}
}
}

View file

@ -18,24 +18,41 @@ package org.keycloak.crypto;
public class JavaAlgorithm {
public static final String RS256 = "SHA256withRSA";
public static final String RS384 = "SHA384withRSA";
public static final String RS512 = "SHA512withRSA";
public static final String HS256 = "HMACSHA256";
public static final String HS384 = "HMACSHA384";
public static final String HS512 = "HMACSHA512";
public static final String ES256 = "SHA256withECDSA";
public static final String ES384 = "SHA384withECDSA";
public static final String ES512 = "SHA512withECDSA";
public static final String AES = "AES";
public static String getJavaAlgorithm(String algorithm) {
switch (algorithm) {
case Algorithm.RS256:
return "SHA256withRSA";
return RS256;
case Algorithm.RS384:
return "SHA384withRSA";
return RS384;
case Algorithm.RS512:
return "SHA512withRSA";
return RS512;
case Algorithm.HS256:
return "HMACSHA256";
return HS256;
case Algorithm.HS384:
return "HMACSHA384";
return HS384;
case Algorithm.HS512:
return "HMACSHA512";
return HS512;
case Algorithm.ES256:
return ES256;
case Algorithm.ES384:
return ES384;
case Algorithm.ES512:
return ES512;
case Algorithm.AES:
return "AES";
return AES;
default:
throw new IllegalArgumentException("Unkown algorithm " + algorithm);
throw new IllegalArgumentException("Unknown algorithm " + algorithm);
}
}

View file

@ -19,17 +19,13 @@ package org.keycloak.crypto;
import javax.crypto.SecretKey;
import java.security.Key;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class KeyWrapper {
private String providerId;
private long providerPriority;
private String kid;
private Set<String> algorithms;
private String algorithm;
private String type;
private KeyUse use;
private KeyStatus status;
@ -62,19 +58,12 @@ public class KeyWrapper {
this.kid = kid;
}
public Set<String> getAlgorithms() {
return algorithms;
public String getAlgorithm() {
return algorithm;
}
public void setAlgorithms(String... algorithms) {
this.algorithms = new HashSet<>();
for (String a : algorithms) {
this.algorithms.add(a);
}
}
public void setAlgorithms(Set<String> algorithms) {
this.algorithms = algorithms;
public void setAlgorithm(String algorithm) {
this.algorithm = algorithm;
}
public String getType() {

View file

@ -0,0 +1,51 @@
/*
* 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.crypto;
import javax.crypto.Mac;
public class MacSignatureSignerContext implements SignatureSignerContext {
private final KeyWrapper key;
public MacSignatureSignerContext(KeyWrapper key) throws SignatureException {
this.key = key;
}
@Override
public String getKid() {
return key.getKid();
}
@Override
public String getAlgorithm() {
return key.getAlgorithm();
}
@Override
public byte[] sign(byte[] data) throws SignatureException {
try {
Mac mac = Mac.getInstance(JavaAlgorithm.getJavaAlgorithm(key.getAlgorithm()));
mac.init(key.getSecretKey());
mac.update(data);
return mac.doFinal();
} catch (Exception e) {
throw new SignatureException("Signing failed", e);
}
}
}

View file

@ -0,0 +1,55 @@
/*
* 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.crypto;
import org.keycloak.common.VerificationException;
import javax.crypto.Mac;
import java.security.MessageDigest;
public class MacSignatureVerifierContext implements SignatureVerifierContext {
private final KeyWrapper key;
public MacSignatureVerifierContext(KeyWrapper key) {
this.key = key;
}
@Override
public String getKid() {
return key.getKid();
}
@Override
public String getAlgorithm() {
return key.getAlgorithm();
}
@Override
public boolean verify(byte[] data, byte[] signature) throws VerificationException {
try {
Mac mac = Mac.getInstance(JavaAlgorithm.getJavaAlgorithm(key.getAlgorithm()));
mac.init(key.getSecretKey());
mac.update(data);
byte[] verificationSignature = mac.doFinal();
return MessageDigest.isEqual(verificationSignature, signature);
} catch (Exception e) {
throw new VerificationException("Signing failed", e);
}
}
}

View file

@ -0,0 +1,29 @@
/*
* 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.crypto;
public class SignatureException extends RuntimeException {
public SignatureException(String message) {
super(message);
}
public SignatureException(String message, Throwable cause) {
super(message, cause);
}
}

View file

@ -0,0 +1,27 @@
/*
* 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.crypto;
public interface SignatureSignerContext {
String getKid();
String getAlgorithm();
byte[] sign(byte[] data) throws SignatureException;
}

View file

@ -0,0 +1,29 @@
/*
* 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.crypto;
import org.keycloak.common.VerificationException;
public interface SignatureVerifierContext {
String getKid();
String getAlgorithm();
boolean verify(byte[] data, byte[] signature) throws VerificationException;
}

View file

@ -0,0 +1,63 @@
/*
* 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 com.fasterxml.jackson.annotation.JsonProperty;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class ECPublicJWK extends JWK {
public static final String CRV = "crv";
public static final String X = "x";
public static final String Y = "y";
@JsonProperty(CRV)
private String crv;
@JsonProperty(X)
private String x;
@JsonProperty(Y)
private String y;
public String getCrv() {
return crv;
}
public void setCrv(String crv) {
this.crv = crv;
}
public String getX() {
return x;
}
public void setX(String x) {
this.x = x;
}
public String getY() {
return y;
}
public void setY(String y) {
this.y = y;
}
}

View file

@ -19,9 +19,13 @@ package org.keycloak.jose.jwk;
import org.keycloak.common.util.Base64Url;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyType;
import java.math.BigInteger;
import java.security.Key;
import java.security.PublicKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPublicKey;
/**
@ -30,10 +34,11 @@ import java.security.interfaces.RSAPublicKey;
public class JWKBuilder {
public static final String DEFAULT_PUBLIC_KEY_USE = "sig";
public static final String DEFAULT_MESSAGE_DIGEST = "SHA-256";
private String kid;
private String algorithm;
private JWKBuilder() {
}
@ -46,15 +51,25 @@ public class JWKBuilder {
return this;
}
public JWKBuilder algorithm(String algorithm) {
this.algorithm = algorithm;
return this;
}
public JWK rs256(PublicKey key) {
algorithm(Algorithm.RS256);
return rsa(key);
}
public JWK rsa(Key key) {
RSAPublicKey rsaKey = (RSAPublicKey) key;
RSAPublicJWK k = new RSAPublicJWK();
String kid = this.kid != null ? this.kid : KeyUtils.createKeyId(key);
k.setKeyId(kid);
k.setKeyType(RSAPublicJWK.RSA);
k.setAlgorithm(RSAPublicJWK.RS256);
k.setKeyType(KeyType.RSA);
k.setAlgorithm(algorithm);
k.setPublicKeyUse(DEFAULT_PUBLIC_KEY_USE);
k.setModulus(Base64Url.encode(toIntegerBytes(rsaKey.getModulus())));
k.setPublicExponent(Base64Url.encode(toIntegerBytes(rsaKey.getPublicExponent())));
@ -62,6 +77,24 @@ public class JWKBuilder {
return k;
}
public JWK ec(Key key) {
ECPublicKey ecKey = (ECPublicKey) key;
ECPublicJWK k = new ECPublicJWK();
String kid = this.kid != null ? this.kid : KeyUtils.createKeyId(key);
k.setKeyId(kid);
k.setKeyType(KeyType.EC);
k.setAlgorithm(algorithm);
k.setPublicKeyUse(DEFAULT_PUBLIC_KEY_USE);
k.setCrv("P-" + ecKey.getParams().getCurve().getField().getFieldSize());
k.setX(Base64Url.encode(ecKey.getW().getAffineX().toByteArray()));
k.setY(Base64Url.encode(ecKey.getW().getAffineY().toByteArray()));
return k;
}
/**
* Copied from org.apache.commons.codec.binary.Base64
*/

View file

@ -18,12 +18,18 @@
package org.keycloak.jose.jwk;
import com.fasterxml.jackson.core.type.TypeReference;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
import org.keycloak.common.util.Base64Url;
import org.keycloak.crypto.KeyType;
import org.keycloak.util.JsonSerialization;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.ECPoint;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.Map;
@ -66,20 +72,61 @@ public class JWKParser {
public PublicKey toPublicKey() {
String keyType = jwk.getKeyType();
if (isKeyTypeSupported(keyType)) {
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()));
if (keyType.equals(KeyType.RSA)) {
return createRSAPublicKey();
} else if (keyType.equals(KeyType.EC)) {
return createECPublicKey();
try {
return KeyFactory.getInstance("RSA").generatePublic(new RSAPublicKeySpec(modulus, publicExponent));
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
throw new RuntimeException("Unsupported keyType " + keyType);
}
}
private PublicKey createECPublicKey() {
String crv = (String) jwk.getOtherClaims().get(ECPublicJWK.CRV);
BigInteger x = new BigInteger(1, Base64Url.decode((String) jwk.getOtherClaims().get(ECPublicJWK.X)));
BigInteger y = new BigInteger(1, Base64Url.decode((String) jwk.getOtherClaims().get(ECPublicJWK.Y)));
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 {
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec(name);
ECNamedCurveSpec params = new ECNamedCurveSpec("prime256v1", spec.getCurve(), spec.getG(), spec.getN());
ECPoint point = new ECPoint(x, y);
ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(point, params);
KeyFactory kf = KeyFactory.getInstance("ECDSA");
return kf.generatePublic(pubKeySpec);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private 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);
}

View file

@ -24,6 +24,7 @@ import org.keycloak.jose.jws.crypto.SignatureProvider;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
@Deprecated
public enum Algorithm {
none(null, null),

View file

@ -18,6 +18,7 @@
package org.keycloak.jose.jws;
import org.keycloak.common.util.Base64Url;
import org.keycloak.crypto.SignatureSignerContext;
import org.keycloak.jose.jws.crypto.HMACProvider;
import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.util.JsonSerialization;
@ -25,7 +26,6 @@ import org.keycloak.util.JsonSerialization;
import javax.crypto.SecretKey;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.Key;
import java.security.PrivateKey;
/**
@ -37,7 +37,7 @@ public class JWSBuilder {
String kid;
String contentType;
byte[] contentBytes;
public JWSBuilder type(String type) {
this.type = type;
return this;
@ -67,31 +67,7 @@ public class JWSBuilder {
return new EncodingBuilder();
}
protected String encodeAll(StringBuffer encoding, byte[] signature) {
encoding.append('.');
if (signature != null) {
encoding.append(Base64Url.encode(signature));
}
return encoding.toString();
}
protected void encode(Algorithm alg, byte[] data, StringBuffer encoding) {
// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
encode(alg.name(), data, encoding);
}
protected byte[] marshalContent() {
return contentBytes;
}
// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
protected void encode(String sigAlgName, byte[] data, StringBuffer encoding) {
encoding.append(encodeHeader(sigAlgName));
encoding.append('.');
encoding.append(Base64Url.encode(data));
}
// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
protected String encodeHeader(String sigAlgName) {
StringBuilder builder = new StringBuilder("{");
builder.append("\"alg\":\"").append(sigAlgName).append("\"");
@ -107,30 +83,55 @@ public class JWSBuilder {
}
}
public class EncodingBuilder {
public String none() {
StringBuffer buffer = new StringBuffer();
byte[] data = marshalContent();
encode(Algorithm.none, data, buffer);
return encodeAll(buffer, null);
protected String encodeAll(StringBuilder encoding, byte[] signature) {
encoding.append('.');
if (signature != null) {
encoding.append(Base64Url.encode(signature));
}
return encoding.toString();
}
// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
public String sign(JWSSignatureProvider signatureProvider, String sigAlgName, Key key) {
StringBuffer buffer = new StringBuffer();
protected void encode(Algorithm alg, byte[] data, StringBuilder encoding) {
encode(alg.name(), data, encoding);
}
protected void encode(String sigAlgName, byte[] data, StringBuilder encoding) {
encoding.append(encodeHeader(sigAlgName));
encoding.append('.');
encoding.append(Base64Url.encode(data));
}
protected byte[] marshalContent() {
return contentBytes;
}
public class EncodingBuilder {
public String sign(SignatureSignerContext signer) {
kid = signer.getKid();
StringBuilder buffer = new StringBuilder();
byte[] data = marshalContent();
encode(sigAlgName, data, buffer);
encode(signer.getAlgorithm(), data, buffer);
byte[] signature = null;
try {
signature = signatureProvider.sign(buffer.toString().getBytes("UTF-8"), sigAlgName, key);
} catch (UnsupportedEncodingException e) {
signature = signer.sign(buffer.toString().getBytes("UTF-8"));
} catch (Exception e) {
throw new RuntimeException(e);
}
return encodeAll(buffer, signature);
}
public String none() {
StringBuilder buffer = new StringBuilder();
byte[] data = marshalContent();
encode(Algorithm.none, data, buffer);
return encodeAll(buffer, null);
}
@Deprecated
public String sign(Algorithm algorithm, PrivateKey privateKey) {
StringBuffer buffer = new StringBuffer();
StringBuilder buffer = new StringBuilder();
byte[] data = marshalContent();
encode(algorithm, data, buffer);
byte[] signature = null;
@ -142,20 +143,24 @@ public class JWSBuilder {
return encodeAll(buffer, signature);
}
@Deprecated
public String rsa256(PrivateKey privateKey) {
return sign(Algorithm.RS256, privateKey);
}
@Deprecated
public String rsa384(PrivateKey privateKey) {
return sign(Algorithm.RS384, privateKey);
}
@Deprecated
public String rsa512(PrivateKey privateKey) {
return sign(Algorithm.RS512, privateKey);
}
@Deprecated
public String hmac256(byte[] sharedSecret) {
StringBuffer buffer = new StringBuffer();
StringBuilder buffer = new StringBuilder();
byte[] data = marshalContent();
encode(Algorithm.HS256, data, buffer);
byte[] signature = null;
@ -167,8 +172,9 @@ public class JWSBuilder {
return encodeAll(buffer, signature);
}
@Deprecated
public String hmac384(byte[] sharedSecret) {
StringBuffer buffer = new StringBuffer();
StringBuilder buffer = new StringBuilder();
byte[] data = marshalContent();
encode(Algorithm.HS384, data, buffer);
byte[] signature = null;
@ -180,8 +186,9 @@ public class JWSBuilder {
return encodeAll(buffer, signature);
}
@Deprecated
public String hmac512(byte[] sharedSecret) {
StringBuffer buffer = new StringBuffer();
StringBuilder buffer = new StringBuilder();
byte[] data = marshalContent();
encode(Algorithm.HS512, data, buffer);
byte[] signature = null;
@ -193,8 +200,9 @@ public class JWSBuilder {
return encodeAll(buffer, signature);
}
@Deprecated
public String hmac256(SecretKey sharedSecret) {
StringBuffer buffer = new StringBuffer();
StringBuilder buffer = new StringBuilder();
byte[] data = marshalContent();
encode(Algorithm.HS256, data, buffer);
byte[] signature = null;
@ -206,8 +214,9 @@ public class JWSBuilder {
return encodeAll(buffer, signature);
}
@Deprecated
public String hmac384(SecretKey sharedSecret) {
StringBuffer buffer = new StringBuffer();
StringBuilder buffer = new StringBuilder();
byte[] data = marshalContent();
encode(Algorithm.HS384, data, buffer);
byte[] signature = null;
@ -219,8 +228,9 @@ public class JWSBuilder {
return encodeAll(buffer, signature);
}
@Deprecated
public String hmac512(SecretKey sharedSecret) {
StringBuffer buffer = new StringBuffer();
StringBuilder buffer = new StringBuilder();
byte[] data = marshalContent();
encode(Algorithm.HS512, data, buffer);
byte[] signature = null;

View file

@ -1,9 +0,0 @@
package org.keycloak.jose.jws;
import java.security.Key;
public interface JWSSignatureProvider {
// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
byte[] sign(byte[] data, String sigAlgName, Key key);
boolean verify(JWSInput input, Key key);
}

View file

@ -25,7 +25,6 @@ import org.keycloak.jose.jws.JWSInput;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

View file

@ -27,7 +27,7 @@ import java.util.Arrays;
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class HashProvider {
// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
public static String oidcHash(String jwtAlgorithmName, String input) {
byte[] digest = digest(jwtAlgorithmName, input);

View file

@ -19,6 +19,7 @@ package org.keycloak.representations;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.keycloak.TokenCategory;
import org.keycloak.representations.idm.authorization.Permission;
import java.io.Serializable;
@ -270,4 +271,10 @@ public class AccessToken extends IDToken {
public void setScope(String scope) {
this.scope = scope;
}
@Override
public TokenCategory getCategory() {
return TokenCategory.ACCESS;
}
}

View file

@ -18,6 +18,7 @@
package org.keycloak.representations;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.keycloak.TokenCategory;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -357,4 +358,10 @@ public class IDToken extends JsonWebToken {
public void setStateHash(String stateHash) {
this.stateHash = stateHash;
}
@Override
public TokenCategory getCategory() {
return TokenCategory.ID;
}
}

View file

@ -23,6 +23,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.keycloak.Token;
import org.keycloak.TokenCategory;
import org.keycloak.common.util.Time;
import org.keycloak.json.StringOrArrayDeserializer;
import org.keycloak.json.StringOrArraySerializer;
@ -35,7 +37,7 @@ import java.util.Map;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class JsonWebToken implements Serializable {
public class JsonWebToken implements Serializable, Token {
@JsonProperty("jti")
protected String id;
@JsonProperty("exp")
@ -209,4 +211,9 @@ public class JsonWebToken implements Serializable {
public void setOtherClaims(String name, Object value) {
otherClaims.put(name, value);
}
@Override
public TokenCategory getCategory() {
return TokenCategory.INTERNAL;
}
}

View file

@ -17,6 +17,7 @@
package org.keycloak.representations;
import org.keycloak.TokenCategory;
import org.keycloak.util.TokenUtil;
import java.util.HashMap;
@ -58,4 +59,8 @@ public class RefreshToken extends AccessToken {
}
}
@Override
public TokenCategory getCategory() {
return TokenCategory.INTERNAL;
}
}

View file

@ -18,6 +18,8 @@
package org.keycloak.representations.adapters.action;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.keycloak.Token;
import org.keycloak.TokenCategory;
import org.keycloak.common.util.Time;
/**
@ -26,7 +28,7 @@ import org.keycloak.common.util.Time;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public abstract class AdminAction {
public abstract class AdminAction implements Token {
protected String id;
protected int expiration;
protected String resource;
@ -85,4 +87,9 @@ public abstract class AdminAction {
}
public abstract boolean validate();
@Override
public TokenCategory getCategory() {
return TokenCategory.ADMIN;
}
}

View file

@ -55,7 +55,7 @@ public class KeysMetadataRepresentation {
private String status;
private String type;
private Set<String> algorithms;
private String algorithm;
private String publicKey;
private String certificate;
@ -100,12 +100,12 @@ public class KeysMetadataRepresentation {
this.type = type;
}
public Set<String> getAlgorithms() {
return algorithms;
public String getAlgorithm() {
return algorithm;
}
public void setAlgorithms(Set<String> algorithms) {
this.algorithms = algorithms;
public void setAlgorithm(String algorithm) {
this.algorithm = algorithm;
}
public String getPublicKey() {

View file

@ -37,6 +37,7 @@ public class RealmRepresentation {
protected String displayName;
protected String displayNameHtml;
protected Integer notBefore;
protected String defaultSignatureAlgorithm;
protected Boolean revokeRefreshToken;
protected Integer refreshTokenMaxReuse;
protected Integer accessTokenLifespan;
@ -243,6 +244,14 @@ public class RealmRepresentation {
this.sslRequired = sslRequired;
}
public String getDefaultSignatureAlgorithm() {
return defaultSignatureAlgorithm;
}
public void setDefaultSignatureAlgorithm(String defaultSignatureAlgorithm) {
this.defaultSignatureAlgorithm = defaultSignatureAlgorithm;
}
public Boolean getRevokeRefreshToken() {
return revokeRefreshToken;
}

View file

@ -1,75 +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 org.junit.Test;
import org.keycloak.util.JsonSerialization;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class JWKBuilderTest {
@Test
public void publicRs256() throws Exception {
PublicKey publicKey = KeyPairGenerator.getInstance("RSA").generateKeyPair().getPublic();
JWK jwk = JWKBuilder.create().rs256(publicKey);
assertNotNull(jwk.getKeyId());
assertEquals("RSA", jwk.getKeyType());
assertEquals("RS256", jwk.getAlgorithm());
assertEquals("sig", jwk.getPublicKeyUse());
assertTrue(jwk instanceof RSAPublicJWK);
assertNotNull(((RSAPublicJWK) jwk).getModulus());
assertNotNull(((RSAPublicJWK) jwk).getPublicExponent());
String jwkJson = JsonSerialization.writeValueAsString(jwk);
// Parse
assertArrayEquals(publicKey.getEncoded(), JWKParser.create().parse(jwkJson).toPublicKey().getEncoded());
}
@Test
public void parse() throws NoSuchAlgorithmException, InvalidKeySpecException {
String jwkJson = "{" +
" \"kty\": \"RSA\"," +
" \"alg\": \"RS256\"," +
" \"use\": \"sig\"," +
" \"kid\": \"3121adaa80ace09f89d80899d4a5dc4ce33d0747\"," +
" \"n\": \"soFDjoZ5mQ8XAA7reQAFg90inKAHk0DXMTizo4JuOsgzUbhcplIeZ7ks83hsEjm8mP8lUVaHMPMAHEIp3gu6Xxsg-s73ofx1dtt_Fo7aj8j383MFQGl8-FvixTVobNeGeC0XBBQjN8lEl-lIwOa4ZoERNAShplTej0ntDp7TQm0=\"," +
" \"e\": \"AQAB\"" +
" }";
PublicKey key = JWKParser.create().parse(jwkJson).toPublicKey();
assertEquals("RSA", key.getAlgorithm());
assertEquals("X.509", key.getFormat());
}
}

View file

@ -0,0 +1,136 @@
/*
* 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 org.junit.Test;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.crypto.JavaAlgorithm;
import org.keycloak.util.JsonSerialization;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.Signature;
import java.security.spec.ECGenParameterSpec;
import static org.junit.Assert.*;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class JWKTest {
@Test
public void publicRs256() throws Exception {
KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
PublicKey publicKey = keyPair.getPublic();
JWK jwk = JWKBuilder.create().kid(KeyUtils.createKeyId(publicKey)).algorithm("RS256").rsa(publicKey);
assertNotNull(jwk.getKeyId());
assertEquals("RSA", jwk.getKeyType());
assertEquals("RS256", jwk.getAlgorithm());
assertEquals("sig", jwk.getPublicKeyUse());
assertTrue(jwk instanceof RSAPublicJWK);
assertNotNull(((RSAPublicJWK) jwk).getModulus());
assertNotNull(((RSAPublicJWK) jwk).getPublicExponent());
String jwkJson = JsonSerialization.writeValueAsString(jwk);
PublicKey publicKeyFromJwk = JWKParser.create().parse(jwkJson).toPublicKey();
// Parse
assertArrayEquals(publicKey.getEncoded(), publicKeyFromJwk.getEncoded());
byte[] data = "Some test string".getBytes("utf-8");
byte[] sign = sign(data, JavaAlgorithm.RS256, keyPair.getPrivate());
verify(data, sign, JavaAlgorithm.RS256, publicKeyFromJwk);
}
@Test
public void publicEs256() throws Exception {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
SecureRandom randomGen = SecureRandom.getInstance("SHA1PRNG");
ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1");
keyGen.initialize(ecSpec, randomGen);
KeyPair keyPair = keyGen.generateKeyPair();
PublicKey publicKey = keyPair.getPublic();
JWK jwk = JWKBuilder.create().kid(KeyUtils.createKeyId(keyPair.getPublic())).algorithm("ES256").ec(publicKey);
assertEquals("EC", jwk.getKeyType());
assertEquals("ES256", jwk.getAlgorithm());
assertEquals("sig", jwk.getPublicKeyUse());
assertTrue(jwk instanceof ECPublicJWK);
assertNotNull(((ECPublicJWK) jwk).getCrv());
assertNotNull(((ECPublicJWK) jwk).getX());
assertNotNull(((ECPublicJWK) jwk).getY());
String jwkJson = JsonSerialization.writeValueAsString(jwk);
JWKParser parser = JWKParser.create().parse(jwkJson);
PublicKey publicKeyFromJwk = parser.toPublicKey();
assertArrayEquals(publicKey.getEncoded(), publicKeyFromJwk.getEncoded());
byte[] data = "Some test string".getBytes("utf-8");
byte[] sign = sign(data, JavaAlgorithm.ES256, keyPair.getPrivate());
verify(data, sign, JavaAlgorithm.ES256, publicKeyFromJwk);
}
@Test
public void parse() {
String jwkJson = "{" +
" \"kty\": \"RSA\"," +
" \"alg\": \"RS256\"," +
" \"use\": \"sig\"," +
" \"kid\": \"3121adaa80ace09f89d80899d4a5dc4ce33d0747\"," +
" \"n\": \"soFDjoZ5mQ8XAA7reQAFg90inKAHk0DXMTizo4JuOsgzUbhcplIeZ7ks83hsEjm8mP8lUVaHMPMAHEIp3gu6Xxsg-s73ofx1dtt_Fo7aj8j383MFQGl8-FvixTVobNeGeC0XBBQjN8lEl-lIwOa4ZoERNAShplTej0ntDp7TQm0=\"," +
" \"e\": \"AQAB\"" +
" }";
PublicKey key = JWKParser.create().parse(jwkJson).toPublicKey();
assertEquals("RSA", key.getAlgorithm());
assertEquals("X.509", key.getFormat());
}
private byte[] sign(byte[] data, String javaAlgorithm, PrivateKey key) throws Exception {
Signature signature = Signature.getInstance(javaAlgorithm);
signature.initSign(key);
signature.update(data);
return signature.sign();
}
private boolean verify(byte[] data, byte[] signature, String javaAlgorithm, PublicKey key) throws Exception {
Signature verifier = Signature.getInstance(javaAlgorithm);
verifier.initVerify(key);
verifier.update(data);
return verifier.verify(signature);
}
}

View file

@ -88,6 +88,8 @@
-->
<include>org/keycloak/representations/idm/**</include>
<include>org/keycloak/representations/JsonWebToken.class</include>
<include>org/keycloak/Token.class</include>
<include>org/keycloak/TokenCategory.class</include>
</includes>
</filter>
<filter>

View file

@ -76,6 +76,8 @@
<include>org/keycloak/representations/oidc/OIDCClientRepresentation.class</include>
<include>org/keycloak/representations/idm/authorization/**</include>
<include>org/keycloak/representations/JsonWebToken.class</include>
<include>org/keycloak/Token.class</include>
<include>org/keycloak/TokenCategory.class</include>
</includes>
</filter>
<filter>

View file

@ -201,6 +201,18 @@ public class RealmAdapter implements CachedRealmModel {
updated.setRememberMe(rememberMe);
}
@Override
public String getDefaultSignatureAlgorithm() {
if(isUpdated()) return updated.getDefaultSignatureAlgorithm();
return cached.getDefaultSignatureAlgorithm();
}
@Override
public void setDefaultSignatureAlgorithm(String defaultSignatureAlgorithm) {
getDelegateForUpdate();
updated.setDefaultSignatureAlgorithm(defaultSignatureAlgorithm);
}
@Override
public boolean isBruteForceProtected() {
if (isUpdated()) return updated.isBruteForceProtected();

View file

@ -74,6 +74,7 @@ public class CachedRealm extends AbstractExtendableRevisioned {
protected int failureFactor;
//--- end brute force settings
protected String defaultSignatureAlgorithm;
protected boolean revokeRefreshToken;
protected int refreshTokenMaxReuse;
protected int ssoSessionIdleTimeout;
@ -179,6 +180,7 @@ public class CachedRealm extends AbstractExtendableRevisioned {
failureFactor = model.getFailureFactor();
//--- end brute force settings
defaultSignatureAlgorithm = model.getDefaultSignatureAlgorithm();
revokeRefreshToken = model.isRevokeRefreshToken();
refreshTokenMaxReuse = model.getRefreshTokenMaxReuse();
ssoSessionIdleTimeout = model.getSsoSessionIdleTimeout();
@ -391,6 +393,10 @@ public class CachedRealm extends AbstractExtendableRevisioned {
return editUsernameAllowed;
}
public String getDefaultSignatureAlgorithm() {
return defaultSignatureAlgorithm;
}
public boolean isRevokeRefreshToken() {
return revokeRefreshToken;
}

View file

@ -245,6 +245,16 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
return result;
}
@Override
public String getDefaultSignatureAlgorithm() {
return getAttribute("defaultSignatureAlgorithm");
}
@Override
public void setDefaultSignatureAlgorithm(String defaultSignatureAlgorithm) {
setAttribute("defaultSignatureAlgorithm", defaultSignatureAlgorithm);
}
@Override
public boolean isBruteForceProtected() {
return getAttribute("bruteForceProtected", false);

View file

@ -0,0 +1,32 @@
/*
* 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.crypto;
import org.keycloak.common.VerificationException;
import org.keycloak.provider.Provider;
public interface SignatureProvider extends Provider {
SignatureSignerContext signer() throws SignatureException;
SignatureVerifierContext verifier(String kid) throws VerificationException;
@Override
default void close() {
}
}

View file

@ -0,0 +1,37 @@
/*
* 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.crypto;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderFactory;
public interface SignatureProviderFactory extends ProviderFactory<SignatureProvider> {
@Override
default void init(Config.Scope config) {
}
@Override
default void postInit(KeycloakSessionFactory factory) {
}
@Override
default void close() {
}
}

View file

@ -14,39 +14,32 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.crypto;
package org.keycloak.keys;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
import org.jboss.logging.Logger;
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 FailsafeHmacKeyProvider extends FailsafeSecretKeyProvider {
private static final Logger logger = Logger.getLogger(FailsafeHmacKeyProvider.class);
public class SignatureSpi implements Spi {
@Override
protected KeyUse getUse() {
return KeyUse.SIG;
public boolean isInternal() {
return true;
}
@Override
protected String getType() {
return KeyType.OCT;
public String getName() {
return "signature";
}
@Override
protected String getAlgorithm() {
return Algorithm.HS256;
public Class<? extends Provider> getProviderClass() {
return SignatureProvider.class;
}
@Override
protected Logger logger() {
return logger;
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return SignatureProviderFactory.class;
}
}

View file

@ -1,12 +0,0 @@
package org.keycloak.jose.jws;
import java.security.Key;
import org.keycloak.provider.Provider;
// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
public interface TokenSignatureProvider extends Provider {
byte[] sign(byte[] data, String sigAlgName, Key key);
boolean verify(JWSInput jws, Key verifyKey);
}

View file

@ -1,11 +0,0 @@
package org.keycloak.jose.jws;
import org.keycloak.component.ComponentFactory;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
public interface TokenSignatureProviderFactory<T extends TokenSignatureProvider> extends ComponentFactory<T, TokenSignatureProvider> {
T create(KeycloakSession session, ComponentModel model);
}

View file

@ -1,29 +0,0 @@
package org.keycloak.jose.jws;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
public class TokenSignatureSpi implements Spi {
@Override
public boolean isInternal() {
return true;
}
@Override
public String getName() {
return "tokenSignature";
}
@Override
public Class<? extends Provider> getProviderClass() {
return TokenSignatureProvider.class;
}
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return TokenSignatureProviderFactory.class;
}
}

View file

@ -20,6 +20,7 @@ package org.keycloak.keys;
import org.keycloak.Config;
import org.keycloak.component.ComponentFactory;
import org.keycloak.component.ComponentModel;
import org.keycloak.crypto.KeyUse;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
@ -30,6 +31,10 @@ public interface KeyProviderFactory<T extends KeyProvider> extends ComponentFact
T create(KeycloakSession session, ComponentModel model);
default boolean createFallbackKeys(KeycloakSession session, KeyUse keyUse, String algorithm) {
return false;
}
@Override
default void init(Config.Scope config) {
}

View file

@ -1,10 +0,0 @@
package org.keycloak.keys;
import java.security.Key;
// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
public interface SignatureKeyProvider {
Key getSignKey();
Key getVerifyKey(String kid);
}

View file

@ -19,6 +19,7 @@ package org.keycloak.models.utils;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.component.ComponentModel;
import org.keycloak.crypto.Algorithm;
import org.keycloak.keys.KeyProvider;
import org.keycloak.models.RealmModel;
@ -58,6 +59,7 @@ public class DefaultKeyProviders {
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
config.putSingle("priority", "100");
config.putSingle("algorithm", Algorithm.HS256);
generated.setConfig(config);
realm.addComponentModel(generated);

View file

@ -1,35 +0,0 @@
package org.keycloak.models.utils;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.component.ComponentModel;
import org.keycloak.jose.jws.TokenSignatureProvider;
import org.keycloak.models.RealmModel;
// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
public class DefaultTokenSignatureProviders {
private static final String COMPONENT_SIGNATURE_ALGORITHM_KEY = "org.keycloak.jose.jws.TokenSignatureProvider.algorithm";
private static final String RSASSA_PROVIDER_ID = "rsassa-signature";
private static final String HMAC_PROVIDER_ID = "hmac-signature";
public static void createProviders(RealmModel realm) {
createAndAddProvider(realm, RSASSA_PROVIDER_ID, "RS256");
createAndAddProvider(realm, RSASSA_PROVIDER_ID, "RS384");
createAndAddProvider(realm, RSASSA_PROVIDER_ID, "RS512");
createAndAddProvider(realm, HMAC_PROVIDER_ID, "HS256");
createAndAddProvider(realm, HMAC_PROVIDER_ID, "HS384");
createAndAddProvider(realm, HMAC_PROVIDER_ID, "HS512");
}
private static void createAndAddProvider(RealmModel realm, String providerId, String sigAlgName) {
ComponentModel generated = new ComponentModel();
generated.setName(providerId);
generated.setParentId(realm.getId());
generated.setProviderId(providerId);
generated.setProviderType(TokenSignatureProvider.class.getName());
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
config.putSingle(COMPONENT_SIGNATURE_ALGORITHM_KEY, sigAlgName);
generated.setConfig(config);
realm.addComponentModel(generated);
}
}

View file

@ -275,6 +275,7 @@ public class ModelToRepresentation {
rep.setDuplicateEmailsAllowed(realm.isDuplicateEmailsAllowed());
rep.setResetPasswordAllowed(realm.isResetPasswordAllowed());
rep.setEditUsernameAllowed(realm.isEditUsernameAllowed());
rep.setDefaultSignatureAlgorithm(realm.getDefaultSignatureAlgorithm());
rep.setRevokeRefreshToken(realm.isRevokeRefreshToken());
rep.setRefreshTokenMaxReuse(realm.getRefreshTokenMaxReuse());
rep.setAccessTokenLifespan(realm.getAccessTokenLifespan());

View file

@ -53,7 +53,6 @@ import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.UriUtils;
import org.keycloak.component.ComponentModel;
import org.keycloak.credential.CredentialModel;
import org.keycloak.jose.jws.TokenSignatureProvider;
import org.keycloak.keys.KeyProvider;
import org.keycloak.migration.MigrationProvider;
import org.keycloak.migration.migrators.MigrationUtils;
@ -175,6 +174,8 @@ public class RepresentationToModel {
if (rep.getNotBefore() != null) newRealm.setNotBefore(rep.getNotBefore());
if (rep.getDefaultSignatureAlgorithm() != null) newRealm.setDefaultSignatureAlgorithm(rep.getDefaultSignatureAlgorithm());
if (rep.getRevokeRefreshToken() != null) newRealm.setRevokeRefreshToken(rep.getRevokeRefreshToken());
else newRealm.setRevokeRefreshToken(false);
@ -421,12 +422,6 @@ public class RepresentationToModel {
DefaultKeyProviders.createProviders(newRealm);
}
}
// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
if (newRealm.getComponents(newRealm.getId(), TokenSignatureProvider.class.getName()).isEmpty()) {
DefaultTokenSignatureProviders.createProviders(newRealm);
}
}
public static void importUserFederationProvidersAndMappers(KeycloakSession session, RealmRepresentation rep, RealmModel newRealm) {
@ -912,6 +907,7 @@ public class RepresentationToModel {
if (rep.getActionTokenGeneratedByUserLifespan() != null)
realm.setActionTokenGeneratedByUserLifespan(rep.getActionTokenGeneratedByUserLifespan());
if (rep.getNotBefore() != null) realm.setNotBefore(rep.getNotBefore());
if (rep.getDefaultSignatureAlgorithm() != null) realm.setDefaultSignatureAlgorithm(rep.getDefaultSignatureAlgorithm());
if (rep.getRevokeRefreshToken() != null) realm.setRevokeRefreshToken(rep.getRevokeRefreshToken());
if (rep.getRefreshTokenMaxReuse() != null) realm.setRefreshTokenMaxReuse(rep.getRefreshTokenMaxReuse());
if (rep.getAccessTokenLifespan() != null) realm.setAccessTokenLifespan(rep.getAccessTokenLifespan());

View file

@ -71,6 +71,4 @@ org.keycloak.credential.CredentialSpi
org.keycloak.keys.PublicKeyStorageSpi
org.keycloak.keys.KeySpi
org.keycloak.storage.client.ClientStorageProviderSpi
# KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
org.keycloak.jose.jws.TokenSignatureSpi
org.keycloak.crypto.SignatureSpi

View file

@ -84,6 +84,11 @@ public class ComponentModel implements Serializable {
return config.getFirst(key);
}
public String get(String key, String defaultValue) {
String s = config.getFirst(key);
return s != null ? s : defaultValue;
}
public int get(String key, int defaultValue) {
String s = config.getFirst(key);
return s != null ? Integer.parseInt(s) : defaultValue;

View file

@ -180,4 +180,11 @@ public interface KeycloakSession {
*/
ThemeManager theme();
/**
* Token manager
*
* @return
*/
TokenManager tokens();
}

View file

@ -162,6 +162,9 @@ public interface RealmModel extends RoleContainerModel {
void setResetPasswordAllowed(boolean resetPasswordAllowed);
String getDefaultSignatureAlgorithm();
void setDefaultSignatureAlgorithm(String defaultSignatureAlgorithm);
boolean isRevokeRefreshToken();
void setRevokeRefreshToken(boolean revokeRefreshToken);

View file

@ -0,0 +1,44 @@
/*
* 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.models;
import org.keycloak.Token;
import org.keycloak.TokenCategory;
public interface TokenManager {
/**
* Encodes the supplied token
*
* @param token the token to encode
* @return The encoded token
*/
String encode(Token token);
/**
* Decodes and verifies the token, or <code>null</code> if the token was invalid
*
* @param token the token to decode
* @param clazz the token type to return
* @param <T>
* @return The decoded token, or <code>null</code> if the token was not valid
*/
<T extends Token> T decode(String token, Class<T> clazz);
String signatureAlgorithm(TokenCategory category);
}

View file

@ -21,7 +21,6 @@ import org.keycloak.TokenVerifier.Predicate;
import org.keycloak.common.VerificationException;
import org.keycloak.common.util.Time;
import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.models.*;
import org.keycloak.services.Urls;
import com.fasterxml.jackson.annotation.JsonIgnore;
@ -139,7 +138,6 @@ public class DefaultActionToken extends DefaultActionTokenKey implements ActionT
*/
public String serialize(KeycloakSession session, RealmModel realm, UriInfo uri) {
String issuerUri = getIssuer(realm, uri);
KeyManager.ActiveHmacKey keys = session.keys().getActiveHmacKey(realm);
this
.issuedAt(Time.currentTime())
@ -147,10 +145,7 @@ public class DefaultActionToken extends DefaultActionTokenKey implements ActionT
.issuer(issuerUri)
.audience(issuerUri);
return new JWSBuilder()
.kid(keys.getKid())
.jsonContent(this)
.hmac512(keys.getSecretKey());
return session.tokens().encode(this);
}
private static String getIssuer(RealmModel realm, UriInfo uri) {

View file

@ -57,8 +57,6 @@ import org.keycloak.authorization.util.Permissions;
import org.keycloak.authorization.util.Tokens;
import org.keycloak.common.util.Base64Url;
import org.keycloak.events.EventBuilder;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionContext;
@ -123,7 +121,7 @@ public class AuthorizationTokenService {
throw new RuntimeException("Claim token can not be null and must be a valid IDToken");
}
IDToken idToken = new TokenManager().verifyIDTokenSignature(keycloakSession, realm, accessToken);
IDToken idToken = new TokenManager().verifyIDTokenSignature(keycloakSession, accessToken);
return new KeycloakEvaluationContext(new KeycloakIdentity(keycloakSession, idToken), authorizationRequest.getClaims(), keycloakSession);
} catch (OAuthErrorException cause) {
throw new RuntimeException("Failed to verify ID token", cause);
@ -538,21 +536,16 @@ public class AuthorizationTokenService {
private PermissionTicketToken verifyPermissionTicket(KeycloakAuthorizationRequest request) {
String ticketString = request.getTicket();
if (ticketString == null || !Tokens.verifySignature(request.getKeycloakSession(), request.getRealm(), ticketString)) {
PermissionTicketToken ticket = request.getKeycloakSession().tokens().decode(ticketString, PermissionTicketToken.class);
if (ticket == null) {
throw new CorsErrorResponseException(request.getCors(), "invalid_ticket", "Ticket verification failed", Status.FORBIDDEN);
}
try {
PermissionTicketToken ticket = new JWSInput(ticketString).readJsonContent(PermissionTicketToken.class);
if (!ticket.isActive()) {
throw new CorsErrorResponseException(request.getCors(), "invalid_ticket", "Invalid permission ticket.", Status.FORBIDDEN);
}
return ticket;
} catch (JWSInputException e) {
throw new CorsErrorResponseException(request.getCors(), "invalid_ticket", "Could not parse permission ticket.", Status.FORBIDDEN);
if (!ticket.isActive()) {
throw new CorsErrorResponseException(request.getCors(), "invalid_ticket", "Invalid permission ticket.", Status.FORBIDDEN);
}
return ticket;
}
private boolean isGranted(PermissionTicketToken ticket, AuthorizationRequest request, Collection<Permission> permissions) {

View file

@ -16,6 +16,21 @@
*/
package org.keycloak.authorization.protection.permission;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.common.KeycloakIdentity;
import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.store.ResourceStore;
import org.keycloak.models.ClientModel;
import org.keycloak.models.TokenManager;
import org.keycloak.representations.idm.authorization.Permission;
import org.keycloak.representations.idm.authorization.PermissionRequest;
import org.keycloak.representations.idm.authorization.PermissionResponse;
import org.keycloak.representations.idm.authorization.PermissionTicketToken;
import org.keycloak.services.ErrorResponseException;
import javax.ws.rs.core.Response;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@ -24,23 +39,6 @@ import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.ws.rs.core.Response;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.common.KeycloakIdentity;
import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.store.ResourceStore;
import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeyManager;
import org.keycloak.representations.idm.authorization.Permission;
import org.keycloak.representations.idm.authorization.PermissionRequest;
import org.keycloak.representations.idm.authorization.PermissionResponse;
import org.keycloak.representations.idm.authorization.PermissionTicketToken;
import org.keycloak.services.ErrorResponseException;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
@ -150,7 +148,6 @@ public class AbstractPermissionService {
private String createPermissionTicket(List<PermissionRequest> request) {
List<Permission> permissions = verifyRequestedResource(request);
KeyManager.ActiveRsaKey keys = this.authorization.getKeycloakSession().keys().getActiveRsaKey(this.authorization.getRealm());
ClientModel targetClient = authorization.getRealm().getClientById(resourceServer.getId());
PermissionTicketToken token = new PermissionTicketToken(permissions, targetClient.getClientId(), this.identity.getAccessToken());
Map<String, List<String>> claims = new HashMap<>();
@ -167,7 +164,6 @@ public class AbstractPermissionService {
token.setClaims(claims);
}
return new JWSBuilder().kid(keys.getKid()).jsonContent(token)
.rsa256(keys.getPrivateKey());
return this.authorization.getKeycloakSession().tokens().encode(token);
}
}

View file

@ -19,17 +19,14 @@
package org.keycloak.authorization.util;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.models.KeycloakContext;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.representations.AccessToken;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.managers.AppAuthManager;
import org.keycloak.services.managers.AuthenticationManager.AuthResult;
import javax.ws.rs.core.Response.Status;
import java.security.PublicKey;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@ -60,13 +57,4 @@ public class Tokens {
return null;
}
public static boolean verifySignature(KeycloakSession keycloakSession, RealmModel realm, String token) {
try {
JWSInput jws = new JWSInput(token);
PublicKey publicKey = keycloakSession.keys().getRsaPublicKey(realm, jws.getHeader().getKeyId());
return RSAProvider.verify(jws, publicKey);
} catch (Exception e) {
throw new ErrorResponseException("invalid_signature", "Unexpected error while validating signature.", Status.INTERNAL_SERVER_ERROR);
}
}
}

View file

@ -321,4 +321,4 @@ public class HttpClientBuilder {
.build();
}
}
}

View file

@ -0,0 +1,42 @@
/*
* 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.crypto;
import org.keycloak.common.VerificationException;
import org.keycloak.models.KeycloakSession;
public class AsymmetricSignatureProvider implements SignatureProvider {
private final KeycloakSession session;
private final String algorithm;
public AsymmetricSignatureProvider(KeycloakSession session, String algorithm) {
this.session = session;
this.algorithm = algorithm;
}
@Override
public SignatureSignerContext signer() throws SignatureException {
return new ServerAsymmetricSignatureSignerContext(session, algorithm);
}
@Override
public SignatureVerifierContext verifier(String kid) throws VerificationException {
return new ServerAsymmetricSignatureVerifierContext(session, kid, algorithm);
}
}

View file

@ -0,0 +1,35 @@
/*
* 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.crypto;
import org.keycloak.models.KeycloakSession;
public class ES256SignatureProviderFactory implements SignatureProviderFactory {
public static final String ID = Algorithm.ES256;
@Override
public String getId() {
return ID;
}
@Override
public SignatureProvider create(KeycloakSession session) {
return new AsymmetricSignatureProvider(session, Algorithm.ES256);
}
}

View file

@ -0,0 +1,35 @@
/*
* 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.crypto;
import org.keycloak.models.KeycloakSession;
public class ES384SignatureProviderFactory implements SignatureProviderFactory {
public static final String ID = Algorithm.ES384;
@Override
public String getId() {
return ID;
}
@Override
public SignatureProvider create(KeycloakSession session) {
return new AsymmetricSignatureProvider(session, Algorithm.ES384);
}
}

View file

@ -0,0 +1,35 @@
/*
* 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.crypto;
import org.keycloak.models.KeycloakSession;
public class ES512SignatureProviderFactory implements SignatureProviderFactory {
public static final String ID = Algorithm.ES512;
@Override
public String getId() {
return ID;
}
@Override
public SignatureProvider create(KeycloakSession session) {
return new AsymmetricSignatureProvider(session, Algorithm.ES512);
}
}

View file

@ -0,0 +1,35 @@
/*
* 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.crypto;
import org.keycloak.models.KeycloakSession;
public class HS256SignatureProviderFactory implements SignatureProviderFactory {
public static final String ID = Algorithm.HS256;
@Override
public String getId() {
return ID;
}
@Override
public SignatureProvider create(KeycloakSession session) {
return new MacSecretSignatureProvider(session, Algorithm.HS256);
}
}

View file

@ -0,0 +1,35 @@
/*
* 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.crypto;
import org.keycloak.models.KeycloakSession;
public class HS384SignatureProviderFactory implements SignatureProviderFactory {
public static final String ID = Algorithm.HS384;
@Override
public String getId() {
return ID;
}
@Override
public SignatureProvider create(KeycloakSession session) {
return new MacSecretSignatureProvider(session, Algorithm.HS384);
}
}

View file

@ -0,0 +1,35 @@
/*
* 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.crypto;
import org.keycloak.models.KeycloakSession;
public class HS512SignatureProviderFactory implements SignatureProviderFactory {
public static final String ID = Algorithm.HS512;
@Override
public String getId() {
return ID;
}
@Override
public SignatureProvider create(KeycloakSession session) {
return new MacSecretSignatureProvider(session, Algorithm.HS512);
}
}

View file

@ -0,0 +1,42 @@
/*
* 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.crypto;
import org.keycloak.common.VerificationException;
import org.keycloak.models.KeycloakSession;
public class MacSecretSignatureProvider implements SignatureProvider {
private final KeycloakSession session;
private final String algorithm;
public MacSecretSignatureProvider(KeycloakSession session, String algorithm) {
this.session = session;
this.algorithm = algorithm;
}
@Override
public SignatureSignerContext signer() throws SignatureException {
return new ServerMacSignatureSignerContext(session, algorithm);
}
@Override
public SignatureVerifierContext verifier(String kid) throws VerificationException {
return new ServerMacSignatureVerifierContext(session, kid, algorithm);
}
}

View file

@ -0,0 +1,35 @@
/*
* 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.crypto;
import org.keycloak.models.KeycloakSession;
public class RS256SignatureProviderFactory implements SignatureProviderFactory {
public static final String ID = Algorithm.RS256;
@Override
public String getId() {
return ID;
}
@Override
public SignatureProvider create(KeycloakSession session) {
return new AsymmetricSignatureProvider(session, Algorithm.RS256);
}
}

View file

@ -0,0 +1,35 @@
/*
* 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.crypto;
import org.keycloak.models.KeycloakSession;
public class RS384SignatureProviderFactory implements SignatureProviderFactory {
public static final String ID = Algorithm.RS384;
@Override
public String getId() {
return ID;
}
@Override
public SignatureProvider create(KeycloakSession session) {
return new AsymmetricSignatureProvider(session, Algorithm.RS384);
}
}

View file

@ -0,0 +1,35 @@
/*
* 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.crypto;
import org.keycloak.models.KeycloakSession;
public class RS512SignatureProviderFactory implements SignatureProviderFactory {
public static final String ID = Algorithm.RS512;
@Override
public String getId() {
return ID;
}
@Override
public SignatureProvider create(KeycloakSession session) {
return new AsymmetricSignatureProvider(session, Algorithm.RS512);
}
}

View file

@ -0,0 +1,35 @@
/*
* 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.crypto;
import org.keycloak.models.KeycloakSession;
public class ServerAsymmetricSignatureSignerContext extends AsymmetricSignatureSignerContext {
public ServerAsymmetricSignatureSignerContext(KeycloakSession session, String algorithm) throws SignatureException {
super(getKey(session, algorithm));
}
private static KeyWrapper getKey(KeycloakSession session, String algorithm) {
KeyWrapper key = session.keys().getActiveKey(session.getContext().getRealm(), KeyUse.SIG, algorithm);
if (key == null) {
throw new SignatureException("Active key for " + algorithm + " not found");
}
return key;
}
}

View file

@ -0,0 +1,36 @@
/*
* 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.crypto;
import org.keycloak.common.VerificationException;
import org.keycloak.models.KeycloakSession;
public class ServerAsymmetricSignatureVerifierContext extends AsymmetricSignatureVerifierContext {
public ServerAsymmetricSignatureVerifierContext(KeycloakSession session, String kid, String algorithm) throws VerificationException {
super(getKey(session, kid, algorithm));
}
private static KeyWrapper getKey(KeycloakSession session, String kid, String algorithm) throws VerificationException {
KeyWrapper key = session.keys().getKey(session.getContext().getRealm(), kid, KeyUse.SIG, algorithm);
if (key == null) {
throw new VerificationException("Key not found");
}
return key;
}
}

View file

@ -0,0 +1,35 @@
/*
* 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.crypto;
import org.keycloak.models.KeycloakSession;
public class ServerMacSignatureSignerContext extends MacSignatureSignerContext {
public ServerMacSignatureSignerContext(KeycloakSession session, String algorithm) throws SignatureException {
super(getKey(session, algorithm));
}
private static KeyWrapper getKey(KeycloakSession session, String algorithm) {
KeyWrapper key = session.keys().getActiveKey(session.getContext().getRealm(), KeyUse.SIG, algorithm);
if (key == null) {
throw new SignatureException("Active key for " + algorithm + " not found");
}
return key;
}
}

View file

@ -0,0 +1,36 @@
/*
* 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.crypto;
import org.keycloak.common.VerificationException;
import org.keycloak.models.KeycloakSession;
public class ServerMacSignatureVerifierContext extends MacSignatureVerifierContext {
public ServerMacSignatureVerifierContext(KeycloakSession session, String kid, String algorithm) throws VerificationException {
super(getKey(session, kid, algorithm));
}
private static KeyWrapper getKey(KeycloakSession session, String kid, String algorithm) throws VerificationException {
KeyWrapper key = session.keys().getKey(session.getContext().getRealm(), kid, KeyUse.SIG, algorithm);
if (key == null) {
throw new VerificationException("Key not found");
}
return key;
}
}

View file

@ -1,36 +0,0 @@
package org.keycloak.jose.jws;
import java.security.Key;
import java.security.Signature;
import org.jboss.logging.Logger;
import org.keycloak.component.ComponentModel;
import org.keycloak.crypto.JavaAlgorithm;
import org.keycloak.models.KeycloakSession;
import org.keycloak.jose.jws.JWSSignatureProvider;
// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
public abstract class AbstractTokenSignatureProvider implements TokenSignatureProvider, JWSSignatureProvider {
protected static final Logger logger = Logger.getLogger(AbstractTokenSignatureProvider.class);
public AbstractTokenSignatureProvider(KeycloakSession session, ComponentModel model) {}
@Override
public void close() {}
@Override
public abstract byte[] sign(byte[] data, String sigAlgName, Key key);
@Override
public abstract boolean verify(JWSInput jws, Key verifyKey);
protected Signature getSignature(String sigAlgName) {
try {
return Signature.getInstance(JavaAlgorithm.getJavaAlgorithm(sigAlgName));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View file

@ -0,0 +1,121 @@
/*
* 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.jws;
import org.jboss.logging.Logger;
import org.keycloak.Token;
import org.keycloak.TokenCategory;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyUse;
import org.keycloak.crypto.SignatureProvider;
import org.keycloak.crypto.SignatureSignerContext;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.TokenManager;
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
public class DefaultTokenManager implements TokenManager {
private static final Logger logger = Logger.getLogger(DefaultTokenManager.class);
private static String DEFAULT_ALGORITHM_NAME = Algorithm.RS256;
private final KeycloakSession session;
public DefaultTokenManager(KeycloakSession session) {
this.session = session;
}
@Override
public String encode(Token token) {
String signatureAlgorithm = signatureAlgorithm(token.getCategory());
SignatureProvider signatureProvider = session.getProvider(SignatureProvider.class, signatureAlgorithm);
SignatureSignerContext signer = signatureProvider.signer();
String encodedToken = new JWSBuilder().type("JWT").jsonContent(token).sign(signer);
return encodedToken;
}
@Override
public <T extends Token> T decode(String token, Class<T> clazz) {
if (token == null) {
return null;
}
try {
JWSInput jws = new JWSInput(token);
String signatureAlgorithm = jws.getHeader().getAlgorithm().name();
SignatureProvider signatureProvider = session.getProvider(SignatureProvider.class, signatureAlgorithm);
if (signatureProvider == null) {
return null;
}
String kid = jws.getHeader().getKeyId();
// Backwards compatibility. Old offline tokens and cookies didn't have KID in the header
if (kid == null) {
logger.debugf("KID is null in token. Using the realm active key to verify token signature.");
kid = session.keys().getActiveKey(session.getContext().getRealm(), KeyUse.SIG, signatureAlgorithm).getKid();
}
boolean valid = signatureProvider.verifier(kid).verify(jws.getEncodedSignatureInput().getBytes("UTF-8"), jws.getSignature());
return valid ? jws.readJsonContent(clazz) : null;
} catch (Exception e) {
logger.debug("Failed to decode token", e);
return null;
}
}
@Override
public String signatureAlgorithm(TokenCategory category) {
switch (category) {
case INTERNAL:
return Algorithm.HS256;
case ADMIN:
return getSignatureAlgorithm(null);
case ACCESS:
return getSignatureAlgorithm(OIDCConfigAttributes.ACCESS_TOKEN_SIGNED_RESPONSE_ALG);
case ID:
return getSignatureAlgorithm(OIDCConfigAttributes.ID_TOKEN_SIGNED_RESPONSE_ALG);
case USERINFO:
return getSignatureAlgorithm(OIDCConfigAttributes.USER_INFO_RESPONSE_SIGNATURE_ALG);
default:
throw new RuntimeException("Unknown token type");
}
}
private String getSignatureAlgorithm(String clientAttribute) {
RealmModel realm = session.getContext().getRealm();
ClientModel client = session.getContext().getClient();
String algorithm = client != null && clientAttribute != null ? client.getAttribute(clientAttribute) : null;
if (algorithm != null && !algorithm.equals("")) {
return algorithm;
}
algorithm = realm.getDefaultSignatureAlgorithm();
if (algorithm != null && !algorithm.equals("")) {
return algorithm;
}
return DEFAULT_ALGORITHM_NAME;
}
}

View file

@ -1,69 +0,0 @@
package org.keycloak.jose.jws;
import java.security.Key;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
public class EcdsaTokenSignatureProvider extends AbstractTokenSignatureProvider {
public EcdsaTokenSignatureProvider(KeycloakSession session, ComponentModel model) {
super(session, model);
}
@Override
public void close() {}
@Override
public byte[] sign(byte[] data, String sigAlgName, Key key) {
try {
PrivateKey privateKey = (PrivateKey)key;
Signature signature = getSignature(sigAlgName);
signature.initSign(privateKey);
signature.update(data);
return signature.sign();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public boolean verify(JWSInput jws, Key verifyKey) {
try {
PublicKey publicKey = (PublicKey)verifyKey;
Signature verifier = getSignature(jws.getHeader().getAlgorithm().name());
verifier.initVerify(publicKey);
verifier.update(jws.getEncodedSignatureInput().getBytes("UTF-8"));
return verifier.verify(jws.getSignature());
} catch (Exception e) {
return false;
}
}
@Override
protected Signature getSignature(String sigAlgName) {
try {
return Signature.getInstance(getJavaAlgorithm(sigAlgName));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private String getJavaAlgorithm(String sigAlgName) {
switch (sigAlgName) {
case "ES256":
return "SHA256withECDSA";
case "ES384":
return "SHA384withECDSA";
case "ES512":
return "SHA512withECDSA";
default:
throw new IllegalArgumentException("Not an ECDSA Algorithm");
}
}
}

View file

@ -1,54 +0,0 @@
package org.keycloak.jose.jws;
import java.util.List;
import org.keycloak.Config.Scope;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderConfigurationBuilder;
// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
@SuppressWarnings("rawtypes")
public class EcdsaTokenSignatureProviderFactory implements TokenSignatureProviderFactory {
public static final String ID = "ecdsa-signature";
private static final String HELP_TEXT = "Generates token signature provider using EC key";
private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = ProviderConfigurationBuilder.create().build();
@Override
public void init(Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return ID;
}
@Override
public String getHelpText() {
return HELP_TEXT;
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return CONFIG_PROPERTIES;
}
@Override
public TokenSignatureProvider create(KeycloakSession session, ComponentModel model) {
return new EcdsaTokenSignatureProvider(session, model);
}
}

View file

@ -1,52 +0,0 @@
package org.keycloak.jose.jws;
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Mac;
import org.keycloak.common.util.Base64Url;
import org.keycloak.component.ComponentModel;
import org.keycloak.crypto.JavaAlgorithm;
import org.keycloak.models.KeycloakSession;
// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
public class HmacTokenSignatureProvider extends AbstractTokenSignatureProvider {
public HmacTokenSignatureProvider(KeycloakSession session, ComponentModel model) {
super(session, model);
}
private Mac getMAC(final String sigAlgName) {
try {
return Mac.getInstance(JavaAlgorithm.getJavaAlgorithm(sigAlgName));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Unsupported HMAC algorithm: " + e.getMessage(), e);
}
}
@Override
public byte[] sign(byte[] data, String sigAlgName, Key key) {
try {
Mac mac = getMAC(sigAlgName);
mac.init(key);
mac.update(data);
return mac.doFinal();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public boolean verify(JWSInput jws, Key verifyKey) {
try {
byte[] signature = sign(jws.getEncodedSignatureInput().getBytes("UTF-8"), jws.getHeader().getAlgorithm().name(), verifyKey);
return MessageDigest.isEqual(signature, Base64Url.decode(jws.getEncodedSignature()));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View file

@ -1,56 +0,0 @@
package org.keycloak.jose.jws;
import java.util.List;
import org.keycloak.Config.Scope;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderConfigurationBuilder;
// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
@SuppressWarnings("rawtypes")
public class HmacTokenSignatureProviderFactory implements TokenSignatureProviderFactory {
public static final String ID = "hmac-signature";
private static final String HELP_TEXT = "Generates token signature provider using HMAC key";
private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = ProviderConfigurationBuilder.create().build();
@Override
public void init(Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return ID;
}
@Override
public String getHelpText() {
return HELP_TEXT;
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return CONFIG_PROPERTIES;
}
@Override
public TokenSignatureProvider create(KeycloakSession session, ComponentModel model) {
return new HmacTokenSignatureProvider(session, model);
}
}

View file

@ -1,47 +0,0 @@
package org.keycloak.jose.jws;
import java.security.Key;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
public class RsassaTokenSignatureProvider extends AbstractTokenSignatureProvider {
public RsassaTokenSignatureProvider(KeycloakSession session, ComponentModel model) {
super(session, model);
}
@Override
public void close() {}
@Override
public byte[] sign(byte[] data, String sigAlgName, Key key) {
try {
PrivateKey privateKey = (PrivateKey)key;
Signature signature = getSignature(sigAlgName);
signature.initSign(privateKey);
signature.update(data);
return signature.sign();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public boolean verify(JWSInput jws, Key verifyKey) {
try {
PublicKey publicKey = (PublicKey)verifyKey;
Signature verifier = getSignature(jws.getHeader().getAlgorithm().name());
verifier.initVerify(publicKey);
verifier.update(jws.getEncodedSignatureInput().getBytes("UTF-8"));
return verifier.verify(jws.getSignature());
} catch (Exception e) {
return false;
}
}
}

View file

@ -1,55 +0,0 @@
package org.keycloak.jose.jws;
import java.util.List;
import org.keycloak.Config.Scope;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderConfigurationBuilder;
// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
@SuppressWarnings("rawtypes")
public class RsassaTokenSignatureProviderFactory implements TokenSignatureProviderFactory {
public static final String ID = "rsassa-signature";
private static final String HELP_TEXT = "Generates token signature provider using RSA key";
private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = ProviderConfigurationBuilder.create().build();
@Override
public void init(Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return ID;
}
@Override
public String getHelpText() {
return HELP_TEXT;
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return CONFIG_PROPERTIES;
}
@Override
public TokenSignatureProvider create(KeycloakSession session, ComponentModel model) {
return new RsassaTokenSignatureProvider(session, model);
}
}

View file

@ -1,97 +0,0 @@
package org.keycloak.jose.jws;
import java.security.Key;
import java.util.LinkedList;
import java.util.List;
import org.jboss.logging.Logger;
import org.keycloak.component.ComponentModel;
import org.keycloak.crypto.KeyUse;
import org.keycloak.crypto.KeyWrapper;
import org.keycloak.jose.jws.JWSSignatureProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.representations.JsonWebToken;
import org.keycloak.representations.RefreshToken;
import org.keycloak.util.TokenUtil;
// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
public class TokenSignature {
private static final Logger logger = Logger.getLogger(TokenSignature.class);
KeycloakSession session;
RealmModel realm;
String sigAlgName;
public static TokenSignature getInstance(KeycloakSession session, RealmModel realm, String sigAlgName) {
return new TokenSignature(session, realm, sigAlgName);
}
public TokenSignature(KeycloakSession session, RealmModel realm, String sigAlgName) {
this.session = session;
this.realm = realm;
this.sigAlgName = sigAlgName;
}
public String sign(JsonWebToken jwt) {
TokenSignatureProvider tokenSignatureProvider = getTokenSignatureProvider(sigAlgName);
if (tokenSignatureProvider == null) return null;
KeyWrapper keyWrapper = session.keys().getActiveKey(realm, KeyUse.SIG, sigAlgName);
if (keyWrapper == null) return null;
String keyId = keyWrapper.getKid();
Key signKey = keyWrapper.getSignKey();
String encodedToken = new JWSBuilder().type("JWT").kid(keyId).jsonContent(jwt).sign((JWSSignatureProvider)tokenSignatureProvider, sigAlgName, signKey);
return encodedToken;
}
public boolean verify(JWSInput jws) throws JWSInputException {
TokenSignatureProvider tokenSignatureProvider = getTokenSignatureProvider(sigAlgName);
if (tokenSignatureProvider == null) return false;
KeyWrapper keyWrapper = null;
// Backwards compatibility. Old offline tokens didn't have KID in the header
if (jws.getHeader().getKeyId() == null && isOfflineToken(jws)) {
logger.debugf("KID is null in offline token. Using the realm active key to verify token signature.");
keyWrapper = session.keys().getActiveKey(realm, KeyUse.SIG, sigAlgName);
} else {
keyWrapper = session.keys().getKey(realm, jws.getHeader().getKeyId(), KeyUse.SIG, sigAlgName);
}
if (keyWrapper == null) return false;
return tokenSignatureProvider.verify(jws, keyWrapper.getVerifyKey());
}
private static final String COMPONENT_SIGNATURE_ALGORITHM_KEY = "org.keycloak.jose.jws.TokenSignatureProvider.algorithm";
@SuppressWarnings("rawtypes")
private TokenSignatureProvider getTokenSignatureProvider(String sigAlgName) {
List<ComponentModel> components = new LinkedList<>(realm.getComponents(realm.getId(), TokenSignatureProvider.class.getName()));
ComponentModel c = null;
for (ComponentModel component : components) {
if (sigAlgName.equals(component.get(COMPONENT_SIGNATURE_ALGORITHM_KEY))) {
c = component;
break;
}
}
if (c == null) {
if (logger.isTraceEnabled()) {
logger.tracev("Failed to find TokenSignatureProvider algorithm={0}.", sigAlgName);
}
return null;
}
ProviderFactory<TokenSignatureProvider> f = session.getKeycloakSessionFactory().getProviderFactory(TokenSignatureProvider.class, c.getProviderId());
TokenSignatureProviderFactory factory = (TokenSignatureProviderFactory) f;
TokenSignatureProvider provider = factory.create(session, c);
return provider;
}
private boolean isOfflineToken(JWSInput jws) throws JWSInputException {
RefreshToken token = TokenUtil.getRefreshToken(jws.getContent());
return token.getType().equals(TokenUtil.TOKEN_TYPE_OFFLINE);
}
}

View file

@ -1,22 +0,0 @@
package org.keycloak.jose.jws;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
public class TokenSignatureUtil {
public static final String REALM_SIGNATURE_ALGORITHM_KEY = "token.signed.response.alg";
private static String DEFAULT_ALGORITHM_NAME = "RS256";
public static String getTokenSignatureAlgorithm(KeycloakSession session, RealmModel realm, ClientModel client) {
String realmSigAlgName = realm.getAttribute(REALM_SIGNATURE_ALGORITHM_KEY);
String clientSigAlgname = null;
if (client != null) clientSigAlgname = OIDCAdvancedConfigWrapper.fromClientModel(client).getIdTokenSignedResponseAlg();
String sigAlgName = clientSigAlgname;
if (sigAlgName == null) sigAlgName = (realmSigAlgName == null ? DEFAULT_ALGORITHM_NAME : realmSigAlgName);
return sigAlgName;
}
}

View file

@ -1,9 +1,21 @@
/*
* 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.keys;
import java.security.KeyPair;
import java.util.Collections;
import java.util.List;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.component.ComponentModel;
import org.keycloak.crypto.KeyStatus;
@ -12,7 +24,9 @@ import org.keycloak.crypto.KeyUse;
import org.keycloak.crypto.KeyWrapper;
import org.keycloak.models.RealmModel;
// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
import java.security.KeyPair;
import java.util.Collections;
import java.util.List;
public abstract class AbstractEcdsaKeyProvider implements KeyProvider {
@ -50,7 +64,7 @@ public abstract class AbstractEcdsaKeyProvider implements KeyProvider {
key.setKid(KeyUtils.createKeyId(keyPair.getPublic()));
key.setUse(KeyUse.SIG);
key.setType(KeyType.EC);
key.setAlgorithms(AbstractEcdsaKeyProviderFactory.convertECDomainParmNistRepToAlgorithm(ecInNistRep));
key.setAlgorithm(AbstractEcdsaKeyProviderFactory.convertECDomainParmNistRepToAlgorithm(ecInNistRep));
key.setStatus(status);
key.setSignKey(keyPair.getPrivate());
key.setVerifyKey(keyPair.getPublic());

View file

@ -1,10 +1,21 @@
/*
* 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.keys;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.security.spec.ECGenParameterSpec;
import org.keycloak.component.ComponentModel;
import org.keycloak.component.ComponentValidationException;
import org.keycloak.crypto.Algorithm;
@ -14,11 +25,13 @@ import org.keycloak.provider.ConfigurationValidationHelper;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderConfigurationBuilder;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.security.spec.ECGenParameterSpec;
import static org.keycloak.provider.ProviderConfigProperty.LIST_TYPE;
// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
@SuppressWarnings("rawtypes")
public abstract class AbstractEcdsaKeyProviderFactory implements KeyProviderFactory {
protected static final String ECDSA_PRIVATE_KEY_KEY = "ecdsaPrivateKey";
@ -77,22 +90,29 @@ public abstract class AbstractEcdsaKeyProviderFactory implements KeyProviderFact
}
public static String convertECDomainParmNistRepToAlgorithm(String ecInNistRep) {
// convert Elliptic Curve Domain Parameter Name in NIST to Algorithm (JWA) representation
String ecInAlgorithmRep = null;
switch(ecInNistRep) {
case "P-256" :
ecInAlgorithmRep = Algorithm.ES256;
break;
return Algorithm.ES256;
case "P-384" :
ecInAlgorithmRep = Algorithm.ES384;
break;
return Algorithm.ES384;
case "P-521" :
ecInAlgorithmRep = Algorithm.ES512;
break;
return Algorithm.ES512;
default :
// return null
return null;
}
}
public static String convertAlgorithmToECDomainParmNistRep(String algorithm) {
switch(algorithm) {
case Algorithm.ES256 :
return "P-256";
case Algorithm.ES384 :
return "P-384";
case Algorithm.ES512 :
return "P-521";
default :
return null;
}
return ecInAlgorithmRep;
}
}

View file

@ -24,7 +24,6 @@ import org.keycloak.crypto.JavaAlgorithm;
import org.keycloak.crypto.KeyStatus;
import org.keycloak.crypto.KeyUse;
import org.keycloak.crypto.KeyWrapper;
import org.keycloak.models.utils.KeycloakModelUtils;
import javax.crypto.SecretKey;
import java.util.Collections;
@ -33,7 +32,7 @@ import java.util.List;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public abstract class GeneratedSecretKeyProvider implements KeyProvider {
public abstract class AbstractGeneratedSecretKeyProvider implements KeyProvider {
private final KeyStatus status;
private final ComponentModel model;
@ -43,7 +42,7 @@ public abstract class GeneratedSecretKeyProvider implements KeyProvider {
private String type;
private final String algorithm;
public GeneratedSecretKeyProvider(ComponentModel model, KeyUse use, String type, String algorithm) {
public AbstractGeneratedSecretKeyProvider(ComponentModel model, KeyUse use, String type, String algorithm) {
this.status = KeyStatus.from(model.get(Attributes.ACTIVE_KEY, true), model.get(Attributes.ENABLED_KEY, true));
this.kid = model.get(Attributes.KID_KEY);
this.model = model;
@ -69,7 +68,7 @@ public abstract class GeneratedSecretKeyProvider implements KeyProvider {
key.setKid(kid);
key.setUse(use);
key.setType(type);
key.setAlgorithms(algorithm);
key.setAlgorithm(algorithm);
key.setStatus(status);
key.setSecretKey(secretKey);

View file

@ -18,12 +18,10 @@
package org.keycloak.keys;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.common.util.Base64Url;
import org.keycloak.component.ComponentModel;
import org.keycloak.component.ComponentValidationException;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.provider.ConfigurationValidationHelper;
@ -31,7 +29,7 @@ import org.keycloak.provider.ConfigurationValidationHelper;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public abstract class GeneratedSecretKeyProviderFactory<T extends KeyProvider> implements KeyProviderFactory<T> {
public abstract class AbstractGeneratedSecretKeyProviderFactory<T extends KeyProvider> implements KeyProviderFactory<T> {
@Override
public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel model) throws ComponentValidationException {

View file

@ -19,7 +19,11 @@ package org.keycloak.keys;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.component.ComponentModel;
import org.keycloak.crypto.*;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyStatus;
import org.keycloak.crypto.KeyType;
import org.keycloak.crypto.KeyUse;
import org.keycloak.crypto.KeyWrapper;
import org.keycloak.models.RealmModel;
import java.security.KeyPair;
@ -38,9 +42,12 @@ public abstract class AbstractRsaKeyProvider implements KeyProvider {
private final KeyWrapper key;
private final String algorithm;
public AbstractRsaKeyProvider(RealmModel realm, ComponentModel model) {
this.model = model;
this.status = KeyStatus.from(model.get(Attributes.ACTIVE_KEY, true), model.get(Attributes.ENABLED_KEY, true));
this.algorithm = model.get(Attributes.ALGORITHM_KEY, Algorithm.RS256);
if (model.hasNote(KeyWrapper.class.getName())) {
key = model.getNote(KeyWrapper.class.getName());
@ -66,7 +73,7 @@ public abstract class AbstractRsaKeyProvider implements KeyProvider {
key.setKid(KeyUtils.createKeyId(keyPair.getPublic()));
key.setUse(KeyUse.SIG);
key.setType(KeyType.RSA);
key.setAlgorithms(Algorithm.RS256, Algorithm.RS384, Algorithm.RS512);
key.setAlgorithm(algorithm);
key.setStatus(status);
key.setSignKey(keyPair.getPrivate());
key.setVerifyKey(keyPair.getPublic());

View file

@ -33,7 +33,8 @@ public abstract class AbstractRsaKeyProviderFactory implements KeyProviderFactor
return ProviderConfigurationBuilder.create()
.property(Attributes.PRIORITY_PROPERTY)
.property(Attributes.ENABLED_PROPERTY)
.property(Attributes.ACTIVE_PROPERTY);
.property(Attributes.ACTIVE_PROPERTY)
.property(Attributes.RS_ALGORITHM_PROPERTY);
}
@Override

View file

@ -17,10 +17,9 @@
package org.keycloak.keys;
import org.keycloak.crypto.Algorithm;
import org.keycloak.provider.ProviderConfigProperty;
import java.util.LinkedList;
import static org.keycloak.provider.ProviderConfigProperty.*;
/**
@ -55,4 +54,13 @@ public interface Attributes {
String.valueOf(GeneratedHmacKeyProviderFactory.DEFAULT_HMAC_KEY_SIZE),
"16", "24", "32", "64", "128", "256", "512");
String ALGORITHM_KEY = "algorithm";
ProviderConfigProperty RS_ALGORITHM_PROPERTY = new ProviderConfigProperty(ALGORITHM_KEY, "Algorithm", "Intended algorithm for the key", LIST_TYPE,
Algorithm.RS256,
Algorithm.RS256, Algorithm.RS384, Algorithm.RS512);
ProviderConfigProperty HS_ALGORITHM_PROPERTY = new ProviderConfigProperty(ALGORITHM_KEY, "Algorithm", "Intended algorithm for the key", LIST_TYPE,
Algorithm.HS256,
Algorithm.HS256, Algorithm.HS384, Algorithm.HS512);
}

View file

@ -31,7 +31,11 @@ import javax.crypto.SecretKey;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.util.*;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -49,18 +53,45 @@ public class DefaultKeyManager implements KeyManager {
@Override
public KeyWrapper getActiveKey(RealmModel realm, KeyUse use, String algorithm) {
for (KeyProvider p : getProviders(realm)) {
KeyWrapper activeKey = getActiveKey(getProviders(realm), realm, use, algorithm);
if (activeKey != null) {
return activeKey;
}
logger.debugv("Failed to find active key for realm, trying fallback: realm={0} algorithm={1} use={2}", realm.getName(), algorithm, use.name());
for (ProviderFactory f : session.getKeycloakSessionFactory().getProviderFactories(KeyProvider.class)) {
KeyProviderFactory kf = (KeyProviderFactory) f;
if (kf.createFallbackKeys(session, use, algorithm)) {
providersMap.remove(realm.getId());
List<KeyProvider> providers = getProviders(realm);
activeKey = getActiveKey(providers, realm, use, algorithm);
if (activeKey != null) {
logger.warnv("Fallback key created: realm={0} algorithm={1} use={2}", realm.getName(), algorithm, use.name());
return activeKey;
} else {
break;
}
}
}
logger.errorv("Failed to create fallback key for realm: realm={0} algorithm={1} use={2", realm.getName(), algorithm, use.name());
throw new RuntimeException("Failed to find key: realm=" + realm.getName() + " algorithm=" + algorithm + " use=" + use.name());
}
private KeyWrapper getActiveKey(List<KeyProvider> providers, RealmModel realm, KeyUse use, String algorithm) {
for (KeyProvider p : providers) {
for (KeyWrapper key : p .getKeys()) {
if (key.getStatus().isActive() && matches(key, use, algorithm)) {
if (logger.isTraceEnabled()) {
logger.tracev("Active key found: realm={0} kid={1} algorithm={2}", realm.getName(), key.getKid(), algorithm);
logger.tracev("Active key found: realm={0} kid={1} algorithm={2} use={3}", realm.getName(), key.getKid(), algorithm, use.name());
}
return key;
}
}
}
throw new RuntimeException("Failed to find key: realm=" + realm.getName() + " algorithm=" + algorithm);
return null;
}
@Override
@ -74,7 +105,7 @@ public class DefaultKeyManager implements KeyManager {
for (KeyWrapper key : p.getKeys()) {
if (key.getKid().equals(kid) && key.getStatus().isEnabled() && matches(key, use, algorithm)) {
if (logger.isTraceEnabled()) {
logger.tracev("Active key realm={0} kid={1} algorithm={2}", realm.getName(), key.getKid(), algorithm);
logger.tracev("Found key: realm={0} kid={1} algorithm={2} use={3}", realm.getName(), key.getKid(), algorithm, use.name());
}
return key;
@ -83,7 +114,7 @@ public class DefaultKeyManager implements KeyManager {
}
if (logger.isTraceEnabled()) {
logger.tracev("Failed to find public key realm={0} kid={1} algorithm={2}", realm.getName(), kid, algorithm);
logger.tracev("Failed to find public key: realm={0} kid={1} algorithm={2} use={3}", realm.getName(), kid, algorithm, use.name());
}
return null;
@ -211,7 +242,7 @@ public class DefaultKeyManager implements KeyManager {
}
private boolean matches(KeyWrapper key, KeyUse use, String algorithm) {
return use.equals(key.getUse()) && key.getAlgorithms().contains(algorithm);
return use.equals(key.getUse()) && key.getAlgorithm().equals(algorithm);
}
private List<KeyProvider> getProviders(RealmModel realm) {
@ -235,24 +266,6 @@ public class DefaultKeyManager implements KeyManager {
}
providersMap.put(realm.getId(), providers);
try {
getActiveKey(realm, KeyUse.SIG, Algorithm.RS256);
} catch (RuntimeException e) {
providers.add(new FailsafeRsaKeyProvider());
}
try {
getActiveKey(realm, KeyUse.SIG, Algorithm.HS256);
} catch (RuntimeException e) {
providers.add(new FailsafeHmacKeyProvider());
}
try {
getActiveKey(realm, KeyUse.ENC, Algorithm.AES);
} catch (RuntimeException e) {
providers.add(new FailsafeAesKeyProvider());
}
}
return providers;
}

View file

@ -1,52 +0,0 @@
/*
* Copyright 2017 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.keys;
import org.jboss.logging.Logger;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyType;
import org.keycloak.crypto.KeyUse;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class FailsafeAesKeyProvider extends FailsafeSecretKeyProvider {
private static final Logger logger = Logger.getLogger(FailsafeAesKeyProvider.class);
@Override
protected KeyUse getUse() {
return KeyUse.ENC;
}
@Override
protected String getType() {
return KeyType.OCT;
}
@Override
protected String getAlgorithm() {
return Algorithm.AES;
}
@Override
protected Logger logger() {
return logger;
}
}

View file

@ -1,66 +0,0 @@
package org.keycloak.keys;
import java.security.KeyPair;
import java.util.Collections;
import java.util.List;
import org.jboss.logging.Logger;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.common.util.Time;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyStatus;
import org.keycloak.crypto.KeyType;
import org.keycloak.crypto.KeyUse;
import org.keycloak.crypto.KeyWrapper;
// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
public class FailsafeEcdsaKeyProvider implements KeyProvider {
private static final Logger logger = Logger.getLogger(FailsafeEcdsaKeyProvider.class);
private static KeyWrapper KEY;
private static long EXPIRES;
private KeyWrapper key;
public FailsafeEcdsaKeyProvider() {
logger.errorv("No active keys found, using failsafe provider, please login to admin console to add keys. Clustering is not supported.");
synchronized (FailsafeEcdsaKeyProvider.class) {
if (EXPIRES < Time.currentTime()) {
KEY = createKeyWrapper();
EXPIRES = Time.currentTime() + 60 * 10;
if (EXPIRES > 0) {
logger.warnv("Keys expired, re-generated kid={0}", KEY.getKid());
}
}
key = KEY;
}
}
@Override
public List<KeyWrapper> getKeys() {
return Collections.singletonList(key);
}
private KeyWrapper createKeyWrapper() {
// secp256r1,NIST P-256,X9.62 prime256v1,1.2.840.10045.3.1.7
KeyPair keyPair = AbstractEcdsaKeyProviderFactory.generateEcdsaKeyPair("secp256r1");
KeyWrapper key = new KeyWrapper();
key.setKid(KeyUtils.createKeyId(keyPair.getPublic()));
key.setUse(KeyUse.SIG);
key.setType(KeyType.EC);
key.setAlgorithms(Algorithm.ES256);
key.setStatus(KeyStatus.ACTIVE);
key.setSignKey(keyPair.getPrivate());
key.setVerifyKey(keyPair.getPublic());
return key;
}
}

View file

@ -1,80 +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.keys;
import org.jboss.logging.Logger;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.common.util.Time;
import org.keycloak.crypto.*;
import java.security.KeyPair;
import java.util.Collections;
import java.util.List;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class FailsafeRsaKeyProvider implements KeyProvider {
private static final Logger logger = Logger.getLogger(FailsafeRsaKeyProvider.class);
private static KeyWrapper KEY;
private static long EXPIRES;
private KeyWrapper key;
public FailsafeRsaKeyProvider() {
logger.errorv("No active keys found, using failsafe provider, please login to admin console to add keys. Clustering is not supported.");
synchronized (FailsafeRsaKeyProvider.class) {
if (EXPIRES < Time.currentTime()) {
KEY = createKeyWrapper();
EXPIRES = Time.currentTime() + 60 * 10;
if (EXPIRES > 0) {
logger.warnv("Keys expired, re-generated kid={0}", KEY.getKid());
}
}
key = KEY;
}
}
@Override
public List<KeyWrapper> getKeys() {
return Collections.singletonList(key);
}
private KeyWrapper createKeyWrapper() {
KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048);
KeyWrapper key = new KeyWrapper();
key.setKid(KeyUtils.createKeyId(keyPair.getPublic()));
key.setUse(KeyUse.SIG);
key.setType(KeyType.RSA);
key.setAlgorithms(Algorithm.RS256, Algorithm.RS384, Algorithm.RS512);
key.setStatus(KeyStatus.ACTIVE);
key.setSignKey(keyPair.getPrivate());
key.setVerifyKey(keyPair.getPublic());
return key;
}
}

View file

@ -1,88 +0,0 @@
/*
* Copyright 2017 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.keys;
import org.jboss.logging.Logger;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.common.util.Time;
import org.keycloak.crypto.JavaAlgorithm;
import org.keycloak.crypto.KeyStatus;
import org.keycloak.crypto.KeyUse;
import org.keycloak.crypto.KeyWrapper;
import org.keycloak.models.utils.KeycloakModelUtils;
import javax.crypto.SecretKey;
import java.util.Collections;
import java.util.List;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public abstract class FailsafeSecretKeyProvider implements KeyProvider {
private static KeyWrapper KEY;
private static long EXPIRES;
private KeyWrapper key;
public FailsafeSecretKeyProvider() {
logger().errorv("No active keys found, using failsafe provider, please login to admin console to add keys. Clustering is not supported.");
synchronized (FailsafeHmacKeyProvider.class) {
if (EXPIRES < Time.currentTime()) {
KEY = createKeyWrapper();
EXPIRES = Time.currentTime() + 60 * 10;
if (EXPIRES > 0) {
logger().warnv("Keys expired, re-generated kid={0}", KEY.getKid());
}
}
key = KEY;
}
}
@Override
public List<KeyWrapper> getKeys() {
return Collections.singletonList(key);
}
private KeyWrapper createKeyWrapper() {
SecretKey secretKey = KeyUtils.loadSecretKey(KeycloakModelUtils.generateSecret(32), JavaAlgorithm.getJavaAlgorithm(getAlgorithm()));
KeyWrapper key = new KeyWrapper();
key.setKid(KeycloakModelUtils.generateId());
key.setUse(getUse());
key.setType(getType());
key.setAlgorithms(getAlgorithm());
key.setStatus(KeyStatus.ACTIVE);
key.setSecretKey(secretKey);
return key;
}
protected abstract KeyUse getUse();
protected abstract String getType();
protected abstract String getAlgorithm();
protected abstract Logger logger();
}

View file

@ -25,7 +25,7 @@ import org.keycloak.crypto.KeyUse;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class GeneratedAesKeyProvider extends GeneratedSecretKeyProvider implements KeyProvider {
public class GeneratedAesKeyProvider extends AbstractGeneratedSecretKeyProvider implements KeyProvider {
public GeneratedAesKeyProvider(ComponentModel model) {
super(model, KeyUse.ENC, KeyType.OCT, Algorithm.AES);

View file

@ -17,20 +17,23 @@
package org.keycloak.keys;
import java.util.List;
import org.jboss.logging.Logger;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.component.ComponentModel;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyUse;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.provider.ProviderConfigProperty;
import java.util.List;
import static org.keycloak.provider.ProviderConfigProperty.LIST_TYPE;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class GeneratedAesKeyProviderFactory extends GeneratedSecretKeyProviderFactory<GeneratedSecretKeyProvider> {
public class GeneratedAesKeyProviderFactory extends AbstractGeneratedSecretKeyProviderFactory<AbstractGeneratedSecretKeyProvider> {
private static final Logger logger = Logger.getLogger(GeneratedAesKeyProviderFactory.class);
@ -57,6 +60,29 @@ public class GeneratedAesKeyProviderFactory extends GeneratedSecretKeyProviderFa
return new GeneratedAesKeyProvider(model);
}
@Override
public boolean createFallbackKeys(KeycloakSession session, KeyUse keyUse, String algorithm) {
if (keyUse.equals(KeyUse.ENC) && algorithm.equals(Algorithm.AES)) {
RealmModel realm = session.getContext().getRealm();
ComponentModel generated = new ComponentModel();
generated.setName("fallback-" + algorithm);
generated.setParentId(realm.getId());
generated.setProviderId(ID);
generated.setProviderType(KeyProvider.class.getName());
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
config.putSingle(Attributes.PRIORITY_KEY, "-100");
generated.setConfig(config);
realm.addComponentModel(generated);
return true;
} else {
return false;
}
}
@Override
public String getHelpText() {
return HELP_TEXT;

View file

@ -1,19 +1,33 @@
/*
* 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.keys;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import org.jboss.logging.Logger;
import org.keycloak.common.util.Base64;
import org.keycloak.component.ComponentModel;
import org.keycloak.crypto.KeyWrapper;
import org.keycloak.models.RealmModel;
// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
public class GeneratedEcdsaKeyProvider extends AbstractEcdsaKeyProvider {
private static final Logger logger = Logger.getLogger(GeneratedEcdsaKeyProvider.class);

View file

@ -1,23 +1,35 @@
/*
* 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.keys;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.List;
import org.jboss.logging.Logger;
import org.keycloak.common.util.Base64;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.component.ComponentModel;
import org.keycloak.component.ComponentValidationException;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyUse;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.provider.ConfigurationValidationHelper;
import org.keycloak.provider.ProviderConfigProperty;
// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
import java.security.KeyPair;
import java.util.List;
public class GeneratedEcdsaKeyProviderFactory extends AbstractEcdsaKeyProviderFactory {
@ -39,6 +51,30 @@ public class GeneratedEcdsaKeyProviderFactory extends AbstractEcdsaKeyProviderFa
return new GeneratedEcdsaKeyProvider(session.getContext().getRealm(), model);
}
@Override
public boolean createFallbackKeys(KeycloakSession session, KeyUse keyUse, String algorithm) {
if (keyUse.equals(KeyUse.SIG) && (algorithm.equals(Algorithm.ES256) || algorithm.equals(Algorithm.ES384) || algorithm.equals(Algorithm.ES512))) {
RealmModel realm = session.getContext().getRealm();
ComponentModel generated = new ComponentModel();
generated.setName("fallback-" + algorithm);
generated.setParentId(realm.getId());
generated.setProviderId(ID);
generated.setProviderType(KeyProvider.class.getName());
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
config.putSingle(Attributes.PRIORITY_KEY, "-100");
config.putSingle(ECDSA_ELLIPTIC_CURVE_KEY, convertAlgorithmToECDomainParmNistRep(algorithm));
generated.setConfig(config);
realm.addComponentModel(generated);
return true;
} else {
return false;
}
}
@Override
public String getHelpText() {
return HELP_TEXT;
@ -64,18 +100,18 @@ public class GeneratedEcdsaKeyProviderFactory extends AbstractEcdsaKeyProviderFa
if (ecInNistRep == null) ecInNistRep = DEFAULT_ECDSA_ELLIPTIC_CURVE;
if (!(model.contains(ECDSA_PRIVATE_KEY_KEY) && model.contains(ECDSA_PUBLIC_KEY_KEY))) {
generateKeys(realm, model, ecInNistRep);
generateKeys(model, ecInNistRep);
logger.debugv("Generated keys for {0}", realm.getName());
} else {
String currentEc = model.get(ECDSA_ELLIPTIC_CURVE_KEY);
if (!ecInNistRep.equals(currentEc)) {
generateKeys(realm, model, ecInNistRep);
generateKeys(model, ecInNistRep);
logger.debugv("Elliptic Curve changed, generating new keys for {0}", realm.getName());
}
}
}
private void generateKeys(RealmModel realm, ComponentModel model, String ecInNistRep) {
private void generateKeys(ComponentModel model, String ecInNistRep) {
KeyPair keyPair;
try {
keyPair = generateEcdsaKeyPair(convertECDomainParmNistRepToSecRep(ecInNistRep));

View file

@ -17,8 +17,6 @@
package org.keycloak.keys;
import java.security.Key;
import org.keycloak.component.ComponentModel;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyType;
@ -28,10 +26,10 @@ import org.keycloak.crypto.KeyUse;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class GeneratedHmacKeyProvider extends GeneratedSecretKeyProvider {
public class GeneratedHmacKeyProvider extends AbstractGeneratedSecretKeyProvider {
public GeneratedHmacKeyProvider(ComponentModel model) {
super(model, KeyUse.SIG, KeyType.OCT, Algorithm.HS256);
super(model, KeyUse.SIG, KeyType.OCT, model.get(Attributes.ALGORITHM_KEY, Algorithm.HS256));
}
}

View file

@ -18,15 +18,12 @@
package org.keycloak.keys;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.common.util.Base64Url;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.component.ComponentModel;
import org.keycloak.component.ComponentValidationException;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyUse;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.provider.ConfigurationValidationHelper;
import org.keycloak.provider.ProviderConfigProperty;
import java.util.List;
@ -34,7 +31,7 @@ import java.util.List;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class GeneratedHmacKeyProviderFactory extends GeneratedSecretKeyProviderFactory<GeneratedHmacKeyProvider> {
public class GeneratedHmacKeyProviderFactory extends AbstractGeneratedSecretKeyProviderFactory<GeneratedHmacKeyProvider> {
private static final Logger logger = Logger.getLogger(GeneratedHmacKeyProviderFactory.class);
@ -42,10 +39,11 @@ public class GeneratedHmacKeyProviderFactory extends GeneratedSecretKeyProviderF
private static final String HELP_TEXT = "Generates HMAC secret key";
public static final int DEFAULT_HMAC_KEY_SIZE = 32;
public static final int DEFAULT_HMAC_KEY_SIZE = 64;
private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = SecretKeyProviderUtils.configurationBuilder()
.property(Attributes.SECRET_SIZE_PROPERTY)
.property(Attributes.HS_ALGORITHM_PROPERTY)
.build();
@Override
@ -53,6 +51,30 @@ public class GeneratedHmacKeyProviderFactory extends GeneratedSecretKeyProviderF
return new GeneratedHmacKeyProvider(model);
}
@Override
public boolean createFallbackKeys(KeycloakSession session, KeyUse keyUse, String algorithm) {
if (keyUse.equals(KeyUse.SIG) && (algorithm.equals(Algorithm.HS256) || algorithm.equals(Algorithm.HS384) || algorithm.equals(Algorithm.HS512))) {
RealmModel realm = session.getContext().getRealm();
ComponentModel generated = new ComponentModel();
generated.setName("fallback-" + algorithm);
generated.setParentId(realm.getId());
generated.setProviderId(ID);
generated.setProviderType(KeyProvider.class.getName());
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
config.putSingle(Attributes.PRIORITY_KEY, "-100");
config.putSingle(Attributes.ALGORITHM_KEY, algorithm);
generated.setConfig(config);
realm.addComponentModel(generated);
return true;
} else {
return false;
}
}
@Override
public String getHelpText() {
return HELP_TEXT;

Some files were not shown because too many files have changed in this diff Show more