KEYCLOAK-4092 key provider for HMAC signatures

This commit is contained in:
Stian Thorgersen committed 2016-12-19 10:50:43 +01:00
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
View File
@@ -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
}