/**
* 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.charset.StandardCharsets;
import java.security.Security;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import org.apache.cxf.common.util.Base64UrlUtility;
import org.apache.cxf.rs.security.jose.jwa.AlgorithmUtils;
import org.apache.cxf.rs.security.jose.jwa.ContentAlgorithm;
import org.apache.cxf.rs.security.jose.jwa.KeyAlgorithm;
import org.apache.cxf.rs.security.jose.jwk.JsonWebKey;
import org.apache.cxf.rs.security.jose.jws.JwsCompactReaderWriterTest;
import org.apache.cxf.rt.security.crypto.CryptoUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
public class JweCompactReaderWriterTest extends Assert {
// A1 example
static final byte[] CONTENT_ENCRYPTION_KEY_A1 = {
(byte)177, (byte)161, (byte)244, (byte)128, 84, (byte)143, (byte)225,
115, 63, (byte)180, 3, (byte)255, 107, (byte)154, (byte)212, (byte)246,
(byte)138, 7, 110, 91, 112, 46, 34, 105, 47,
(byte)130, (byte)203, 46, 122, (byte)234, 64, (byte)252};
static final String RSA_MODULUS_ENCODED_A1 = "oahUIoWw0K0usKNuOR6H4wkf4oBUXHTxRvgb48E-BVvxkeDNjbC4he8rUW"
+ "cJoZmds2h7M70imEVhRU5djINXtqllXI4DFqcI1DgjT9LewND8MW2Krf3S"
+ "psk_ZkoFnilakGygTwpZ3uesH-PFABNIUYpOiN15dsQRkgr0vEhxN92i2a"
+ "sbOenSZeyaxziK72UwxrrKoExv6kc5twXTq4h-QChLOln0_mtUZwfsRaMS"
+ "tPs6mS6XrgxnxbWhojf663tuEQueGC-FCMfra36C9knDFGzKsNa7LZK2dj"
+ "YgyD3JR_MB_4NUJW_TqOQtwHYbxevoJArm-L5StowjzGy-_bq6Gw";
static final String RSA_PUBLIC_EXPONENT_ENCODED_A1 = "AQAB";
static final String RSA_PRIVATE_EXPONENT_ENCODED_A1 =
"kLdtIj6GbDks_ApCSTYQtelcNttlKiOyPzMrXHeI-yk1F7-kpDxY4-WY5N"
+ "WV5KntaEeXS1j82E375xxhWMHXyvjYecPT9fpwR_M9gV8n9Hrh2anTpTD9"
+ "3Dt62ypW3yDsJzBnTnrYu1iwWRgBKrEYY46qAZIrA2xAwnm2X7uGR1hghk"
+ "qDp0Vqj3kbSCz1XyfCs6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vl"
+ "t3UVe4WO3JkJOzlpUf-KTVI2Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSnd"
+ "VTIznSxfyrj8ILL6MG_Uv8YAu7VILSB3lOW085-4qE3DzgrTjgyQ";
static final byte[] INIT_VECTOR_A1 = {(byte)227, (byte)197, 117, (byte)252, 2, (byte)219,
(byte)233, 68, (byte)180, (byte)225, 77, (byte)219};
// A3 example
static final byte[] CONTENT_ENCRYPTION_KEY_A3 = {
4, (byte)211, 31, (byte)197, 84, (byte)157, (byte)252, (byte)254, 11, 100,
(byte)157, (byte)250, 63, (byte)170, 106, (byte)206, 107, 124, (byte)212,
45, 111, 107, 9, (byte)219, (byte)200, (byte)177, 0, (byte)240, (byte)143,
(byte)156, 44, (byte)207};
static final byte[] INIT_VECTOR_A3 = {
3, 22, 60, 12, 43, 67, 104, 105, 108, 108, 105, 99, 111, 116, 104, 101};
static final String KEY_ENCRYPTION_KEY_A3 = "GawgguFyGrWKav7AX4VKUg";
private static final String JWE_OUTPUT_A3 =
"eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0"
+ ".6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ"
+ ".AxY8DCtDaGlsbGljb3RoZQ"
+ ".KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY"
+ ".U0m_YmjN04DJvceFICbCVQ";
@BeforeClass
public static void registerBouncyCastleIfNeeded() throws Exception {
try {
Cipher.getInstance(AlgorithmUtils.AES_GCM_ALGO_JAVA);
Cipher.getInstance(AlgorithmUtils.AES_CBC_ALGO_JAVA);
} catch (Throwable t) {
Security.addProvider(new BouncyCastleProvider());
}
}
@AfterClass
public static void unregisterBouncyCastleIfNeeded() throws Exception {
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
}
@Test
public void testEncryptDecryptAesWrapA128CBCHS256() throws Exception {
final String specPlainText = "Live long and prosper.";
byte[] cekEncryptionKey = Base64UrlUtility.decode(KEY_ENCRYPTION_KEY_A3);
AesWrapKeyEncryptionAlgorithm keyEncryption =
new AesWrapKeyEncryptionAlgorithm(cekEncryptionKey, KeyAlgorithm.A128KW);
JweEncryptionProvider encryption = new AesCbcHmacJweEncryption(ContentAlgorithm.A128CBC_HS256,
CONTENT_ENCRYPTION_KEY_A3,
INIT_VECTOR_A3,
keyEncryption);
String jweContent = encryption.encrypt(specPlainText.getBytes(StandardCharsets.UTF_8), null);
assertEquals(JWE_OUTPUT_A3, jweContent);
AesWrapKeyDecryptionAlgorithm keyDecryption = new AesWrapKeyDecryptionAlgorithm(cekEncryptionKey);
JweDecryptionProvider decryption = new AesCbcHmacJweDecryption(keyDecryption);
String decryptedText = decryption.decrypt(jweContent).getContentText();
assertEquals(specPlainText, decryptedText);
}
@Test
public void testECDHESDirectKeyEncryption() throws Exception {
ECPrivateKey bobPrivateKey =
CryptoUtils.getECPrivateKey(JsonWebKey.EC_CURVE_P256,
"VEmDZpDXXK8p8N0Cndsxs924q6nS1RXFASRl6BfUqdw");
final ECPublicKey bobPublicKey =
CryptoUtils.getECPublicKey(JsonWebKey.EC_CURVE_P256,
"weNJy2HscCSM6AEDTDg04biOvhFhyyWvOHQfeF_PxMQ",
"e8lnCO-AlStT-NJVX-crhB7QRYhiix03illJOVAOyck");
JweEncryptionProvider jweOut =
new EcdhDirectKeyJweEncryption(bobPublicKey,
JsonWebKey.EC_CURVE_P256,
"Alice",
"Bob",
ContentAlgorithm.A128GCM);
String jweOutput = jweOut.encrypt("Hello".getBytes(), null);
JweDecryptionProvider jweIn =
new EcdhDirectKeyJweDecryption(bobPrivateKey, ContentAlgorithm.A128GCM);
assertEquals("Hello", jweIn.decrypt(jweOutput).getContentText());
}
@Test
public void testEncryptDecryptRSA15WrapA128CBCHS256() throws Exception {
final String specPlainText = "Live long and prosper.";
RSAPublicKey publicKey = CryptoUtils.getRSAPublicKey(RSA_MODULUS_ENCODED_A1,
RSA_PUBLIC_EXPONENT_ENCODED_A1);
KeyEncryptionProvider keyEncryption = new RSAKeyEncryptionAlgorithm(publicKey,
KeyAlgorithm.RSA1_5);
JweEncryptionProvider encryption = new AesCbcHmacJweEncryption(ContentAlgorithm.A128CBC_HS256,
CONTENT_ENCRYPTION_KEY_A3,
INIT_VECTOR_A3,
keyEncryption);
String jweContent = encryption.encrypt(specPlainText.getBytes(StandardCharsets.UTF_8), null);
RSAPrivateKey privateKey = CryptoUtils.getRSAPrivateKey(RSA_MODULUS_ENCODED_A1,
RSA_PRIVATE_EXPONENT_ENCODED_A1);
KeyDecryptionProvider keyDecryption = new RSAKeyDecryptionAlgorithm(privateKey,
KeyAlgorithm.RSA1_5);
JweDecryptionProvider decryption = new AesCbcHmacJweDecryption(keyDecryption);
String decryptedText = decryption.decrypt(jweContent).getContentText();
assertEquals(specPlainText, decryptedText);
}
@Test
public void testEncryptDecryptAesGcmWrapA128CBCHS256() throws Exception {
//
// This test fails with the IBM JDK
//
if ("IBM Corporation".equals(System.getProperty("java.vendor"))) {
return;
}
final String specPlainText = "Live long and prosper.";
byte[] cekEncryptionKey = Base64UrlUtility.decode(KEY_ENCRYPTION_KEY_A3);
AesGcmWrapKeyEncryptionAlgorithm keyEncryption =
new AesGcmWrapKeyEncryptionAlgorithm(cekEncryptionKey, KeyAlgorithm.A128GCMKW);
JweEncryptionProvider encryption = new AesCbcHmacJweEncryption(ContentAlgorithm.A128CBC_HS256,
CONTENT_ENCRYPTION_KEY_A3,
INIT_VECTOR_A3,
keyEncryption);
String jweContent = encryption.encrypt(specPlainText.getBytes(StandardCharsets.UTF_8), null);
AesGcmWrapKeyDecryptionAlgorithm keyDecryption = new AesGcmWrapKeyDecryptionAlgorithm(cekEncryptionKey);
JweDecryptionProvider decryption = new AesCbcHmacJweDecryption(keyDecryption);
String decryptedText = decryption.decrypt(jweContent).getContentText();
assertEquals(specPlainText, decryptedText);
}
@Test
public void testEncryptDecryptSpecExample() throws Exception {
final String specPlainText = "The true sign of intelligence is not knowledge but imagination.";
String jweContent = encryptContent(specPlainText, true);
decrypt(jweContent, specPlainText, true);
}
@Test
public void testDirectKeyEncryptDecrypt() throws Exception {
final String specPlainText = "The true sign of intelligence is not knowledge but imagination.";
SecretKey key = createSecretKey(true);
String jweContent = encryptContentDirect(key, specPlainText);
decryptDirect(key, jweContent, specPlainText);
}
@Test
public void testEncryptDecryptJwsToken() throws Exception {
String jweContent = encryptContent(JwsCompactReaderWriterTest.ENCODED_TOKEN_SIGNED_BY_MAC, false);
decrypt(jweContent, JwsCompactReaderWriterTest.ENCODED_TOKEN_SIGNED_BY_MAC, false);
}
private String encryptContent(String content, boolean createIfException) throws Exception {
RSAPublicKey publicKey = CryptoUtils.getRSAPublicKey(RSA_MODULUS_ENCODED_A1,
RSA_PUBLIC_EXPONENT_ENCODED_A1);
SecretKey key = createSecretKey(createIfException);
String jwtKeyName = null;
if (key == null) {
// the encryptor will generate it
jwtKeyName = ContentAlgorithm.A128GCM.getJwaName();
} else {
jwtKeyName = AlgorithmUtils.toJwaName(key.getAlgorithm(), key.getEncoded().length * 8);
}
KeyEncryptionProvider keyEncryptionAlgo = new RSAKeyEncryptionAlgorithm(publicKey,
KeyAlgorithm.RSA_OAEP);
ContentEncryptionProvider contentEncryptionAlgo =
new AesGcmContentEncryptionAlgorithm(key == null ? null : key.getEncoded(), INIT_VECTOR_A1,
ContentAlgorithm.getAlgorithm(jwtKeyName));
JweEncryptionProvider encryptor = new JweEncryption(keyEncryptionAlgo, contentEncryptionAlgo);
return encryptor.encrypt(content.getBytes(StandardCharsets.UTF_8), null);
}
private String encryptContentDirect(SecretKey key, String content) throws Exception {
JweEncryption encryptor = new JweEncryption(new DirectKeyEncryptionAlgorithm(),
new AesGcmContentEncryptionAlgorithm(key, INIT_VECTOR_A1, ContentAlgorithm.A128GCM));
return encryptor.encrypt(content.getBytes(StandardCharsets.UTF_8), null);
}
private void decrypt(String jweContent, String plainContent, boolean unwrap) throws Exception {
RSAPrivateKey privateKey = CryptoUtils.getRSAPrivateKey(RSA_MODULUS_ENCODED_A1,
RSA_PRIVATE_EXPONENT_ENCODED_A1);
ContentAlgorithm algo = Cipher.getMaxAllowedKeyLength("AES") > 128
? ContentAlgorithm.A256GCM : ContentAlgorithm.A128GCM;
JweDecryptionProvider decryptor = new JweDecryption(new RSAKeyDecryptionAlgorithm(privateKey),
new AesGcmContentDecryptionAlgorithm(algo));
String decryptedText = decryptor.decrypt(jweContent).getContentText();
assertEquals(decryptedText, plainContent);
}
private void decryptDirect(SecretKey key, String jweContent, String plainContent) throws Exception {
JweDecryption decryptor = new JweDecryption(new DirectKeyDecryptionAlgorithm(key),
new AesGcmContentDecryptionAlgorithm(ContentAlgorithm.A128GCM));
String decryptedText = decryptor.decrypt(jweContent).getContentText();
assertEquals(decryptedText, plainContent);
}
private SecretKey createSecretKey(boolean createIfException) throws Exception {
SecretKey key = null;
if (Cipher.getMaxAllowedKeyLength("AES") > 128) {
key = CryptoUtils.createSecretKeySpec(CONTENT_ENCRYPTION_KEY_A1, "AES");
} else if (createIfException) {
key = CryptoUtils.createSecretKeySpec(CryptoUtils.generateSecureRandomBytes(128 / 8), "AES");
}
return key;
}
}