Pbes2HmacShaWithAesKeyWrapAlgorithmTest.java
/*
* Copyright 2012-2017 Brian Campbell
*
* 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.jose4j.jwe;
import org.jose4j.base64url.Base64Url;
import org.jose4j.jca.ProviderContextTest;
import org.jose4j.jwa.AlgorithmConstraints;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.MalformedClaimException;
import org.jose4j.jwt.NumericDate;
import org.jose4j.jwt.consumer.InvalidJwtException;
import org.jose4j.jwt.consumer.JwtConsumer;
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
import org.jose4j.jwt.consumer.JwtContext;
import org.jose4j.jwt.consumer.SimpleJwtConsumerTestHelp;
import org.jose4j.jwx.HeaderParameterNames;
import org.jose4j.jwx.Headers;
import org.jose4j.keys.AesKey;
import org.jose4j.keys.ExampleRsaJwksFromJwe;
import org.jose4j.keys.PbkdfKey;
import org.jose4j.lang.ByteUtil;
import org.jose4j.lang.InvalidKeyException;
import org.jose4j.lang.JoseException;
import org.junit.Assert;
import org.junit.Test;
import java.security.Key;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.jose4j.jwa.AlgorithmConstraints.ConstraintType.PERMIT;
import static org.jose4j.jwe.ContentEncryptionAlgorithmIdentifiers.*;
import static org.jose4j.jwe.KeyManagementAlgorithmIdentifiers.*;
import static org.jose4j.jwe.KeyManagementAlgorithmIdentifiers.RSA1_5;
import static org.junit.Assert.*;
/**
*/
public class Pbes2HmacShaWithAesKeyWrapAlgorithmTest
{
// per http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-23#section-4.8.1.2
// "A minimum iteration count of 1000 is RECOMMENDED."
public static final int MINIMUM_ITERATION_COUNT = 1000;
// per tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-23#section-4.8.1.1
// "A Salt Input value containing 8 or more octets MUST be used"
public static final int MINIMUM_SALT_BYTE_LENGTH = 8;
@Test
public void combinationOfRoundTrips() throws Exception
{
String[] algs = new String[] {PBES2_HS256_A128KW, PBES2_HS384_A192KW, PBES2_HS256_A128KW};
String[] encs = new String[] {AES_128_CBC_HMAC_SHA_256, AES_192_CBC_HMAC_SHA_384, AES_256_CBC_HMAC_SHA_512};
String password = "password";
String plaintext = "<insert some witty quote or remark here>";
for (String alg : algs)
{
for (String enc : encs)
{
JsonWebEncryption encryptingJwe = new JsonWebEncryption();
encryptingJwe.setAlgorithmConstraints(new AlgorithmConstraints(PERMIT, algs));
encryptingJwe.setAlgorithmHeaderValue(alg);
encryptingJwe.setEncryptionMethodHeaderParameter(enc);
encryptingJwe.setPayload(plaintext);
encryptingJwe.setKey(new PbkdfKey(password));
String compactSerialization = encryptingJwe.getCompactSerialization();
JsonWebEncryption decryptingJwe = new JsonWebEncryption();
decryptingJwe.setAlgorithmConstraints(new AlgorithmConstraints(PERMIT, algs));
decryptingJwe.setCompactSerialization(compactSerialization);
decryptingJwe.setKey(new PbkdfKey(password));
assertThat(plaintext, equalTo(decryptingJwe.getPayload()));
}
}
}
@Test (expected = InvalidKeyException.class)
public void testNullKey() throws JoseException
{
JsonWebEncryption encryptingJwe = new JsonWebEncryption();
encryptingJwe.setAlgorithmHeaderValue(PBES2_HS256_A128KW);
encryptingJwe.setAlgorithmConstraints(new AlgorithmConstraints(PERMIT, KeyManagementAlgorithmIdentifiers.PBES2_HS256_A128KW));
encryptingJwe.setEncryptionMethodHeaderParameter(AES_128_CBC_HMAC_SHA_256);
encryptingJwe.setPayload("meh");
encryptingJwe.getCompactSerialization();
}
@Test
public void testDefaultsMeetMinimumRequiredOrSuggested() throws JoseException
{
JsonWebEncryption encryptingJwe = new JsonWebEncryption();
encryptingJwe.setAlgorithmHeaderValue(PBES2_HS256_A128KW);
encryptingJwe.setAlgorithmConstraints(new AlgorithmConstraints(PERMIT, KeyManagementAlgorithmIdentifiers.PBES2_HS256_A128KW));
encryptingJwe.setEncryptionMethodHeaderParameter(AES_128_CBC_HMAC_SHA_256);
encryptingJwe.setPayload("meh");
PbkdfKey key = new PbkdfKey("passtheword");
encryptingJwe.setKey(key);
String compactSerialization = encryptingJwe.getCompactSerialization();
System.out.println(compactSerialization);
JsonWebEncryption decryptingJwe = new JsonWebEncryption();
decryptingJwe.setAlgorithmConstraints(new AlgorithmConstraints(PERMIT, KeyManagementAlgorithmIdentifiers.PBES2_HS256_A128KW));
decryptingJwe.setCompactSerialization(compactSerialization);
decryptingJwe.setKey(key);
decryptingJwe.getPayload();
Headers headers = decryptingJwe.getHeaders();
Long iterationCount = headers.getLongHeaderValue(HeaderParameterNames.PBES2_ITERATION_COUNT);
assertTrue(iterationCount >= MINIMUM_ITERATION_COUNT);
String saltInputString = headers.getStringHeaderValue(HeaderParameterNames.PBES2_SALT_INPUT);
Base64Url b = new Base64Url();
byte[] saltInput = b.base64UrlDecode(saltInputString);
assertTrue(saltInput.length >= MINIMUM_SALT_BYTE_LENGTH);
}
@Test
public void testUsingAndSettingDefaults() throws JoseException
{
Pbes2HmacShaWithAesKeyWrapAlgorithm pbes2 = new Pbes2HmacShaWithAesKeyWrapAlgorithm.HmacSha256Aes128();
assertTrue(pbes2.getDefaultIterationCount() >= MINIMUM_ITERATION_COUNT);
assertTrue(pbes2.getDefaultSaltByteLength() >= MINIMUM_SALT_BYTE_LENGTH);
PbkdfKey key = new PbkdfKey("a password");
Headers headers = new Headers();
Key derivedKey = pbes2.deriveForEncrypt(key, headers, ProviderContextTest.EMPTY_CONTEXT);
assertThat(derivedKey.getEncoded().length, equalTo(16));
String saltInputString = headers.getStringHeaderValue(HeaderParameterNames.PBES2_SALT_INPUT);
byte[] saltInput = Base64Url.decode(saltInputString);
assertThat(saltInput.length, equalTo(pbes2.getDefaultSaltByteLength()));
Long iterationCount = headers.getLongHeaderValue(HeaderParameterNames.PBES2_ITERATION_COUNT);
assertThat(iterationCount, equalTo(pbes2.getDefaultIterationCount()));
Pbes2HmacShaWithAesKeyWrapAlgorithm newPbes2 = new Pbes2HmacShaWithAesKeyWrapAlgorithm.HmacSha256Aes128();
long newDefaultIterationCount = 1024;
newPbes2.setDefaultIterationCount(newDefaultIterationCount);
int newDefaultSaltByteLength = 16;
newPbes2.setDefaultSaltByteLength(newDefaultSaltByteLength);
headers = new Headers();
derivedKey = newPbes2.deriveForEncrypt(key, headers, ProviderContextTest.EMPTY_CONTEXT);
saltInputString = headers.getStringHeaderValue(HeaderParameterNames.PBES2_SALT_INPUT);
saltInput = Base64Url.decode(saltInputString);
assertThat(saltInput.length, equalTo(newDefaultSaltByteLength));
iterationCount = headers.getLongHeaderValue(HeaderParameterNames.PBES2_ITERATION_COUNT);
assertThat(iterationCount, equalTo(newDefaultIterationCount));
assertThat(derivedKey.getEncoded().length, equalTo(16));
}
@Test
public void testSettingSaltAndIterationCount() throws JoseException
{
String password = "secret word";
String plaintext = "<insert some witty quote or remark here, again>";
JsonWebEncryption encryptingJwe = new JsonWebEncryption();
int saltByteLength = 32;
String saltInputString = Base64Url.encode(ByteUtil.randomBytes(saltByteLength));
encryptingJwe.getHeaders().setStringHeaderValue(HeaderParameterNames.PBES2_SALT_INPUT, saltInputString);
long iterationCount = 1024L;
encryptingJwe.setHeader(HeaderParameterNames.PBES2_ITERATION_COUNT, iterationCount);
encryptingJwe.setAlgorithmHeaderValue(PBES2_HS384_A192KW);
encryptingJwe.setAlgorithmConstraints(new AlgorithmConstraints(PERMIT, KeyManagementAlgorithmIdentifiers.PBES2_HS384_A192KW));
encryptingJwe.setEncryptionMethodHeaderParameter(AES_192_CBC_HMAC_SHA_384);
encryptingJwe.setPayload(plaintext);
encryptingJwe.setKey(new PbkdfKey(password));
String compactSerialization = encryptingJwe.getCompactSerialization();
JsonWebEncryption decryptingJwe = new JsonWebEncryption();
decryptingJwe.setAlgorithmConstraints(new AlgorithmConstraints(PERMIT, KeyManagementAlgorithmIdentifiers.PBES2_HS384_A192KW));
decryptingJwe.setCompactSerialization(compactSerialization);
decryptingJwe.setKey(new PbkdfKey(password));
assertThat(plaintext, equalTo(decryptingJwe.getPayload()));
String saltInputStringFromHeader = decryptingJwe.getHeader(HeaderParameterNames.PBES2_SALT_INPUT);
assertThat(saltInputString, equalTo(saltInputStringFromHeader));
assertThat(saltByteLength, equalTo(Base64Url.decode(saltInputStringFromHeader).length));
long iterationCountFromHeader = decryptingJwe.getHeaders().getLongHeaderValue(HeaderParameterNames.PBES2_ITERATION_COUNT);
assertThat(iterationCount, equalTo(iterationCountFromHeader));
}
@Test (expected = JoseException.class)
public void testTooSmallIterationCountRejected() throws JoseException
{
JsonWebEncryption encryptingJwe = new JsonWebEncryption();
encryptingJwe.setHeader(HeaderParameterNames.PBES2_ITERATION_COUNT, 918L);
encryptingJwe.setAlgorithmHeaderValue(PBES2_HS256_A128KW);
encryptingJwe.setEncryptionMethodHeaderParameter(AES_128_CBC_HMAC_SHA_256);
encryptingJwe.setPayload("some text");
encryptingJwe.setKey(new PbkdfKey("super secret word"));
encryptingJwe.getCompactSerialization();
}
@Test (expected = JoseException.class)
public void testTooLittleSaltRejected() throws JoseException
{
JsonWebEncryption encryptingJwe = new JsonWebEncryption();
encryptingJwe.setHeader(HeaderParameterNames.PBES2_SALT_INPUT, "bWVo");
encryptingJwe.setAlgorithmHeaderValue(PBES2_HS256_A128KW);
encryptingJwe.setEncryptionMethodHeaderParameter(AES_128_CBC_HMAC_SHA_256);
encryptingJwe.setPayload("some text");
encryptingJwe.setKey(new PbkdfKey("super secret word"));
encryptingJwe.getCompactSerialization();
}
@Test
public void p2cTooBig() throws InvalidJwtException, MalformedClaimException
{
// check that default protections are in place for the "Billion hashes attack" from
// https://i.blackhat.com/BH-US-23/Presentations/US-23-Tervoort-Three-New-Attacks-Against-JSON-Web-Tokens-whitepaper.pdf
// PBES2-HS256+A128KW
String jwe1 = "eyJhbGciOiJQQkVTMi1IUzUxMitBMjU2S1ciLCJwMnMiOiI4UTFTemluYXNSM3h" +
"jaFl6NlpaY0hBIiwicDJjIjoyMTQ3NDgzNjQ3LCJlbmMiOiJBMTI4Q0JDLUhTMj" +
"U2In0.YKbKLsEoyw_JoNvhtuHo9aaeRNSEhhAW2OVHcuF_HLqS0n6hA_fgCA.VB" +
"iCzVHNoLiR3F4V82uoTQ.23i-Tb1AV4n0WKVSSgcQrdg6GRqsUKxjruHXYsTHAJ" +
"LZ2nsnGIX86vMXqIi6IRsfywCRFzLxEcZBRnTvG3nhzPk0GDD7FMyXhUHpDjEYC" +
"NA_XOmzg8yZR9oyjo6lTF6si4q9FZ2EhzgFQCLO_6h5EVg3vR75_hkBsnuoqoM3" +
"dwejXBtIodN84PeqMb6asmas_dpSsz7H10fC5ni9xIz424givB1YLldF6exVmL9" +
"3R3fOoOJbmk2GBQZL_SEGllv2cQsBgeprARsaQ7Bq99tT80coH8ItBjgV08AtzX" +
"FFsx9qKvC982KLKdPQMTlVJKkqtV4Ru5LEVpBZXBnZrtViSOgyg6AiuwaS-rCrc" +
"D_ePOGSuxvgtrokAKYPqmXUeRdjFJwafkYEkiuDCV9vWGAi1DH2xTafhJwcmywI" +
"yzi4BqRpmdn_N-zl5tuJYyuvKhjKv6ihbsV_k1hJGPGAxJ6wUpmwC4PTQ2izEm0" +
"TuSE8oMKdTw8V3kobXZ77ulMwDs4p.ALTKwxvAefeL-32NY7eTAQ";
// PBES2-HS512+A256KW
String jwe2 = "eyJwMmMiOjI1MDAwMDAsImFsZyI6IlBCRVMyLUhTNTEyK0EyNTZLVyIsImVuYyI6IkEyNTZDQkMtSFM1MTIiLC" +
"JwMnMiOiJGbVE0aDY1aUFlZEs0SUFyIn0._P2Mbn0nvqRZVCaEaLnKQkMFwGNmEVbm8Ffnb5uIas0iAt5wcWC3T7rdTw" +
"yliWW11YnhpaiXH0WRalAsIUyaVHC4Ku1j9bVP.Tg2KOblqWEF9iC71O-WgBw.OCjt9WYrTFMIst7XBZ8HeA.YRqs3_n" +
"MchYr39AJYquQs8-PrZa2NGuqshOvtLfWSvE";
// PBES2-HS384+A192KW
String jwe3 = "eyJwMmMiOjI1MDAwMDAsImFsZyI6IlBCRVMyLUhTMzg0K0ExOTJLVyIsImVuYyI6IkExOTJDQkMtSFMzODQiLC" +
"JwMnMiOiJneXBKYzJFVXNtbmNqTUtqIn0.RYXJhCW2m4Pa5XPUPVVQVJRg8z-jj-zyXoa-Q1JzdfjO2tvrELM7Ko3qhk" +
"v2WcUAw3ZagzIeNjY.FhNCr7zjUt0fA6KotCbdUw.DMYLybjcrOX9cfwdWaORLg.QCGY9clkv4sz1rZeexg2dUx4ViH-BeL7\n";
for (String jwt : new String[] {jwe1, jwe2, jwe3})
{
// PBE2 algs blocked by default
JwtConsumer c = new JwtConsumerBuilder()
.setDecryptionKey(ExampleRsaJwksFromJwe.APPENDIX_A_2.getPrivateKey())
.build();
SimpleJwtConsumerTestHelp.expectProcessingFailure(jwt, c);
// but also there's a max on the # of iterations
c = new JwtConsumerBuilder()
.setDecryptionKey(new PbkdfKey("super secret word"))
.setJweAlgorithmConstraints(AlgorithmConstraints.NO_CONSTRAINTS)
.build();
SimpleJwtConsumerTestHelp.expectProcessingFailure(jwt, c);
}
}
}