/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.apache.cxf.rs.security.jose.jwe; import java.nio.ByteBuffer; import java.security.spec.AlgorithmParameterSpec; import java.util.HashMap; import java.util.Map; import javax.crypto.Mac; import javax.crypto.spec.IvParameterSpec; import org.apache.cxf.rs.security.jose.jwa.AlgorithmUtils; import org.apache.cxf.rs.security.jose.jwa.ContentAlgorithm; import org.apache.cxf.rt.security.crypto.HmacUtils; public class AesCbcHmacJweEncryption extends JweEncryption { private static final Map<String, String> AES_HMAC_MAP; private static final Map<String, Integer> AES_CEK_SIZE_MAP; static { AES_HMAC_MAP = new HashMap<>(); AES_HMAC_MAP.put(ContentAlgorithm.A128CBC_HS256.getJwaName(), AlgorithmUtils.HMAC_SHA_256_JAVA); AES_HMAC_MAP.put(ContentAlgorithm.A192CBC_HS384.getJwaName(), AlgorithmUtils.HMAC_SHA_384_JAVA); AES_HMAC_MAP.put(ContentAlgorithm.A256CBC_HS512.getJwaName(), AlgorithmUtils.HMAC_SHA_512_JAVA); AES_CEK_SIZE_MAP = new HashMap<>(); AES_CEK_SIZE_MAP.put(ContentAlgorithm.A128CBC_HS256.getJwaName(), 32); AES_CEK_SIZE_MAP.put(ContentAlgorithm.A192CBC_HS384.getJwaName(), 48); AES_CEK_SIZE_MAP.put(ContentAlgorithm.A256CBC_HS512.getJwaName(), 64); } public AesCbcHmacJweEncryption(ContentAlgorithm cekAlgoJwt, KeyEncryptionProvider keyEncryptionAlgorithm) { this(cekAlgoJwt, keyEncryptionAlgorithm, false); } public AesCbcHmacJweEncryption(ContentAlgorithm cekAlgoJwt, KeyEncryptionProvider keyEncryptionAlgorithm, boolean generateCekOnce) { super(keyEncryptionAlgorithm, new AesCbcContentEncryptionAlgorithm(validateCekAlgorithm(cekAlgoJwt), generateCekOnce)); } public AesCbcHmacJweEncryption(ContentAlgorithm cekAlgoJwt, byte[] cek, byte[] iv, KeyEncryptionProvider keyEncryptionAlgorithm) { super(keyEncryptionAlgorithm, new AesCbcContentEncryptionAlgorithm(cek, iv, validateCekAlgorithm(cekAlgoJwt))); } @Override protected byte[] getActualCek(byte[] theCek, String algoJwt) { return doGetActualCek(theCek, algoJwt); } protected static byte[] doGetActualCek(byte[] theCek, String algoJwt) { int size = getFullCekKeySize(algoJwt) / 2; byte[] actualCek = new byte[size]; System.arraycopy(theCek, size, actualCek, 0, size); return actualCek; } protected static int getFullCekKeySize(String algoJwt) { return AES_CEK_SIZE_MAP.get(algoJwt); } protected byte[] getActualCipher(byte[] cipher) { return cipher; } protected byte[] getAuthenticationTag(JweEncryptionInternal state, byte[] cipher) { final MacState macState = getInitializedMacState(state); macState.mac.update(cipher); return signAndGetTag(macState); } protected static byte[] signAndGetTag(MacState macState) { macState.mac.update(macState.al); byte[] sig = macState.mac.doFinal(); int authTagLen = DEFAULT_AUTH_TAG_LENGTH / 8; byte[] authTag = new byte[authTagLen]; System.arraycopy(sig, 0, authTag, 0, authTagLen); return authTag; } private MacState getInitializedMacState(final JweEncryptionInternal state) { return getInitializedMacState(state.secretKey, state.theIv, state.aad, state.theHeaders, state.protectedHeadersJson); } protected static MacState getInitializedMacState(byte[] secretKey, byte[] theIv, byte[] extraAad, JweHeaders theHeaders, String protectedHeadersJson) { String algoJwt = theHeaders.getContentEncryptionAlgorithm().getJwaName(); int size = getFullCekKeySize(algoJwt) / 2; byte[] macKey = new byte[size]; System.arraycopy(secretKey, 0, macKey, 0, size); String hmacAlgoJava = AES_HMAC_MAP.get(algoJwt); Mac mac = HmacUtils.getInitializedMac(macKey, hmacAlgoJava, null); byte[] aad = JweUtils.getAdditionalAuthenticationData(protectedHeadersJson, extraAad); ByteBuffer buf = ByteBuffer.allocate(8); final byte[] al = buf.putInt(0).putInt(aad.length * 8).array(); mac.update(aad); mac.update(theIv); MacState macState = new MacState(); macState.mac = mac; macState.al = al; return macState; } protected AuthenticationTagProducer getAuthenticationTagProducer(final JweEncryptionInternal state) { final MacState macState = getInitializedMacState(state); return new AuthenticationTagProducer() { @Override public void update(byte[] cipher, int off, int len) { macState.mac.update(cipher, off, len); } @Override public byte[] getTag() { return signAndGetTag(macState); } }; } @Override protected byte[] getEncryptedContentEncryptionKey(JweHeaders headers, byte[] theCek) { return getKeyEncryptionAlgo().getEncryptedContentEncryptionKey(headers, theCek); } private static class AesCbcContentEncryptionAlgorithm extends AbstractContentEncryptionAlgorithm { AesCbcContentEncryptionAlgorithm(ContentAlgorithm algo, boolean generateCekOnce) { super(algo, generateCekOnce); } AesCbcContentEncryptionAlgorithm(byte[] cek, byte[] iv, ContentAlgorithm algo) { super(cek, iv, algo); } @Override public AlgorithmParameterSpec getAlgorithmParameterSpec(byte[] theIv) { return new IvParameterSpec(theIv); } @Override public byte[] getAdditionalAuthenticationData(String headersJson, byte[] aad) { return null; } @Override protected int getContentEncryptionKeySize(JweHeaders headers) { return getFullCekKeySize(getAlgorithm().getJwaName()) * 8; } } protected static class MacState { protected Mac mac; private byte[] al; } private static ContentAlgorithm validateCekAlgorithm(ContentAlgorithm cekAlgo) { if (!AlgorithmUtils.isAesCbcHmac(cekAlgo.getJwaName())) { LOG.warning("Invalid content encryption algorithm"); throw new JweException(JweException.Error.INVALID_CONTENT_ALGORITHM); } return cekAlgo; } }