Skip to content

Commit

Permalink
KEYCLOAK-5007 Used single-use cache for tracke OAuth code. OAuth code…
Browse files Browse the repository at this point in the history
… changed to be encrypted and signed JWT
  • Loading branch information
mposolda committed Sep 29, 2017
1 parent 63673c4 commit 3b6e1f4
Show file tree
Hide file tree
Showing 69 changed files with 1,568 additions and 458 deletions.
4 changes: 2 additions & 2 deletions common/src/main/java/org/keycloak/common/util/KeyUtils.java
Expand Up @@ -40,8 +40,8 @@ public class KeyUtils {
private KeyUtils() { private KeyUtils() {
} }


public static SecretKey loadSecretKey(byte[] secret) { public static SecretKey loadSecretKey(byte[] secret, String javaAlgorithmName) {
return new SecretKeySpec(secret, "HmacSHA256"); return new SecretKeySpec(secret, javaAlgorithmName);
} }


public static KeyPair generateRsaKeyPair(int keysize) { public static KeyPair generateRsaKeyPair(int keysize) {
Expand Down
Expand Up @@ -16,7 +16,7 @@ public void loadSecretKey() throws Exception {
byte[] secretBytes = new byte[32]; byte[] secretBytes = new byte[32];
ThreadLocalRandom.current().nextBytes(secretBytes); ThreadLocalRandom.current().nextBytes(secretBytes);
SecretKeySpec expected = new SecretKeySpec(secretBytes, "HmacSHA256"); SecretKeySpec expected = new SecretKeySpec(secretBytes, "HmacSHA256");
SecretKey actual = KeyUtils.loadSecretKey(secretBytes); SecretKey actual = KeyUtils.loadSecretKey(secretBytes, "HmacSHA256");
assertEquals(expected.getAlgorithm(), actual.getAlgorithm()); assertEquals(expected.getAlgorithm(), actual.getAlgorithm());
assertArrayEquals(expected.getEncoded(), actual.getEncoded()); assertArrayEquals(expected.getEncoded(), actual.getEncoded());
} }
Expand Down
12 changes: 6 additions & 6 deletions core/src/main/java/org/keycloak/jose/jwe/JWE.java
Expand Up @@ -111,7 +111,7 @@ public void setEncryptedContentInfo(byte[] initializationVector, byte[] encrypte
} }




public String encodeJwe() { public String encodeJwe() throws JWEException {
try { try {
if (header == null) { if (header == null) {
throw new IllegalStateException("Header must be set"); throw new IllegalStateException("Header must be set");
Expand Down Expand Up @@ -139,8 +139,8 @@ public String encodeJwe() {
encryptionProvider.encodeJwe(this); encryptionProvider.encodeJwe(this);


return getEncodedJweString(); return getEncodedJweString();
} catch (IOException | GeneralSecurityException e) { } catch (Exception e) {
throw new RuntimeException(e); throw new JWEException(e);
} }
} }


Expand All @@ -157,7 +157,7 @@ private String getEncodedJweString() {
} }




public JWE verifyAndDecodeJwe(String jweStr) { public JWE verifyAndDecodeJwe(String jweStr) throws JWEException {
try { try {
String[] parts = jweStr.split("\\."); String[] parts = jweStr.split("\\.");
if (parts.length != 5) { if (parts.length != 5) {
Expand Down Expand Up @@ -189,8 +189,8 @@ public JWE verifyAndDecodeJwe(String jweStr) {
encryptionProvider.verifyAndDecodeJwe(this); encryptionProvider.verifyAndDecodeJwe(this);


return this; return this;
} catch (IOException | GeneralSecurityException e) { } catch (Exception e) {
throw new RuntimeException(e); throw new JWEException(e);
} }
} }


Expand Down
35 changes: 35 additions & 0 deletions core/src/main/java/org/keycloak/jose/jwe/JWEException.java
@@ -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);
}
}
Expand Up @@ -34,18 +34,14 @@
public class AesKeyWrapAlgorithmProvider implements JWEAlgorithmProvider { public class AesKeyWrapAlgorithmProvider implements JWEAlgorithmProvider {


@Override @Override
public byte[] decodeCek(byte[] encodedCek, Key encryptionKey) throws IOException, GeneralSecurityException { public byte[] decodeCek(byte[] encodedCek, Key encryptionKey) throws Exception {
try { Wrapper encrypter = new AESWrapEngine();
Wrapper encrypter = new AESWrapEngine(); encrypter.init(false, new KeyParameter(encryptionKey.getEncoded()));
encrypter.init(false, new KeyParameter(encryptionKey.getEncoded())); return encrypter.unwrap(encodedCek, 0, encodedCek.length);
return encrypter.unwrap(encodedCek, 0, encodedCek.length);
} catch (InvalidCipherTextException icte) {
throw new IllegalStateException(icte);
}
} }


@Override @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(); Wrapper encrypter = new AESWrapEngine();
encrypter.init(true, new KeyParameter(encryptionKey.getEncoded())); encrypter.init(true, new KeyParameter(encryptionKey.getEncoded()));
byte[] cekBytes = keyStorage.getCekBytes(); byte[] cekBytes = keyStorage.getCekBytes();
Expand Down
Expand Up @@ -30,12 +30,12 @@
public class DirectAlgorithmProvider implements JWEAlgorithmProvider { public class DirectAlgorithmProvider implements JWEAlgorithmProvider {


@Override @Override
public byte[] decodeCek(byte[] encodedCek, Key encryptionKey) throws IOException, GeneralSecurityException { public byte[] decodeCek(byte[] encodedCek, Key encryptionKey) {
return new byte[0]; return new byte[0];
} }


@Override @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]; return new byte[0];
} }
} }
Expand Up @@ -29,8 +29,8 @@
*/ */
public interface JWEAlgorithmProvider { 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;


} }
Expand Up @@ -41,7 +41,7 @@ public interface JWEEncryptionProvider {
* @throws IOException * @throws IOException
* @throws GeneralSecurityException * @throws GeneralSecurityException
*/ */
void encodeJwe(JWE jwe) throws IOException, GeneralSecurityException; void encodeJwe(JWE jwe) throws Exception;




/** /**
Expand All @@ -51,7 +51,7 @@ public interface JWEEncryptionProvider {
* @throws IOException * @throws IOException
* @throws GeneralSecurityException * @throws GeneralSecurityException
*/ */
void verifyAndDecodeJwe(JWE jwe) throws IOException, GeneralSecurityException; void verifyAndDecodeJwe(JWE jwe) throws Exception;




/** /**
Expand Down
Expand Up @@ -25,6 +25,7 @@ public enum AlgorithmType {


RSA, RSA,
HMAC, HMAC,
AES,
ECDSA ECDSA


} }
39 changes: 39 additions & 0 deletions core/src/main/java/org/keycloak/representations/CodeJWT.java
@@ -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;
}

}
55 changes: 55 additions & 0 deletions core/src/main/java/org/keycloak/util/TokenUtil.java
Expand Up @@ -18,11 +18,18 @@
package org.keycloak.util; package org.keycloak.util;


import org.keycloak.OAuth2Constants; 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.JWSInput;
import org.keycloak.jose.jws.JWSInputException; import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.JsonWebToken;
import org.keycloak.representations.RefreshToken; import org.keycloak.representations.RefreshToken;


import java.io.IOException; import java.io.IOException;
import java.security.Key;


/** /**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
Expand Down Expand Up @@ -115,4 +122,52 @@ public static boolean isOfflineToken(String refreshToken) throws JWSInputExcepti
return token.getType().equals(TOKEN_TYPE_OFFLINE); 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);
}
}

} }

0 comments on commit 3b6e1f4

Please sign in to comment.