KEYCLOAK-7560 Refactor Token Sign and Verify by Token Signature SPI
This commit is contained in:
1 parent
bd4098191b
commit
5b6036525c
47 files changed
+1979
-83
No files matched your search
@@ -24,12 +24,15 @@ 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.jose.jws.crypto.HMACProvider;
|
||||
import org.keycloak.jose.jws.crypto.RSAProvider;
|
||||
import org.keycloak.representations.JsonWebToken;
|
||||
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;
|
||||
@@ -144,6 +147,18 @@ 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;
|
||||
return this;
|
||||
}
|
||||
|
||||
protected TokenVerifier(String tokenString, Class<T> clazz) {
|
||||
this.tokenString = tokenString;
|
||||
this.clazz = clazz;
|
||||
@@ -337,6 +352,12 @@ 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) {
|
||||
@@ -361,6 +382,13 @@ public class TokenVerifier<T extends JsonWebToken> {
|
||||
}
|
||||
}
|
||||
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
|
||||
public TokenVerifier<T> verify() throws VerificationException {
|
||||
if (getToken() == null) {
|
||||
parse();
|
||||
|
||||
@@ -25,6 +25,7 @@ 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;
|
||||
|
||||
/**
|
||||
@@ -36,7 +37,7 @@ public class JWSBuilder {
|
||||
String kid;
|
||||
String contentType;
|
||||
byte[] contentBytes;
|
||||
|
||||
|
||||
public JWSBuilder type(String type) {
|
||||
this.type = type;
|
||||
return this;
|
||||
@@ -66,10 +67,34 @@ 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 String encodeHeader(Algorithm alg) {
|
||||
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(alg.toString()).append("\"");
|
||||
builder.append("\"alg\":\"").append(sigAlgName).append("\"");
|
||||
|
||||
if (type != null) builder.append(",\"typ\" : \"").append(type).append("\"");
|
||||
if (kid != null) builder.append(",\"kid\" : \"").append(kid).append("\"");
|
||||
@@ -82,24 +107,6 @@ public class JWSBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
encoding.append(encodeHeader(alg));
|
||||
encoding.append('.');
|
||||
encoding.append(Base64Url.encode(data));
|
||||
}
|
||||
|
||||
protected byte[] marshalContent() {
|
||||
return contentBytes;
|
||||
}
|
||||
|
||||
public class EncodingBuilder {
|
||||
public String none() {
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
@@ -108,6 +115,20 @@ public class JWSBuilder {
|
||||
return encodeAll(buffer, null);
|
||||
}
|
||||
|
||||
// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
|
||||
public String sign(JWSSignatureProvider signatureProvider, String sigAlgName, Key key) {
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
byte[] data = marshalContent();
|
||||
encode(sigAlgName, data, buffer);
|
||||
byte[] signature = null;
|
||||
try {
|
||||
signature = signatureProvider.sign(buffer.toString().getBytes("UTF-8"), sigAlgName, key);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return encodeAll(buffer, signature);
|
||||
}
|
||||
|
||||
public String sign(Algorithm algorithm, PrivateKey privateKey) {
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
byte[] data = marshalContent();
|
||||
@@ -133,7 +154,6 @@ public class JWSBuilder {
|
||||
return sign(Algorithm.RS512, privateKey);
|
||||
}
|
||||
|
||||
|
||||
public String hmac256(byte[] sharedSecret) {
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
byte[] data = marshalContent();
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
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,6 +25,7 @@ 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,18 +27,16 @@ import java.util.Arrays;
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class HashProvider {
|
||||
|
||||
// See "at_hash" and "c_hash" in OIDC specification
|
||||
public static String oidcHash(Algorithm jwtAlgorithm, String input) {
|
||||
byte[] digest = digest(jwtAlgorithm, input);
|
||||
// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
|
||||
public static String oidcHash(String jwtAlgorithmName, String input) {
|
||||
byte[] digest = digest(jwtAlgorithmName, input);
|
||||
|
||||
int hashLength = digest.length / 2;
|
||||
byte[] hashInput = Arrays.copyOf(digest, hashLength);
|
||||
|
||||
return Base64Url.encode(hashInput);
|
||||
}
|
||||
|
||||
private static byte[] digest(Algorithm algorithm, String input) {
|
||||
private static byte[] digest(String algorithm, String input) {
|
||||
String digestAlg = getJavaDigestAlgorithm(algorithm);
|
||||
|
||||
try {
|
||||
@@ -49,18 +47,22 @@ public class HashProvider {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getJavaDigestAlgorithm(Algorithm alg) {
|
||||
private static String getJavaDigestAlgorithm(String alg) {
|
||||
switch (alg) {
|
||||
case RS256:
|
||||
case "RS256":
|
||||
return "SHA-256";
|
||||
case RS384:
|
||||
case "RS384":
|
||||
return "SHA-384";
|
||||
case RS512:
|
||||
case "RS512":
|
||||
return "SHA-512";
|
||||
default:
|
||||
throw new IllegalArgumentException("Not an RSA Algorithm");
|
||||
}
|
||||
}
|
||||
|
||||
// See "at_hash" and "c_hash" in OIDC specification
|
||||
public static String oidcHash(Algorithm jwtAlgorithm, String input) {
|
||||
return oidcHash(jwtAlgorithm.name(), input);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user