KEYCLOAK-5569 Added JWE
This commit is contained in:
parent
4c71e2ec17
commit
63673c4328
13 changed files with 1222 additions and 3 deletions
197
core/src/main/java/org/keycloak/jose/jwe/JWE.java
Normal file
197
core/src/main/java/org/keycloak/jose/jwe/JWE.java
Normal file
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
import org.keycloak.common.util.Base64Url;
|
||||
import org.keycloak.common.util.BouncyIntegration;
|
||||
import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
|
||||
import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class JWE {
|
||||
|
||||
static {
|
||||
BouncyIntegration.init();
|
||||
}
|
||||
|
||||
private JWEHeader header;
|
||||
private String base64Header;
|
||||
|
||||
private JWEKeyStorage keyStorage = new JWEKeyStorage();
|
||||
private String base64Cek;
|
||||
|
||||
private byte[] initializationVector;
|
||||
|
||||
private byte[] content;
|
||||
private byte[] encryptedContent;
|
||||
|
||||
private byte[] authenticationTag;
|
||||
|
||||
public JWE header(JWEHeader header) {
|
||||
this.header = header;
|
||||
this.base64Header = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
JWEHeader getHeader() {
|
||||
if (header == null && base64Header != null) {
|
||||
try {
|
||||
byte[] decodedHeader = Base64Url.decode(base64Header);
|
||||
header = JsonSerialization.readValue(decodedHeader, JWEHeader.class);
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
}
|
||||
}
|
||||
return header;
|
||||
}
|
||||
|
||||
public String getBase64Header() throws IOException {
|
||||
if (base64Header == null && header != null) {
|
||||
byte[] contentBytes = JsonSerialization.writeValueAsBytes(header);
|
||||
base64Header = Base64Url.encode(contentBytes);
|
||||
}
|
||||
return base64Header;
|
||||
}
|
||||
|
||||
|
||||
public JWEKeyStorage getKeyStorage() {
|
||||
return keyStorage;
|
||||
}
|
||||
|
||||
|
||||
public byte[] getInitializationVector() {
|
||||
return initializationVector;
|
||||
}
|
||||
|
||||
|
||||
public JWE content(byte[] content) {
|
||||
this.content = content;
|
||||
return this;
|
||||
}
|
||||
|
||||
public byte[] getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public byte[] getEncryptedContent() {
|
||||
return encryptedContent;
|
||||
}
|
||||
|
||||
|
||||
public byte[] getAuthenticationTag() {
|
||||
return authenticationTag;
|
||||
}
|
||||
|
||||
|
||||
public void setEncryptedContentInfo(byte[] initializationVector, byte[] encryptedContent, byte[] authenticationTag) {
|
||||
this.initializationVector = initializationVector;
|
||||
this.encryptedContent = encryptedContent;
|
||||
this.authenticationTag = authenticationTag;
|
||||
}
|
||||
|
||||
|
||||
public String encodeJwe() {
|
||||
try {
|
||||
if (header == null) {
|
||||
throw new IllegalStateException("Header must be set");
|
||||
}
|
||||
if (content == null) {
|
||||
throw new IllegalStateException("Content must be set");
|
||||
}
|
||||
|
||||
JWEAlgorithmProvider algorithmProvider = JWERegistry.getAlgProvider(header.getAlgorithm());
|
||||
if (algorithmProvider == null) {
|
||||
throw new IllegalArgumentException("No provider for alg '" + header.getAlgorithm() + "'");
|
||||
}
|
||||
|
||||
JWEEncryptionProvider encryptionProvider = JWERegistry.getEncProvider(header.getEncryptionAlgorithm());
|
||||
if (encryptionProvider == null) {
|
||||
throw new IllegalArgumentException("No provider for enc '" + header.getAlgorithm() + "'");
|
||||
}
|
||||
|
||||
keyStorage.setEncryptionProvider(encryptionProvider);
|
||||
keyStorage.getCEKKey(JWEKeyStorage.KeyUse.ENCRYPTION, true); // Will generate CEK if it's not already present
|
||||
|
||||
byte[] encodedCEK = algorithmProvider.encodeCek(encryptionProvider, keyStorage, keyStorage.getEncryptionKey());
|
||||
base64Cek = Base64Url.encode(encodedCEK);
|
||||
|
||||
encryptionProvider.encodeJwe(this);
|
||||
|
||||
return getEncodedJweString();
|
||||
} catch (IOException | GeneralSecurityException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private String getEncodedJweString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append(base64Header).append(".")
|
||||
.append(base64Cek).append(".")
|
||||
.append(Base64Url.encode(initializationVector)).append(".")
|
||||
.append(Base64Url.encode(encryptedContent)).append(".")
|
||||
.append(Base64Url.encode(authenticationTag));
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
|
||||
public JWE verifyAndDecodeJwe(String jweStr) {
|
||||
try {
|
||||
String[] parts = jweStr.split("\\.");
|
||||
if (parts.length != 5) {
|
||||
throw new IllegalStateException("Not a JWE String");
|
||||
}
|
||||
|
||||
this.base64Header = parts[0];
|
||||
this.base64Cek = parts[1];
|
||||
this.initializationVector = Base64Url.decode(parts[2]);
|
||||
this.encryptedContent = Base64Url.decode(parts[3]);
|
||||
this.authenticationTag = Base64Url.decode(parts[4]);
|
||||
|
||||
this.header = getHeader();
|
||||
JWEAlgorithmProvider algorithmProvider = JWERegistry.getAlgProvider(header.getAlgorithm());
|
||||
if (algorithmProvider == null) {
|
||||
throw new IllegalArgumentException("No provider for alg '" + header.getAlgorithm() + "'");
|
||||
}
|
||||
|
||||
JWEEncryptionProvider encryptionProvider = JWERegistry.getEncProvider(header.getEncryptionAlgorithm());
|
||||
if (encryptionProvider == null) {
|
||||
throw new IllegalArgumentException("No provider for enc '" + header.getAlgorithm() + "'");
|
||||
}
|
||||
|
||||
keyStorage.setEncryptionProvider(encryptionProvider);
|
||||
|
||||
byte[] decodedCek = algorithmProvider.decodeCek(Base64Url.decode(base64Cek), keyStorage.getEncryptionKey());
|
||||
keyStorage.setCEKBytes(decodedCek);
|
||||
|
||||
encryptionProvider.verifyAndDecodeJwe(this);
|
||||
|
||||
return this;
|
||||
} catch (IOException | GeneralSecurityException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
32
core/src/main/java/org/keycloak/jose/jwe/JWEConstants.java
Normal file
32
core/src/main/java/org/keycloak/jose/jwe/JWEConstants.java
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 JWEConstants {
|
||||
|
||||
public static final String DIR = "dir";
|
||||
public static final String A128KW = "A128KW";
|
||||
|
||||
public static final String A128CBC_HS256 = "A128CBC-HS256";
|
||||
public static final String A192CBC_HS384 = "A192CBC-HS384";
|
||||
public static final String A256CBC_HS512 = "A256CBC-HS512";
|
||||
|
||||
}
|
103
core/src/main/java/org/keycloak/jose/jwe/JWEHeader.java
Normal file
103
core/src/main/java/org/keycloak/jose/jwe/JWEHeader.java
Normal file
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class JWEHeader implements Serializable {
|
||||
|
||||
@JsonProperty("alg")
|
||||
private String algorithm;
|
||||
|
||||
@JsonProperty("enc")
|
||||
private String encryptionAlgorithm;
|
||||
|
||||
@JsonProperty("zip")
|
||||
private String compressionAlgorithm;
|
||||
|
||||
|
||||
@JsonProperty("typ")
|
||||
private String type;
|
||||
|
||||
@JsonProperty("cty")
|
||||
private String contentType;
|
||||
|
||||
@JsonProperty("kid")
|
||||
private String keyId;
|
||||
|
||||
public JWEHeader() {
|
||||
}
|
||||
|
||||
public JWEHeader(String algorithm, String encryptionAlgorithm, String compressionAlgorithm) {
|
||||
this.algorithm = algorithm;
|
||||
this.encryptionAlgorithm = encryptionAlgorithm;
|
||||
this.compressionAlgorithm = compressionAlgorithm;
|
||||
}
|
||||
|
||||
public String getAlgorithm() {
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
public String getEncryptionAlgorithm() {
|
||||
return encryptionAlgorithm;
|
||||
}
|
||||
|
||||
public String getCompressionAlgorithm() {
|
||||
return compressionAlgorithm;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getContentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
public String getKeyId() {
|
||||
return keyId;
|
||||
}
|
||||
|
||||
private static final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
static {
|
||||
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
try {
|
||||
return mapper.writeValueAsString(this);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
103
core/src/main/java/org/keycloak/jose/jwe/JWEKeyStorage.java
Normal file
103
core/src/main/java/org/keycloak/jose/jwe/JWEKeyStorage.java
Normal file
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import java.security.Key;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class JWEKeyStorage {
|
||||
|
||||
private Key encryptionKey;
|
||||
|
||||
private byte[] cekBytes;
|
||||
|
||||
private Map<KeyUse, Key> decodedCEK = new HashMap<>();
|
||||
|
||||
private JWEEncryptionProvider encryptionProvider;
|
||||
|
||||
|
||||
public Key getEncryptionKey() {
|
||||
return encryptionKey;
|
||||
}
|
||||
|
||||
public JWEKeyStorage setEncryptionKey(Key encryptionKey) {
|
||||
this.encryptionKey = encryptionKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public void setCEKBytes(byte[] cekBytes) {
|
||||
this.cekBytes = cekBytes;
|
||||
}
|
||||
|
||||
public byte[] getCekBytes() {
|
||||
if (cekBytes == null) {
|
||||
cekBytes = encryptionProvider.serializeCEK(this);
|
||||
}
|
||||
return cekBytes;
|
||||
}
|
||||
|
||||
public JWEKeyStorage setCEKKey(Key key, KeyUse keyUse) {
|
||||
decodedCEK.put(keyUse, key);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Key getCEKKey(KeyUse keyUse, boolean generateIfNotPresent) {
|
||||
Key key = decodedCEK.get(keyUse);
|
||||
if (key == null) {
|
||||
if (encryptionProvider != null) {
|
||||
|
||||
if (cekBytes == null && generateIfNotPresent) {
|
||||
generateCekBytes();
|
||||
}
|
||||
|
||||
if (cekBytes != null) {
|
||||
encryptionProvider.deserializeCEK(this);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException("encryptionProvider needs to be set");
|
||||
}
|
||||
}
|
||||
|
||||
return decodedCEK.get(keyUse);
|
||||
}
|
||||
|
||||
|
||||
private void generateCekBytes() {
|
||||
int cekLength = encryptionProvider.getExpectedCEKLength();
|
||||
cekBytes = JWEUtils.generateSecret(cekLength);
|
||||
}
|
||||
|
||||
|
||||
public void setEncryptionProvider(JWEEncryptionProvider encryptionProvider) {
|
||||
this.encryptionProvider = encryptionProvider;
|
||||
}
|
||||
|
||||
|
||||
public enum KeyUse {
|
||||
ENCRYPTION,
|
||||
SIGNATURE
|
||||
}
|
||||
}
|
66
core/src/main/java/org/keycloak/jose/jwe/JWERegistry.java
Normal file
66
core/src/main/java/org/keycloak/jose/jwe/JWERegistry.java
Normal file
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.jose.jwe.alg.AesKeyWrapAlgorithmProvider;
|
||||
import org.keycloak.jose.jwe.alg.DirectAlgorithmProvider;
|
||||
import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
|
||||
import org.keycloak.jose.jwe.enc.AesCbcHmacShaEncryptionProvider;
|
||||
import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
class JWERegistry {
|
||||
|
||||
// https://tools.ietf.org/html/rfc7518#page-12
|
||||
// Registry not pluggable for now. Just supported algorithms included
|
||||
private static final Map<String, JWEEncryptionProvider> ENC_PROVIDERS = new HashMap<>();
|
||||
|
||||
// https://tools.ietf.org/html/rfc7518#page-22
|
||||
// Registry not pluggable for now. Just supported algorithms included
|
||||
private static final Map<String, JWEAlgorithmProvider> ALG_PROVIDERS = new HashMap<>();
|
||||
|
||||
|
||||
static {
|
||||
// Provider 'dir' just directly uses encryption keys for encrypt/decrypt content.
|
||||
ALG_PROVIDERS.put(JWEConstants.DIR, new DirectAlgorithmProvider());
|
||||
ALG_PROVIDERS.put(JWEConstants.A128KW, new AesKeyWrapAlgorithmProvider());
|
||||
|
||||
|
||||
ENC_PROVIDERS.put(JWEConstants.A128CBC_HS256, new AesCbcHmacShaEncryptionProvider.Aes128CbcHmacSha256Provider());
|
||||
ENC_PROVIDERS.put(JWEConstants.A192CBC_HS384, new AesCbcHmacShaEncryptionProvider.Aes192CbcHmacSha384Provider());
|
||||
ENC_PROVIDERS.put(JWEConstants.A256CBC_HS512, new AesCbcHmacShaEncryptionProvider.Aes256CbcHmacSha512Provider());
|
||||
}
|
||||
|
||||
|
||||
static JWEAlgorithmProvider getAlgProvider(String alg) {
|
||||
return ALG_PROVIDERS.get(alg);
|
||||
}
|
||||
|
||||
|
||||
static JWEEncryptionProvider getEncProvider(String enc) {
|
||||
return ENC_PROVIDERS.get(enc);
|
||||
}
|
||||
|
||||
|
||||
}
|
35
core/src/main/java/org/keycloak/jose/jwe/JWEUtils.java
Normal file
35
core/src/main/java/org/keycloak/jose/jwe/JWEUtils.java
Normal file
|
@ -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;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class JWEUtils {
|
||||
|
||||
private JWEUtils() {
|
||||
}
|
||||
|
||||
public static byte[] generateSecret(int bytes) {
|
||||
byte[] buf = new byte[bytes];
|
||||
new SecureRandom().nextBytes(buf);
|
||||
return buf;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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.alg;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.Key;
|
||||
|
||||
import org.bouncycastle.crypto.InvalidCipherTextException;
|
||||
import org.bouncycastle.crypto.Wrapper;
|
||||
import org.bouncycastle.crypto.engines.AESWrapEngine;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
import org.keycloak.jose.jwe.JWEKeyStorage;
|
||||
import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key encryptionKey) throws IOException, GeneralSecurityException {
|
||||
Wrapper encrypter = new AESWrapEngine();
|
||||
encrypter.init(true, new KeyParameter(encryptionKey.getEncoded()));
|
||||
byte[] cekBytes = keyStorage.getCekBytes();
|
||||
return encrypter.wrap(cekBytes, 0, cekBytes.length);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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.alg;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.Key;
|
||||
|
||||
import org.keycloak.jose.jwe.JWEKeyStorage;
|
||||
import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class DirectAlgorithmProvider implements JWEAlgorithmProvider {
|
||||
|
||||
@Override
|
||||
public byte[] decodeCek(byte[] encodedCek, Key encryptionKey) throws IOException, GeneralSecurityException {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key encryptionKey) throws IOException, GeneralSecurityException {
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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.alg;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.Key;
|
||||
|
||||
import org.keycloak.jose.jwe.JWEKeyStorage;
|
||||
import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public interface JWEAlgorithmProvider {
|
||||
|
||||
byte[] decodeCek(byte[] encodedCek, Key encryptionKey) throws IOException, GeneralSecurityException;
|
||||
|
||||
byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key encryptionKey) throws IOException, GeneralSecurityException;
|
||||
|
||||
}
|
|
@ -0,0 +1,272 @@
|
|||
/*
|
||||
* 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.enc;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.Key;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.spec.AlgorithmParameterSpec;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.keycloak.jose.jwe.JWE;
|
||||
import org.keycloak.jose.jwe.JWEKeyStorage;
|
||||
import org.keycloak.jose.jwe.JWEUtils;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public abstract class AesCbcHmacShaEncryptionProvider implements JWEEncryptionProvider {
|
||||
|
||||
|
||||
@Override
|
||||
public void encodeJwe(JWE jwe) throws IOException, GeneralSecurityException {
|
||||
|
||||
byte[] contentBytes = jwe.getContent();
|
||||
|
||||
byte[] initializationVector = JWEUtils.generateSecret(16);
|
||||
|
||||
Key aesKey = jwe.getKeyStorage().getCEKKey(JWEKeyStorage.KeyUse.ENCRYPTION, false);
|
||||
if (aesKey == null) {
|
||||
throw new IllegalArgumentException("AES CEK key not present");
|
||||
}
|
||||
|
||||
Key hmacShaKey = jwe.getKeyStorage().getCEKKey(JWEKeyStorage.KeyUse.SIGNATURE, false);
|
||||
if (hmacShaKey == null) {
|
||||
throw new IllegalArgumentException("HMAC CEK key not present");
|
||||
}
|
||||
|
||||
int expectedAesKeyLength = getExpectedAesKeyLength();
|
||||
if (expectedAesKeyLength != aesKey.getEncoded().length) {
|
||||
throw new IllegalStateException("Length of aes key should be " + expectedAesKeyLength +", but was " + aesKey.getEncoded().length);
|
||||
}
|
||||
|
||||
byte[] cipherBytes = encryptBytes(contentBytes, initializationVector, aesKey);
|
||||
|
||||
byte[] aad = jwe.getBase64Header().getBytes("UTF-8");
|
||||
byte[] authenticationTag = computeAuthenticationTag(aad, initializationVector, cipherBytes, hmacShaKey);
|
||||
|
||||
jwe.setEncryptedContentInfo(initializationVector, cipherBytes, authenticationTag);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void verifyAndDecodeJwe(JWE jwe) throws IOException, GeneralSecurityException {
|
||||
Key aesKey = jwe.getKeyStorage().getCEKKey(JWEKeyStorage.KeyUse.ENCRYPTION, false);
|
||||
if (aesKey == null) {
|
||||
throw new IllegalArgumentException("AES CEK key not present");
|
||||
}
|
||||
|
||||
Key hmacShaKey = jwe.getKeyStorage().getCEKKey(JWEKeyStorage.KeyUse.SIGNATURE, false);
|
||||
if (hmacShaKey == null) {
|
||||
throw new IllegalArgumentException("HMAC CEK key not present");
|
||||
}
|
||||
|
||||
int expectedAesKeyLength = getExpectedAesKeyLength();
|
||||
if (expectedAesKeyLength != aesKey.getEncoded().length) {
|
||||
throw new IllegalStateException("Length of aes key should be " + expectedAesKeyLength +", but was " + aesKey.getEncoded().length);
|
||||
}
|
||||
|
||||
byte[] aad = jwe.getBase64Header().getBytes("UTF-8");
|
||||
byte[] authenticationTag = computeAuthenticationTag(aad, jwe.getInitializationVector(), jwe.getEncryptedContent(), hmacShaKey);
|
||||
|
||||
byte[] expectedAuthTag = jwe.getAuthenticationTag();
|
||||
boolean digitsEqual = MessageDigest.isEqual(expectedAuthTag, authenticationTag);
|
||||
|
||||
if (!digitsEqual) {
|
||||
throw new IllegalArgumentException("Signature validations failed");
|
||||
}
|
||||
|
||||
byte[] contentBytes = decryptBytes(jwe.getEncryptedContent(), jwe.getInitializationVector(), aesKey);
|
||||
|
||||
jwe.content(contentBytes);
|
||||
}
|
||||
|
||||
|
||||
protected abstract int getExpectedAesKeyLength();
|
||||
|
||||
protected abstract String getHmacShaAlgorithm();
|
||||
|
||||
protected abstract int getAuthenticationTagLength();
|
||||
|
||||
|
||||
private byte[] encryptBytes(byte[] contentBytes, byte[] ivBytes, Key aesKey) throws GeneralSecurityException {
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
|
||||
AlgorithmParameterSpec ivParamSpec = new IvParameterSpec(ivBytes);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, aesKey, ivParamSpec);
|
||||
return cipher.doFinal(contentBytes);
|
||||
}
|
||||
|
||||
|
||||
private byte[] decryptBytes(byte[] encryptedBytes, byte[] ivBytes, Key aesKey) throws GeneralSecurityException {
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
|
||||
AlgorithmParameterSpec ivParamSpec = new IvParameterSpec(ivBytes);
|
||||
cipher.init(Cipher.DECRYPT_MODE, aesKey, ivParamSpec);
|
||||
return cipher.doFinal(encryptedBytes);
|
||||
}
|
||||
|
||||
|
||||
private byte[] computeAuthenticationTag(byte[] aadBytes, byte[] ivBytes, byte[] cipherBytes, Key hmacKeySpec) throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
// Compute "al"
|
||||
ByteBuffer b = ByteBuffer.allocate(4);
|
||||
b.order(ByteOrder.BIG_ENDIAN); // optional, the initial order of a byte buffer is always BIG_ENDIAN.
|
||||
int aadLengthInBits = aadBytes.length * 8;
|
||||
b.putInt(aadLengthInBits);
|
||||
byte[] result1 = b.array();
|
||||
byte[] al = new byte[8];
|
||||
System.arraycopy(result1, 0, al, 4, 4);
|
||||
|
||||
byte[] concatenatedHmacInput = new byte[aadBytes.length + ivBytes.length + cipherBytes.length + al.length];
|
||||
System.arraycopy(aadBytes, 0, concatenatedHmacInput, 0, aadBytes.length);
|
||||
System.arraycopy(ivBytes, 0, concatenatedHmacInput, aadBytes.length, ivBytes.length );
|
||||
System.arraycopy(cipherBytes, 0, concatenatedHmacInput, aadBytes.length + ivBytes.length , cipherBytes.length);
|
||||
System.arraycopy(al, 0, concatenatedHmacInput, aadBytes.length + ivBytes.length + cipherBytes.length, al.length);
|
||||
|
||||
String hmacShaAlg = getHmacShaAlgorithm();
|
||||
Mac macImpl = Mac.getInstance(hmacShaAlg);
|
||||
macImpl.init(hmacKeySpec);
|
||||
macImpl.update(concatenatedHmacInput);
|
||||
byte[] macEncoded = macImpl.doFinal();
|
||||
|
||||
int authTagLength = getAuthenticationTagLength();
|
||||
return Arrays.copyOf(macEncoded, authTagLength);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void deserializeCEK(JWEKeyStorage keyStorage) {
|
||||
byte[] cekBytes = keyStorage.getCekBytes();
|
||||
|
||||
int cekLength = getExpectedCEKLength();
|
||||
byte[] cekMacKey = Arrays.copyOf(cekBytes, cekLength / 2);
|
||||
byte[] cekAesKey = Arrays.copyOfRange(cekBytes, cekLength / 2, cekLength);
|
||||
|
||||
SecretKeySpec aesKey = new SecretKeySpec(cekAesKey, "AES");
|
||||
SecretKeySpec hmacKey = new SecretKeySpec(cekMacKey, "HMACSHA2");
|
||||
|
||||
keyStorage.setCEKKey(aesKey, JWEKeyStorage.KeyUse.ENCRYPTION);
|
||||
keyStorage.setCEKKey(hmacKey, JWEKeyStorage.KeyUse.SIGNATURE);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public byte[] serializeCEK(JWEKeyStorage keyStorage) {
|
||||
Key aesKey = keyStorage.getCEKKey(JWEKeyStorage.KeyUse.ENCRYPTION, false);
|
||||
if (aesKey == null) {
|
||||
throw new IllegalArgumentException("AES CEK key not present");
|
||||
}
|
||||
|
||||
Key hmacShaKey = keyStorage.getCEKKey(JWEKeyStorage.KeyUse.SIGNATURE, false);
|
||||
if (hmacShaKey == null) {
|
||||
throw new IllegalArgumentException("HMAC CEK key not present");
|
||||
}
|
||||
|
||||
byte[] hmacBytes = hmacShaKey.getEncoded();
|
||||
byte[] aesBytes = aesKey.getEncoded();
|
||||
|
||||
byte[] result = new byte[hmacBytes.length + aesBytes.length];
|
||||
System.arraycopy(hmacBytes, 0, result, 0, hmacBytes.length);
|
||||
System.arraycopy(aesBytes, 0, result, hmacBytes.length, aesBytes.length);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static class Aes128CbcHmacSha256Provider extends AesCbcHmacShaEncryptionProvider {
|
||||
|
||||
@Override
|
||||
protected int getExpectedAesKeyLength() {
|
||||
return 16;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getHmacShaAlgorithm() {
|
||||
return "HMACSHA256";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getAuthenticationTagLength() {
|
||||
return 16;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getExpectedCEKLength() {
|
||||
return 32;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class Aes192CbcHmacSha384Provider extends AesCbcHmacShaEncryptionProvider {
|
||||
|
||||
@Override
|
||||
protected int getExpectedAesKeyLength() {
|
||||
return 24;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getHmacShaAlgorithm() {
|
||||
return "HMACSHA384";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getAuthenticationTagLength() {
|
||||
return 24;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getExpectedCEKLength() {
|
||||
return 48;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class Aes256CbcHmacSha512Provider extends AesCbcHmacShaEncryptionProvider {
|
||||
|
||||
@Override
|
||||
protected int getExpectedAesKeyLength() {
|
||||
return 32;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getHmacShaAlgorithm() {
|
||||
return "HMACSHA512";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getAuthenticationTagLength() {
|
||||
return 32;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getExpectedCEKLength() {
|
||||
return 64;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* 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.enc;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.Key;
|
||||
|
||||
import org.keycloak.jose.jwe.JWE;
|
||||
import org.keycloak.jose.jwe.JWEKeyStorage;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public interface JWEEncryptionProvider {
|
||||
|
||||
/**
|
||||
* This method usually has 3 outputs:
|
||||
* - generated initialization vector
|
||||
* - encrypted content
|
||||
* - authenticationTag for MAC validation
|
||||
*
|
||||
* It is supposed to call {@link JWE#setEncryptedContentInfo(byte[], byte[], byte[])} after it's finished
|
||||
*
|
||||
* @param jwe
|
||||
* @throws IOException
|
||||
* @throws GeneralSecurityException
|
||||
*/
|
||||
void encodeJwe(JWE jwe) throws IOException, GeneralSecurityException;
|
||||
|
||||
|
||||
/**
|
||||
* This method is supposed to verify checksums and decrypt content. Then it needs to call {@link JWE#content(byte[])} after it's finished
|
||||
*
|
||||
* @param jwe
|
||||
* @throws IOException
|
||||
* @throws GeneralSecurityException
|
||||
*/
|
||||
void verifyAndDecodeJwe(JWE jwe) throws IOException, GeneralSecurityException;
|
||||
|
||||
|
||||
/**
|
||||
* This method requires that decoded CEK keys are present in the keyStorage.decodedCEK map before it's called
|
||||
*
|
||||
* @param keyStorage
|
||||
* @return
|
||||
*/
|
||||
byte[] serializeCEK(JWEKeyStorage keyStorage);
|
||||
|
||||
/**
|
||||
* This method is supposed to deserialize keys. It requires that {@link JWEKeyStorage#getCekBytes()} is set. After keys are deserialized,
|
||||
* this method needs to call {@link JWEKeyStorage#setCEKKey(Key, JWEKeyStorage.KeyUse)} according to all uses, which this encryption algorithm requires.
|
||||
*
|
||||
* @param keyStorage
|
||||
*/
|
||||
void deserializeCEK(JWEKeyStorage keyStorage);
|
||||
|
||||
int getExpectedCEKLength();
|
||||
|
||||
}
|
203
core/src/test/java/org/keycloak/jose/JWETest.java
Normal file
203
core/src/test/java/org/keycloak/jose/JWETest.java
Normal file
|
@ -0,0 +1,203 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.Key;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.junit.Assert;
|
||||
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.JWEHeader;
|
||||
import org.keycloak.jose.jwe.JWEKeyStorage;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
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 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 };
|
||||
|
||||
private static final byte[] HMAC_SHA512_KEY = new byte[] { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 13, 14, 15, 16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
|
||||
private static final byte[] AES_256_KEY = new byte[] { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 13, 14, 15, 16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
|
||||
|
||||
@Test
|
||||
public void testDirect_Aes128CbcHmacSha256() throws Exception {
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@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);
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAesKW_Aes128CbcHmacSha256() throws Exception {
|
||||
SecretKey aesKey = new SecretKeySpec(AES_128_KEY, "AES");
|
||||
|
||||
JWEHeader jweHeader = new JWEHeader(JWEConstants.A128KW, JWEConstants.A128CBC_HS256, null);
|
||||
JWE jwe = new JWE()
|
||||
.header(jweHeader)
|
||||
.content(PAYLOAD.getBytes("UTF-8"));
|
||||
|
||||
jwe.getKeyStorage()
|
||||
.setEncryptionKey(aesKey);
|
||||
|
||||
String encodedContent = jwe.encodeJwe();
|
||||
|
||||
System.out.println("Encoded content: " + encodedContent);
|
||||
System.out.println("Encoded content length: " + encodedContent.length());
|
||||
|
||||
jwe = new JWE();
|
||||
jwe.getKeyStorage()
|
||||
.setEncryptionKey(aesKey);
|
||||
|
||||
jwe.verifyAndDecodeJwe(encodedContent);
|
||||
|
||||
String decodedContent = new String(jwe.getContent(), "UTF-8");
|
||||
|
||||
Assert.assertEquals(PAYLOAD, decodedContent);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void externalJweAes128CbcHmacSha256Test() throws UnsupportedEncodingException {
|
||||
String externalJwe = "eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiZGlyIn0..qysUrI1iVtiG4Z4jyr7XXg.apdNSQhR7WDMg6IHf5aLVI0gGp6JuOHYmIUtflns4WHmyxOOnh_GShLI6DWaK_SiywTV5gZvZYtl8H8Iv5fTfLkc4tiDDjbdtmsOP7tqyRxVh069gU5UvEAgmCXbIKALutgYXcYe2WM4E6BIHPTSt8jXdkktFcm7XHiD7mpakZyjXsG8p3XVkQJ72WbJI_t6.Ks6gHeko7BRTZ4CFs5ijRA";
|
||||
System.out.println("External encoded content length: " + externalJwe.length());
|
||||
|
||||
final SecretKey aesKey = new SecretKeySpec(AES_128_KEY, "AES");
|
||||
final SecretKey hmacKey = new SecretKeySpec(HMAC_SHA256_KEY, "HMACSHA2");
|
||||
|
||||
JWE jwe = new JWE();
|
||||
jwe.getKeyStorage()
|
||||
.setCEKKey(aesKey, JWEKeyStorage.KeyUse.ENCRYPTION)
|
||||
.setCEKKey(hmacKey, JWEKeyStorage.KeyUse.SIGNATURE);
|
||||
|
||||
jwe.verifyAndDecodeJwe(externalJwe);
|
||||
|
||||
String decodedContent = new String(jwe.getContent(), "UTF-8");
|
||||
|
||||
Assert.assertEquals(PAYLOAD, decodedContent);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void externalJweAes256CbcHmacSha512Test() throws UnsupportedEncodingException {
|
||||
String externalJwe = "eyJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwiYWxnIjoiZGlyIn0..xUPndQ5U69CYaWMKr4nyeg.AzSzba6OdNsvTIoNpub8d2TmYnkY7W8Sd-1S33DjJwJsSaNcfvfXBq5bqXAGVAnLHrLZJKWoEYsmOrYHz3Nao-kpLtUpc4XZI8yiYUqkHTjmxZnfD02R6hz31a5KBCnDTtUEv23VSxm8yUyQKoUTpVHbJ3b2VQvycg2XFUXPsA6oaSSEpz-uwe1Vmun2hUBB.Qal4rMYn1RrXQ9AQ9ONUjUXvlS2ow8np-T8QWMBR0ns";
|
||||
System.out.println("External encoded content length: " + externalJwe.length());
|
||||
|
||||
final SecretKey aesKey = new SecretKeySpec(AES_256_KEY, "AES");
|
||||
final SecretKey hmacKey = new SecretKeySpec(HMAC_SHA512_KEY, "HMACSHA2");
|
||||
|
||||
JWE jwe = new JWE();
|
||||
jwe.getKeyStorage()
|
||||
.setCEKKey(aesKey, JWEKeyStorage.KeyUse.ENCRYPTION)
|
||||
.setCEKKey(hmacKey, JWEKeyStorage.KeyUse.SIGNATURE);
|
||||
|
||||
jwe.verifyAndDecodeJwe(externalJwe);
|
||||
|
||||
String decodedContent = new String(jwe.getContent(), "UTF-8");
|
||||
|
||||
Assert.assertEquals(PAYLOAD, decodedContent);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void externalJweAesKeyWrapTest() 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";
|
||||
|
||||
byte[] aesKey = Base64Url.decode("GawgguFyGrWKav7AX4VKUg");
|
||||
SecretKeySpec aesKeySpec = new SecretKeySpec(aesKey, "AES");
|
||||
|
||||
JWE jwe = new JWE();
|
||||
jwe.getKeyStorage()
|
||||
.setEncryptionKey(aesKeySpec);
|
||||
|
||||
jwe.verifyAndDecodeJwe(externalJwe);
|
||||
|
||||
String decodedContent = new String(jwe.getContent(), "UTF-8");
|
||||
|
||||
Assert.assertEquals("Live long and prosper.", decodedContent);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -48,16 +48,16 @@ public class KeycloakRemoteStoreConfiguration extends RemoteStoreConfiguration {
|
|||
|
||||
|
||||
public String useConfigTemplateFromCache() {
|
||||
return useConfigTemplateFromCache==null ? null : useConfigTemplateFromCache.get();
|
||||
return useConfigTemplateFromCache.get();
|
||||
}
|
||||
|
||||
|
||||
public String remoteServers() {
|
||||
return remoteServers==null ? null : remoteServers.get();
|
||||
return remoteServers.get();
|
||||
}
|
||||
|
||||
|
||||
public Boolean sessionCache() {
|
||||
return sessionCache==null ? false : sessionCache.get();
|
||||
return sessionCache.get()==null ? false : sessionCache.get();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue