KEYCLOAK-4092 key provider for HMAC signatures
This commit is contained in:
1 parent
a4cbf130b4
commit
f29bb7d501
65 files changed
+1578
-329
No files matched your search
@@ -19,11 +19,7 @@ package org.keycloak;
|
||||
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.jose.jws.JWSHeader;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.jose.jws.JWSInputException;
|
||||
import org.keycloak.jose.jws.crypto.RSAProvider;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.util.TokenUtil;
|
||||
|
||||
import java.security.PublicKey;
|
||||
|
||||
@@ -33,18 +29,10 @@ import java.security.PublicKey;
|
||||
*/
|
||||
public class RSATokenVerifier {
|
||||
|
||||
private final String tokenString;
|
||||
private PublicKey publicKey;
|
||||
private String realmUrl;
|
||||
private boolean checkTokenType = true;
|
||||
private boolean checkActive = true;
|
||||
private boolean checkRealmUrl = true;
|
||||
|
||||
private JWSInput jws;
|
||||
private AccessToken token;
|
||||
private TokenVerifier tokenVerifier;
|
||||
|
||||
private RSATokenVerifier(String tokenString) {
|
||||
this.tokenString = tokenString;
|
||||
this.tokenVerifier = TokenVerifier.create(tokenString);
|
||||
}
|
||||
|
||||
public static RSATokenVerifier create(String tokenString) {
|
||||
@@ -60,94 +48,45 @@ public class RSATokenVerifier {
|
||||
}
|
||||
|
||||
public RSATokenVerifier publicKey(PublicKey publicKey) {
|
||||
this.publicKey = publicKey;
|
||||
tokenVerifier.publicKey(publicKey);
|
||||
return this;
|
||||
}
|
||||
|
||||
public RSATokenVerifier realmUrl(String realmUrl) {
|
||||
this.realmUrl = realmUrl;
|
||||
tokenVerifier.realmUrl(realmUrl);
|
||||
return this;
|
||||
}
|
||||
|
||||
public RSATokenVerifier checkTokenType(boolean checkTokenType) {
|
||||
this.checkTokenType = checkTokenType;
|
||||
tokenVerifier.checkTokenType(checkTokenType);
|
||||
return this;
|
||||
}
|
||||
|
||||
public RSATokenVerifier checkActive(boolean checkActive) {
|
||||
this.checkActive = checkActive;
|
||||
tokenVerifier.checkActive(checkActive);
|
||||
return this;
|
||||
}
|
||||
|
||||
public RSATokenVerifier checkRealmUrl(boolean checkRealmUrl) {
|
||||
this.checkRealmUrl = checkRealmUrl;
|
||||
tokenVerifier.checkRealmUrl(checkRealmUrl);
|
||||
return this;
|
||||
}
|
||||
|
||||
public RSATokenVerifier parse() throws VerificationException {
|
||||
if (jws == null) {
|
||||
if (tokenString == null) {
|
||||
throw new VerificationException("Token not set");
|
||||
}
|
||||
|
||||
try {
|
||||
jws = new JWSInput(tokenString);
|
||||
} catch (JWSInputException e) {
|
||||
throw new VerificationException("Failed to parse JWT", e);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
token = jws.readJsonContent(AccessToken.class);
|
||||
} catch (JWSInputException e) {
|
||||
throw new VerificationException("Failed to read access token from JWT", e);
|
||||
}
|
||||
}
|
||||
tokenVerifier.parse();
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccessToken getToken() throws VerificationException {
|
||||
parse();
|
||||
return token;
|
||||
return tokenVerifier.getToken();
|
||||
}
|
||||
|
||||
public JWSHeader getHeader() throws VerificationException {
|
||||
parse();
|
||||
return jws.getHeader();
|
||||
return tokenVerifier.getHeader();
|
||||
}
|
||||
|
||||
public RSATokenVerifier verify() throws VerificationException {
|
||||
parse();
|
||||
|
||||
if (publicKey == null) {
|
||||
throw new VerificationException("Public key not set");
|
||||
}
|
||||
|
||||
if (checkRealmUrl && realmUrl == null) {
|
||||
throw new VerificationException("Realm URL not set");
|
||||
}
|
||||
|
||||
if (!RSAProvider.verify(jws, publicKey)) {
|
||||
throw new VerificationException("Invalid token signature");
|
||||
}
|
||||
|
||||
String user = token.getSubject();
|
||||
if (user == null) {
|
||||
throw new VerificationException("Subject missing in token");
|
||||
}
|
||||
|
||||
if (checkRealmUrl && !realmUrl.equals(token.getIssuer())) {
|
||||
throw new VerificationException("Invalid token issuer. Expected '" + realmUrl + "', but was '" + token.getIssuer() + "'");
|
||||
}
|
||||
|
||||
if (checkTokenType && !TokenUtil.TOKEN_TYPE_BEARER.equalsIgnoreCase(token.getType())) {
|
||||
throw new VerificationException("Token type is incorrect. Expected '" + TokenUtil.TOKEN_TYPE_BEARER + "' but was '" + token.getType() + "'");
|
||||
}
|
||||
|
||||
if (checkActive && !token.isActive()) {
|
||||
throw new VerificationException("Token is not active");
|
||||
}
|
||||
|
||||
tokenVerifier.verify();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* 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 org.keycloak.common.VerificationException;
|
||||
import org.keycloak.jose.jws.Algorithm;
|
||||
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.crypto.HMACProvider;
|
||||
import org.keycloak.jose.jws.crypto.RSAProvider;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.util.TokenUtil;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.security.PublicKey;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class TokenVerifier {
|
||||
|
||||
private final String tokenString;
|
||||
private PublicKey publicKey;
|
||||
private SecretKey secretKey;
|
||||
private String realmUrl;
|
||||
private boolean checkTokenType = true;
|
||||
private boolean checkActive = true;
|
||||
private boolean checkRealmUrl = true;
|
||||
|
||||
private JWSInput jws;
|
||||
private AccessToken token;
|
||||
|
||||
protected TokenVerifier(String tokenString) {
|
||||
this.tokenString = tokenString;
|
||||
}
|
||||
|
||||
public static TokenVerifier create(String tokenString) {
|
||||
return new TokenVerifier(tokenString);
|
||||
}
|
||||
|
||||
public TokenVerifier publicKey(PublicKey publicKey) {
|
||||
this.publicKey = publicKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TokenVerifier secretKey(SecretKey secretKey) {
|
||||
this.secretKey = secretKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TokenVerifier realmUrl(String realmUrl) {
|
||||
this.realmUrl = realmUrl;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TokenVerifier checkTokenType(boolean checkTokenType) {
|
||||
this.checkTokenType = checkTokenType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TokenVerifier checkActive(boolean checkActive) {
|
||||
this.checkActive = checkActive;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TokenVerifier checkRealmUrl(boolean checkRealmUrl) {
|
||||
this.checkRealmUrl = checkRealmUrl;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TokenVerifier parse() throws VerificationException {
|
||||
if (jws == null) {
|
||||
if (tokenString == null) {
|
||||
throw new VerificationException("Token not set");
|
||||
}
|
||||
|
||||
try {
|
||||
jws = new JWSInput(tokenString);
|
||||
} catch (JWSInputException e) {
|
||||
throw new VerificationException("Failed to parse JWT", e);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
token = jws.readJsonContent(AccessToken.class);
|
||||
} catch (JWSInputException e) {
|
||||
throw new VerificationException("Failed to read access token from JWT", e);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccessToken getToken() throws VerificationException {
|
||||
parse();
|
||||
return token;
|
||||
}
|
||||
|
||||
public JWSHeader getHeader() throws VerificationException {
|
||||
parse();
|
||||
return jws.getHeader();
|
||||
}
|
||||
|
||||
public TokenVerifier verify() throws VerificationException {
|
||||
parse();
|
||||
|
||||
if (checkRealmUrl && realmUrl == null) {
|
||||
throw new VerificationException("Realm URL not set");
|
||||
}
|
||||
|
||||
AlgorithmType algorithmType = getHeader().getAlgorithm().getType();
|
||||
|
||||
if (AlgorithmType.RSA.equals(algorithmType)) {
|
||||
if (publicKey == null) {
|
||||
throw new VerificationException("Public key not set");
|
||||
}
|
||||
|
||||
if (!RSAProvider.verify(jws, publicKey)) {
|
||||
throw new VerificationException("Invalid token signature");
|
||||
}
|
||||
} else if (AlgorithmType.HMAC.equals(algorithmType)) {
|
||||
if (secretKey == null) {
|
||||
throw new VerificationException("Secret key not set");
|
||||
}
|
||||
|
||||
if (!HMACProvider.verify(jws, secretKey)) {
|
||||
throw new VerificationException("Invalid token signature");
|
||||
}
|
||||
} else {
|
||||
throw new VerificationException("Unknown or unsupported token algorith");
|
||||
}
|
||||
|
||||
String user = token.getSubject();
|
||||
if (user == null) {
|
||||
throw new VerificationException("Subject missing in token");
|
||||
}
|
||||
|
||||
if (checkRealmUrl && !realmUrl.equals(token.getIssuer())) {
|
||||
throw new VerificationException("Invalid token issuer. Expected '" + realmUrl + "', but was '" + token.getIssuer() + "'");
|
||||
}
|
||||
|
||||
if (checkTokenType && !TokenUtil.TOKEN_TYPE_BEARER.equalsIgnoreCase(token.getType())) {
|
||||
throw new VerificationException("Token type is incorrect. Expected '" + TokenUtil.TOKEN_TYPE_BEARER + "' but was '" + token.getType() + "'");
|
||||
}
|
||||
|
||||
if (checkActive && !token.isActive()) {
|
||||
throw new VerificationException("Token is not active");
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,23 +26,30 @@ import org.keycloak.jose.jws.crypto.SignatureProvider;
|
||||
*/
|
||||
public enum Algorithm {
|
||||
|
||||
none(null),
|
||||
HS256(null),
|
||||
HS384(null),
|
||||
HS512(null),
|
||||
RS256(new RSAProvider()),
|
||||
RS384(new RSAProvider()),
|
||||
RS512(new RSAProvider()),
|
||||
ES256(null),
|
||||
ES384(null),
|
||||
ES512(null)
|
||||
none(null, null),
|
||||
HS256(AlgorithmType.HMAC, null),
|
||||
HS384(AlgorithmType.HMAC, null),
|
||||
HS512(AlgorithmType.HMAC, null),
|
||||
RS256(AlgorithmType.RSA, new RSAProvider()),
|
||||
RS384(AlgorithmType.RSA, new RSAProvider()),
|
||||
RS512(AlgorithmType.RSA, new RSAProvider()),
|
||||
ES256(AlgorithmType.ECDSA, null),
|
||||
ES384(AlgorithmType.ECDSA, null),
|
||||
ES512(AlgorithmType.ECDSA, null)
|
||||
;
|
||||
|
||||
private AlgorithmType type;
|
||||
private SignatureProvider provider;
|
||||
|
||||
Algorithm(SignatureProvider provider) {
|
||||
Algorithm(AlgorithmType type, SignatureProvider provider) {
|
||||
this.type = type;
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
public AlgorithmType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public SignatureProvider getProvider() {
|
||||
return provider;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public enum AlgorithmType {
|
||||
|
||||
RSA,
|
||||
HMAC,
|
||||
ECDSA
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user