org.keycloak.jose.jwe.enc.AesCbcHmacShaEncryptionProvider Maven / Gradle / Ivy
The newest version!
/*
* 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.nio.charset.StandardCharsets;
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.common.crypto.CryptoIntegration;
import org.keycloak.jose.jwe.JWE;
import org.keycloak.jose.jwe.JWEKeyStorage;
import org.keycloak.jose.jwe.JWEUtils;
/**
* @author Marek Posolda
*/
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(StandardCharsets.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(StandardCharsets.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 = CryptoIntegration.getProvider().getAesCbcCipher();
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 = CryptoIntegration.getProvider().getAesCbcCipher();
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;
}
}
}