KEYCLOAK-5007 Used single-use cache for tracke OAuth code. OAuth code changed to be encrypted and signed JWT

This commit is contained in:
mposolda committed 2017-09-29 13:20:22 +02:00
1 parent 63673c4328
commit 3b6e1f4e93
69 files changed
+1574 -464

No files matched your search

@@ -111,7 +111,7 @@ public class JWE {
}
public String encodeJwe() {
public String encodeJwe() throws JWEException {
try {
if (header == null) {
throw new IllegalStateException("Header must be set");
@@ -139,8 +139,8 @@ public class JWE {
encryptionProvider.encodeJwe(this);
return getEncodedJweString();
} catch (IOException | GeneralSecurityException e) {
throw new RuntimeException(e);
} catch (Exception e) {
throw new JWEException(e);
}
}
@@ -157,7 +157,7 @@ public class JWE {
}
public JWE verifyAndDecodeJwe(String jweStr) {
public JWE verifyAndDecodeJwe(String jweStr) throws JWEException {
try {
String[] parts = jweStr.split("\\.");
if (parts.length != 5) {
@@ -189,8 +189,8 @@ public class JWE {
encryptionProvider.verifyAndDecodeJwe(this);
return this;
} catch (IOException | GeneralSecurityException e) {
throw new RuntimeException(e);
} catch (Exception e) {
throw new JWEException(e);
}
}
@@ -0,0 +1,35 @@
/*
* 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.jose.jwe;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class JWEException extends Exception {
public JWEException(String s) {
super(s);
}
public JWEException() {
}
public JWEException(Throwable throwable) {
super(throwable);
}
}
@@ -34,18 +34,14 @@ import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
public class AesKeyWrapAlgorithmProvider implements JWEAlgorithmProvider {
@Override
public byte[] decodeCek(byte[] encodedCek, Key encryptionKey) throws IOException, GeneralSecurityException {
try {
Wrapper encrypter = new AESWrapEngine();
encrypter.init(false, new KeyParameter(encryptionKey.getEncoded()));
return encrypter.unwrap(encodedCek, 0, encodedCek.length);
} catch (InvalidCipherTextException icte) {
throw new IllegalStateException(icte);
}
public byte[] decodeCek(byte[] encodedCek, Key encryptionKey) throws Exception {
Wrapper encrypter = new AESWrapEngine();
encrypter.init(false, new KeyParameter(encryptionKey.getEncoded()));
return encrypter.unwrap(encodedCek, 0, encodedCek.length);
}
@Override
public byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key encryptionKey) throws IOException, GeneralSecurityException {
public byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key encryptionKey) throws Exception {
Wrapper encrypter = new AESWrapEngine();
encrypter.init(true, new KeyParameter(encryptionKey.getEncoded()));
byte[] cekBytes = keyStorage.getCekBytes();
@@ -30,12 +30,12 @@ import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
public class DirectAlgorithmProvider implements JWEAlgorithmProvider {
@Override
public byte[] decodeCek(byte[] encodedCek, Key encryptionKey) throws IOException, GeneralSecurityException {
public byte[] decodeCek(byte[] encodedCek, Key encryptionKey) {
return new byte[0];
}
@Override
public byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key encryptionKey) throws IOException, GeneralSecurityException {
public byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key encryptionKey) {
return new byte[0];
}
}
@@ -29,8 +29,8 @@ import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
*/
public interface JWEAlgorithmProvider {
byte[] decodeCek(byte[] encodedCek, Key encryptionKey) throws IOException, GeneralSecurityException;
byte[] decodeCek(byte[] encodedCek, Key encryptionKey) throws Exception;
byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key encryptionKey) throws IOException, GeneralSecurityException;
byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key encryptionKey) throws Exception;
}
@@ -41,7 +41,7 @@ public interface JWEEncryptionProvider {
* @throws IOException
* @throws GeneralSecurityException
*/
void encodeJwe(JWE jwe) throws IOException, GeneralSecurityException;
void encodeJwe(JWE jwe) throws Exception;
/**
@@ -51,7 +51,7 @@ public interface JWEEncryptionProvider {
* @throws IOException
* @throws GeneralSecurityException
*/
void verifyAndDecodeJwe(JWE jwe) throws IOException, GeneralSecurityException;
void verifyAndDecodeJwe(JWE jwe) throws Exception;
/**
@@ -25,6 +25,7 @@ public enum AlgorithmType {
RSA,
HMAC,
AES,
ECDSA
}
@@ -0,0 +1,39 @@
/*
* 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.representations;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class CodeJWT extends JsonWebToken {
@JsonProperty("uss")
protected String userSessionId;
public String getUserSessionId() {
return userSessionId;
}
public CodeJWT userSessionId(String userSessionId) {
this.userSessionId = userSessionId;
return this;
}
}
@@ -18,11 +18,18 @@
package org.keycloak.util;
import org.keycloak.OAuth2Constants;
import org.keycloak.jose.jwe.JWE;
import org.keycloak.jose.jwe.JWEConstants;
import org.keycloak.jose.jwe.JWEException;
import org.keycloak.jose.jwe.JWEHeader;
import org.keycloak.jose.jwe.JWEKeyStorage;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.JsonWebToken;
import org.keycloak.representations.RefreshToken;
import java.io.IOException;
import java.security.Key;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -115,4 +122,52 @@ public class TokenUtil {
return token.getType().equals(TOKEN_TYPE_OFFLINE);
}
public static String jweDirectEncode(Key aesKey, Key hmacKey, JsonWebToken jwt) throws JWEException {
int keyLength = aesKey.getEncoded().length;
String encAlgorithm;
switch (keyLength) {
case 16: encAlgorithm = JWEConstants.A128CBC_HS256;
break;
case 24: encAlgorithm = JWEConstants.A192CBC_HS384;
break;
case 32: encAlgorithm = JWEConstants.A256CBC_HS512;
break;
default: throw new IllegalArgumentException("Bad size for Encryption key: " + aesKey + ". Valid sizes are 16, 24, 32.");
}
try {
byte[] contentBytes = JsonSerialization.writeValueAsBytes(jwt);
JWEHeader jweHeader = new JWEHeader(JWEConstants.DIR, encAlgorithm, null);
JWE jwe = new JWE()
.header(jweHeader)
.content(contentBytes);
jwe.getKeyStorage()
.setCEKKey(aesKey, JWEKeyStorage.KeyUse.ENCRYPTION)
.setCEKKey(hmacKey, JWEKeyStorage.KeyUse.SIGNATURE);
return jwe.encodeJwe();
} catch (IOException ioe) {
throw new JWEException(ioe);
}
}
public static <T extends JsonWebToken> T jweDirectVerifyAndDecode(Key aesKey, Key hmacKey, String jweStr, Class<T> expectedClass) throws JWEException {
JWE jwe = new JWE();
jwe.getKeyStorage()
.setCEKKey(aesKey, JWEKeyStorage.KeyUse.ENCRYPTION)
.setCEKKey(hmacKey, JWEKeyStorage.KeyUse.SIGNATURE);
jwe.verifyAndDecodeJwe(jweStr);
try {
return JsonSerialization.readValue(jwe.getContent(), expectedClass);
} catch (IOException ioe) {
throw new JWEException(ioe);
}
}
}
@@ -28,6 +28,7 @@ import org.junit.Test;
import org.keycloak.common.util.Base64Url;
import org.keycloak.jose.jwe.JWE;
import org.keycloak.jose.jwe.JWEConstants;
import org.keycloak.jose.jwe.JWEException;
import org.keycloak.jose.jwe.JWEHeader;
import org.keycloak.jose.jwe.JWEKeyStorage;
@@ -36,7 +37,7 @@ import org.keycloak.jose.jwe.JWEKeyStorage;
*/
public class JWETest {
private static final String PAYLOAD = "Hello world! How are you? This is some quite a long text, which is much longer than just simple 'Hello World'";
private static final String PAYLOAD = "Hello world! How are you man? I hope you are fine. This is some quite a long text, which is much longer than just simple 'Hello World'";
private static final byte[] HMAC_SHA256_KEY = new byte[] { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 13, 14, 15, 16 };
private static final byte[] AES_128_KEY = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
@@ -49,43 +50,25 @@ public class JWETest {
SecretKey aesKey = new SecretKeySpec(AES_128_KEY, "AES");
SecretKey hmacKey = new SecretKeySpec(HMAC_SHA256_KEY, "HMACSHA2");
JWEHeader jweHeader = new JWEHeader(JWEConstants.DIR, JWEConstants.A128CBC_HS256, null);
JWE jwe = new JWE()
.header(jweHeader)
.content(PAYLOAD.getBytes("UTF-8"));
jwe.getKeyStorage()
.setCEKKey(aesKey, JWEKeyStorage.KeyUse.ENCRYPTION)
.setCEKKey(hmacKey, JWEKeyStorage.KeyUse.SIGNATURE);
String encodedContent = jwe.encodeJwe();
System.out.println("Encoded content: " + encodedContent);
System.out.println("Encoded content length: " + encodedContent.length());
jwe = new JWE();
jwe.getKeyStorage()
.setCEKKey(aesKey, JWEKeyStorage.KeyUse.ENCRYPTION)
.setCEKKey(hmacKey, JWEKeyStorage.KeyUse.SIGNATURE);
jwe.verifyAndDecodeJwe(encodedContent);
String decodedContent = new String(jwe.getContent(), "UTF-8");
Assert.assertEquals(PAYLOAD, decodedContent);
testDirectEncryptAndDecrypt(aesKey, hmacKey, JWEConstants.A128CBC_HS256, PAYLOAD, true);
}
@Test
// Works just on OpenJDK 8. Other JDKs (IBM, Oracle) have restrictions on maximum key size of AES to be 128
// @Test
public void testDirect_Aes256CbcHmacSha512() throws Exception {
final SecretKey aesKey = new SecretKeySpec(AES_256_KEY, "AES");
final SecretKey hmacKey = new SecretKeySpec(HMAC_SHA512_KEY, "HMACSHA2");
JWEHeader jweHeader = new JWEHeader(JWEConstants.DIR, JWEConstants.A256CBC_HS512, null);
testDirectEncryptAndDecrypt(aesKey, hmacKey, JWEConstants.A256CBC_HS512, PAYLOAD, true);
}
private void testDirectEncryptAndDecrypt(Key aesKey, Key hmacKey, String encAlgorithm, String payload, boolean sysout) throws Exception {
JWEHeader jweHeader = new JWEHeader(JWEConstants.DIR, encAlgorithm, null);
JWE jwe = new JWE()
.header(jweHeader)
.content(PAYLOAD.getBytes("UTF-8"));
.content(payload.getBytes("UTF-8"));
jwe.getKeyStorage()
.setCEKKey(aesKey, JWEKeyStorage.KeyUse.ENCRYPTION)
@@ -93,8 +76,10 @@ public class JWETest {
String encodedContent = jwe.encodeJwe();
System.out.println("Encoded content: " + encodedContent);
System.out.println("Encoded content length: " + encodedContent.length());
if (sysout) {
System.out.println("Encoded content: " + encodedContent);
System.out.println("Encoded content length: " + encodedContent.length());
}
jwe = new JWE();
jwe.getKeyStorage()
@@ -105,8 +90,32 @@ public class JWETest {
String decodedContent = new String(jwe.getContent(), "UTF-8");
Assert.assertEquals(PAYLOAD, decodedContent);
Assert.assertEquals(payload, decodedContent);
}
// @Test
public void testPerfDirect() throws Exception {
int iterations = 50000;
long start = System.currentTimeMillis();
for (int i=0 ; i<iterations ; i++) {
// took around 2950 ms with 50000 iterations
SecretKey aesKey = new SecretKeySpec(AES_128_KEY, "AES");
SecretKey hmacKey = new SecretKeySpec(HMAC_SHA256_KEY, "HMACSHA2");
String encAlg = JWEConstants.A128CBC_HS256;
// Similar perf like AES128CBC_HS256
//SecretKey aesKey = new SecretKeySpec(AES_256_KEY, "AES");
//SecretKey hmacKey = new SecretKeySpec(HMAC_SHA512_KEY, "HMACSHA2");
//String encAlg = JWEConstants.A256CBC_HS512;
String payload = PAYLOAD + i;
testDirectEncryptAndDecrypt(aesKey, hmacKey, encAlg, payload, false);
}
long took = System.currentTimeMillis() - start;
System.out.println("Iterations: " + iterations + ", took: " + took);
}
@Test
@@ -139,7 +148,7 @@ public class JWETest {
@Test
public void externalJweAes128CbcHmacSha256Test() throws UnsupportedEncodingException {
public void externalJweAes128CbcHmacSha256Test() throws UnsupportedEncodingException, JWEException {
String externalJwe = "eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiZGlyIn0..qysUrI1iVtiG4Z4jyr7XXg.apdNSQhR7WDMg6IHf5aLVI0gGp6JuOHYmIUtflns4WHmyxOOnh_GShLI6DWaK_SiywTV5gZvZYtl8H8Iv5fTfLkc4tiDDjbdtmsOP7tqyRxVh069gU5UvEAgmCXbIKALutgYXcYe2WM4E6BIHPTSt8jXdkktFcm7XHiD7mpakZyjXsG8p3XVkQJ72WbJI_t6.Ks6gHeko7BRTZ4CFs5ijRA";
System.out.println("External encoded content length: " + externalJwe.length());
@@ -159,8 +168,9 @@ public class JWETest {
}
@Test
public void externalJweAes256CbcHmacSha512Test() throws UnsupportedEncodingException {
// Works just on OpenJDK 8. Other JDKs (IBM, Oracle) have restrictions on maximum key size of AES to be 128
// @Test
public void externalJweAes256CbcHmacSha512Test() throws UnsupportedEncodingException, JWEException {
String externalJwe = "eyJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwiYWxnIjoiZGlyIn0..xUPndQ5U69CYaWMKr4nyeg.AzSzba6OdNsvTIoNpub8d2TmYnkY7W8Sd-1S33DjJwJsSaNcfvfXBq5bqXAGVAnLHrLZJKWoEYsmOrYHz3Nao-kpLtUpc4XZI8yiYUqkHTjmxZnfD02R6hz31a5KBCnDTtUEv23VSxm8yUyQKoUTpVHbJ3b2VQvycg2XFUXPsA6oaSSEpz-uwe1Vmun2hUBB.Qal4rMYn1RrXQ9AQ9ONUjUXvlS2ow8np-T8QWMBR0ns";
System.out.println("External encoded content length: " + externalJwe.length());
@@ -181,7 +191,7 @@ public class JWETest {
@Test
public void externalJweAesKeyWrapTest() throws Exception {
public void externalJweAes128KeyWrapTest() throws Exception {
// See example "A.3" from JWE specification - https://tools.ietf.org/html/rfc7516#page-41
String externalJwe = "eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ.AxY8DCtDaGlsbGljb3RoZQ.KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY.U0m_YmjN04DJvceFICbCVQ";