From 34bb74488b3d8cc9f0378e92345e3355d93fd135 Mon Sep 17 00:00:00 2001 From: Les Hazlewood <121180+lhazlewood@users.noreply.github.com> Date: Sat, 2 Oct 2021 11:01:53 -0700 Subject: [PATCH] Added integration test defined in RFC 7516 Appendix A3 --- .../security/RFC7516AppendixA3Test.groovy | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7516AppendixA3Test.groovy diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7516AppendixA3Test.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7516AppendixA3Test.groovy new file mode 100644 index 00000000..b2d17b13 --- /dev/null +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7516AppendixA3Test.groovy @@ -0,0 +1,125 @@ +package io.jsonwebtoken.impl.security + +import io.jsonwebtoken.Jwe +import io.jsonwebtoken.Jwts +import io.jsonwebtoken.io.Decoders +import io.jsonwebtoken.io.Encoders +import io.jsonwebtoken.security.* +import org.junit.Test + +import javax.crypto.SecretKey +import javax.crypto.spec.SecretKeySpec +import java.nio.charset.StandardCharsets + +import static org.junit.Assert.assertArrayEquals +import static org.junit.Assert.assertEquals + +class RFC7516AppendixA3Test { + + static String encode(byte[] b) { + return Encoders.BASE64URL.encode(b) + } + + static byte[] decode(String val) { + return Decoders.BASE64URL.decode(val) + } + + // defined in https://datatracker.ietf.org/doc/html/rfc7516#appendix-A.3 : + final static String PLAINTEXT = 'Live long and prosper.' as String + final static byte[] PLAINTEXT_BYTES = [76, 105, 118, 101, 32, 108, 111, 110, 103, 32, 97, 110, 100, 32, + 112, 114, 111, 115, 112, 101, 114, 46] as byte[] + + // defined in https://datatracker.ietf.org/doc/html/rfc7516#appendix-A.3.1 : + final static String PROT_HEADER_STRING = '{"alg":"A128KW","enc":"A128CBC-HS256"}' as String + final static String encodedHeader = 'eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0' as String + + // defined in https://datatracker.ietf.org/doc/html/rfc7516#appendix-A.2.2 + final static byte[] CEK_BYTES = [4, 211, 31, 197, 84, 157, 252, 254, 11, 100, 157, 250, 63, 170, 106, + 206, 107, 124, 212, 45, 111, 107, 9, 219, 200, 177, 0, 240, 143, 156, + 44, 207] as byte[] + final static SecretKey CEK = new SecretKeySpec(CEK_BYTES, "AES") + + // defined in https://datatracker.ietf.org/doc/html/rfc7516#appendix-A.3.3 + final static Map KEK_VALUES = [ + "kty": "oct", + "k" : "GawgguFyGrWKav7AX4VKUg" + ] + + final static byte[] ENCRYPTED_CEK_BYTES = [232, 160, 123, 211, 183, 76, 245, 132, 200, 128, 123, 75, 190, 216, + 22, 67, 201, 138, 193, 186, 9, 91, 122, 31, 246, 90, 28, 139, 57, 3, + 76, 124, 193, 11, 98, 37, 173, 61, 104, 57] as byte[] + + final static String encodedEncryptedCek = '6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ' as String + + // https://datatracker.ietf.org/doc/html/rfc7516#appendix-A.3.4 + final static byte[] IV = [3, 22, 60, 12, 43, 67, 104, 105, 108, 108, 105, 99, 111, 116, 104, 101] as byte[] + final static String encodedIv = 'AxY8DCtDaGlsbGljb3RoZQ' as String + + // defined in https://datatracker.ietf.org/doc/html/rfc7516#appendix-A.3.5 + final static byte[] AAD_BYTES = [101, 121, 74, 104, 98, 71, 99, 105, 79, 105, 74, 66, 77, 84, 73, 52, + 83, 49, 99, 105, 76, 67, 74, 108, 98, 109, 77, 105, 79, 105, 74, 66, + 77, 84, 73, 52, 81, 48, 74, 68, 76, 85, 104, 84, 77, 106, 85, 50, 73, + 110, 48] as byte[] + + // defined in https://datatracker.ietf.org/doc/html/rfc7516#appendix-A.3.6 + final static byte[] CIPHERTEXT = [40, 57, 83, 181, 119, 33, 133, 148, 198, 185, 243, 24, 152, 230, 6, + 75, 129, 223, 127, 19, 210, 82, 183, 230, 168, 33, 215, 104, 143, + 112, 56, 102] as byte[] + final static String encodedCiphertext = 'KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY' as String + + final static byte[] TAG = [83, 73, 191, 98, 104, 205, 211, 128, 201, 189, 199, 133, 32, 38, 194, 85] as byte[] + final static String encodedTag = 'U0m_YmjN04DJvceFICbCVQ' + + // defined in https://datatracker.ietf.org/doc/html/rfc7516#appendix-A.3.7 + final static String COMPLETE_JWE = + 'eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.' + + '6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ.' + + 'AxY8DCtDaGlsbGljb3RoZQ.' + + 'KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY.' + + 'U0m_YmjN04DJvceFICbCVQ' as String + + @Test + void test() { + //ensure our test constants are correctly copied and match the RFC values: + assertEquals PLAINTEXT, new String(PLAINTEXT_BYTES, StandardCharsets.UTF_8) + assertEquals PROT_HEADER_STRING, new String(decode(encodedHeader), StandardCharsets.UTF_8) + assertEquals encodedEncryptedCek, encode(ENCRYPTED_CEK_BYTES) + assertEquals encodedIv, encode(IV) + assertArrayEquals AAD_BYTES, encodedHeader.getBytes(StandardCharsets.US_ASCII) + assertArrayEquals CIPHERTEXT, decode(encodedCiphertext) + assertArrayEquals TAG, decode(encodedTag) + + //read the RFC Test JWK to get the private key for decrypting + SecretJwk jwk = Jwks.builder().putAll(KEK_VALUES).build() as SecretJwk + SecretKey kek = jwk.toKey() + + // test decryption per the RFC + Jwe jwe = Jwts.parserBuilder().decryptWith(kek).build().parsePlaintextJwe(COMPLETE_JWE) + assertEquals PLAINTEXT, jwe.getBody() + + // now ensure that when JJWT does the encryption (i.e. a compact value is produced from JJWT, not from the RFC text), + // that the resulting compact string is identical to the RFC as described in + // https://datatracker.ietf.org/doc/html/rfc7516#appendix-A.3.8 : + + //ensure that the algorithm reflects the test harness values: + SymmetricAeadAlgorithm enc = new HmacAesAeadAlgorithm(128) { + @Override + protected byte[] ensureInitializationVector(SecurityRequest request) { + return IV; + } + + @Override + SecretKey generateKey() { + return CEK; + } + } + + String compact = Jwts.jweBuilder() + .setPayload(PLAINTEXT) + .encryptWith(enc) + .withKeyFrom(kek, KeyAlgorithms.A128KW) + .compact() + + assertEquals COMPLETE_JWE, compact + } +}