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:
parent
5b6036525c
commit
24e60747b6
190 changed files with 3240 additions and 2076 deletions
|
@ -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;
|
||||
|
|
26
core/src/main/java/org/keycloak/Token.java
Normal file
26
core/src/main/java/org/keycloak/Token.java
Normal 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();
|
||||
|
||||
}
|
25
core/src/main/java/org/keycloak/TokenCategory.java
Normal file
25
core/src/main/java/org/keycloak/TokenCategory.java
Normal 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
|
||||
}
|
|
@ -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)) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
63
core/src/main/java/org/keycloak/jose/jwk/ECPublicJWK.java
Normal file
63
core/src/main/java/org/keycloak/jose/jwk/ECPublicJWK.java
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
136
core/src/test/java/org/keycloak/jose/jwk/JWKTest.java
Normal file
136
core/src/test/java/org/keycloak/jose/jwk/JWKTest.java
Normal 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);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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() {
|
||||
}
|
||||
|
||||
}
|
|
@ -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() {
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
|
|
|
@ -180,4 +180,11 @@ public interface KeycloakSession {
|
|||
*/
|
||||
ThemeManager theme();
|
||||
|
||||
/**
|
||||
* Token manager
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
TokenManager tokens();
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -321,4 +321,4 @@ public class HttpClientBuilder {
|
|||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
@ -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 {
|
|
@ -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());
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue