Java tutorial
/** * Copyright (c) 2012-2014 Netflix, Inc. All rights reserved. * * 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 com.netflix.msl.keyx; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Security; import java.security.spec.AlgorithmParameterSpec; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.Random; import javax.crypto.SecretKey; import javax.xml.bind.DatatypeConverter; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jce.spec.ECParameterSpec; import org.bouncycastle.math.ec.ECCurve; import org.json.JSONException; import org.json.JSONObject; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; import com.netflix.msl.MslCryptoException; import com.netflix.msl.MslEncodingException; import com.netflix.msl.MslEntityAuthException; import com.netflix.msl.MslError; import com.netflix.msl.MslException; import com.netflix.msl.MslInternalException; import com.netflix.msl.MslKeyExchangeException; import com.netflix.msl.MslMasterTokenException; import com.netflix.msl.crypto.ICryptoContext; import com.netflix.msl.entityauth.EntityAuthenticationScheme; import com.netflix.msl.entityauth.MockPresharedAuthenticationFactory; import com.netflix.msl.keyx.AsymmetricWrappedExchange.RequestData; import com.netflix.msl.keyx.AsymmetricWrappedExchange.RequestData.Mechanism; import com.netflix.msl.keyx.AsymmetricWrappedExchange.ResponseData; import com.netflix.msl.keyx.KeyExchangeFactory.KeyExchangeData; import com.netflix.msl.test.ExpectedMslException; import com.netflix.msl.tokens.MasterToken; import com.netflix.msl.util.JsonUtils; import com.netflix.msl.util.MockAuthenticationUtils; import com.netflix.msl.util.MockMslContext; import com.netflix.msl.util.MslContext; import com.netflix.msl.util.MslTestUtils; /** * Asymmetric wrapped key exchange unit tests. * * @author Wesley Miaw <wmiaw@netflix.com> */ @RunWith(Suite.class) @SuiteClasses({ AsymmetricWrappedExchangeSuite.RequestDataTest.class, AsymmetricWrappedExchangeSuite.RequestDataTest.Params.class, AsymmetricWrappedExchangeSuite.ResponseDataTest.class, AsymmetricWrappedExchangeSuite.KeyExchangeFactoryTest.class, AsymmetricWrappedExchangeSuite.KeyExchangeFactoryTest.Params.class }) public class AsymmetricWrappedExchangeSuite { /** EC curve q. */ private static final BigInteger EC_Q = new BigInteger( "883423532389192164791648750360308885314476597252960362792450860609699839"); /** EC coefficient a. */ private static final BigInteger EC_A = new BigInteger( "7fffffffffffffffffffffff7fffffffffff8000000000007ffffffffffc", 16); /** EC coefficient b. */ private static final BigInteger EC_B = new BigInteger( "6b016c3bdcf18941d0d654921475ca71a9db2fb27d1d37796185c2942c0a", 16); /** EC base point g. */ private static final BigInteger EC_G = new BigInteger( "020ffa963cdca8816ccc33b8642bedf905c3d358573d3f27fbbd3b3cb9aaaf", 16); /** EC generator order n. */ private static final BigInteger EC_N = new BigInteger( "883423532389192164791648750360308884807550341691627752275345424702807307"); /** JSON key key exchange scheme. */ private static final String KEY_SCHEME = "scheme"; /** JSON key key request data. */ private static final String KEY_KEYDATA = "keydata"; /** JSON key key pair ID. */ private static final String KEY_KEY_PAIR_ID = "keypairid"; /** JSON key encrypted encryption key. */ private static final String KEY_ENCRYPTION_KEY = "encryptionkey"; /** JSON key encrypted HMAC key. */ private static final String KEY_HMAC_KEY = "hmackey"; private static final String KEYPAIR_ID = "keypairId"; private static PublicKey ECC_PUBLIC_KEY; private static PrivateKey ECC_PRIVATE_KEY; private static PublicKey RSA_PUBLIC_KEY; private static PrivateKey RSA_PRIVATE_KEY; private static final String IDENTITY = MockPresharedAuthenticationFactory.PSK_ESN; private static MasterToken MASTER_TOKEN; private static byte[] ENCRYPTION_KEY; private static byte[] HMAC_KEY; @BeforeClass public static synchronized void setup() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, MslEncodingException, MslCryptoException { if (ctx == null) { Security.addProvider(new BouncyCastleProvider()); final ECCurve curve = new ECCurve.Fp(EC_Q, EC_A, EC_B); final AlgorithmParameterSpec paramSpec = new ECParameterSpec(curve, curve.decodePoint(EC_G.toByteArray()), EC_N); final KeyPairGenerator eccGenerator = KeyPairGenerator.getInstance("ECIES"); eccGenerator.initialize(paramSpec); final KeyPair eccKeyPair = eccGenerator.generateKeyPair(); ECC_PUBLIC_KEY = eccKeyPair.getPublic(); ECC_PRIVATE_KEY = eccKeyPair.getPrivate(); final KeyPairGenerator rsaGenerator = KeyPairGenerator.getInstance("RSA"); rsaGenerator.initialize(2048); final KeyPair rsaKeyPair = rsaGenerator.generateKeyPair(); RSA_PUBLIC_KEY = rsaKeyPair.getPublic(); RSA_PRIVATE_KEY = rsaKeyPair.getPrivate(); ctx = new MockMslContext(EntityAuthenticationScheme.PSK, false); MASTER_TOKEN = MslTestUtils.getMasterToken(ctx, 1, 1); ENCRYPTION_KEY = MASTER_TOKEN.getEncryptionKey().getEncoded(); HMAC_KEY = MASTER_TOKEN.getHmacKey().getEncoded(); } } @AfterClass public static synchronized void teardown() { // Teardown causes problems because the data is shared by the inner // classes, so don't do any cleanup. } /** MSL context. */ private static MslContext ctx; /** Request data unit tests. */ public static class RequestDataTest { /** JSON key key pair ID. */ private static final String KEY_KEY_PAIR_ID = "keypairid"; /** JSON key mechanism. */ private static final String KEY_MECHANISM = "mechanism"; /** JSON key public key. */ private static final String KEY_PUBLIC_KEY = "publickey"; @RunWith(Parameterized.class) public static class Params { @Rule public ExpectedMslException thrown = ExpectedMslException.none(); @Parameters public static Collection<Object[]> data() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, MslEncodingException, MslCryptoException { AsymmetricWrappedExchangeSuite.setup(); return Arrays.asList(new Object[][] { { Mechanism.JWE_RSA, RSA_PUBLIC_KEY, RSA_PRIVATE_KEY }, { Mechanism.JWEJS_RSA, RSA_PUBLIC_KEY, RSA_PRIVATE_KEY }, { Mechanism.JWK_RSA, RSA_PUBLIC_KEY, RSA_PRIVATE_KEY }, { Mechanism.JWK_RSAES, RSA_PUBLIC_KEY, RSA_PRIVATE_KEY }, }); } /** Key exchange mechanism. */ private final Mechanism mechanism; /** Public key. */ private final PublicKey publicKey; /** Private key. */ private final PrivateKey privateKey; /** * Create a new request data test instance with the specified key * exchange parameters. * * @param mechanism key exchange mechanism. * @param publicKey public key. * @param privateKey private key. */ public Params(final Mechanism mechanism, final PublicKey publicKey, final PrivateKey privateKey) { this.mechanism = mechanism; this.publicKey = publicKey; this.privateKey = privateKey; } @Test public void ctors() throws JSONException, MslEncodingException, MslCryptoException, MslKeyExchangeException { final RequestData req = new RequestData(KEYPAIR_ID, mechanism, publicKey, privateKey); assertEquals(KeyExchangeScheme.ASYMMETRIC_WRAPPED, req.getKeyExchangeScheme()); assertEquals(KEYPAIR_ID, req.getKeyPairId()); assertEquals(mechanism, req.getMechanism()); assertArrayEquals(privateKey.getEncoded(), req.getPrivateKey().getEncoded()); assertArrayEquals(publicKey.getEncoded(), req.getPublicKey().getEncoded()); final JSONObject keydata = req.getKeydata(); assertNotNull(keydata); final RequestData joReq = new RequestData(keydata); assertEquals(req.getKeyExchangeScheme(), joReq.getKeyExchangeScheme()); assertEquals(req.getKeyPairId(), joReq.getKeyPairId()); assertEquals(req.getMechanism(), joReq.getMechanism()); assertNull(joReq.getPrivateKey()); assertArrayEquals(req.getPublicKey().getEncoded(), joReq.getPublicKey().getEncoded()); final JSONObject joKeydata = joReq.getKeydata(); assertNotNull(joKeydata); assertTrue(JsonUtils.equals(keydata, joKeydata)); } @Test public void jsonString() throws JSONException { final RequestData req = new RequestData(KEYPAIR_ID, mechanism, publicKey, privateKey); final JSONObject jo = new JSONObject(req.toJSONString()); assertEquals(KeyExchangeScheme.ASYMMETRIC_WRAPPED.toString(), jo.getString(KEY_SCHEME)); final JSONObject keydata = jo.getJSONObject(KEY_KEYDATA); assertEquals(KEYPAIR_ID, keydata.getString(KEY_KEY_PAIR_ID)); assertEquals(mechanism.toString(), keydata.getString(KEY_MECHANISM)); assertArrayEquals(publicKey.getEncoded(), DatatypeConverter.parseBase64Binary(keydata.getString(KEY_PUBLIC_KEY))); } @Test public void create() throws JSONException, MslEncodingException, MslEntityAuthException, MslCryptoException, MslKeyExchangeException { final RequestData data = new RequestData(KEYPAIR_ID, mechanism, publicKey, privateKey); final String jsonString = data.toJSONString(); final JSONObject jo = new JSONObject(jsonString); final KeyRequestData keyRequestData = KeyRequestData.create(ctx, jo); assertNotNull(keyRequestData); assertTrue(keyRequestData instanceof RequestData); final RequestData joData = (RequestData) keyRequestData; assertEquals(data.getKeyExchangeScheme(), joData.getKeyExchangeScheme()); assertEquals(data.getKeyPairId(), joData.getKeyPairId()); assertEquals(data.getMechanism(), joData.getMechanism()); assertNull(joData.getPrivateKey()); assertArrayEquals(data.getPublicKey().getEncoded(), joData.getPublicKey().getEncoded()); } @Test public void missingKeypairId() throws JSONException, MslEncodingException, MslCryptoException, MslKeyExchangeException { thrown.expect(MslEncodingException.class); thrown.expectMslError(MslError.JSON_PARSE_ERROR); final RequestData req = new RequestData(KEYPAIR_ID, mechanism, publicKey, privateKey); final JSONObject keydata = req.getKeydata(); assertNotNull(keydata.remove(KEY_KEY_PAIR_ID)); new RequestData(keydata); } @Test public void missingMechanism() throws JSONException, MslEncodingException, MslCryptoException, MslKeyExchangeException { thrown.expect(MslEncodingException.class); thrown.expectMslError(MslError.JSON_PARSE_ERROR); final RequestData req = new RequestData(KEYPAIR_ID, mechanism, publicKey, privateKey); final JSONObject keydata = req.getKeydata(); assertNotNull(keydata.remove(KEY_MECHANISM)); new RequestData(keydata); } @Test public void invalidMechanism() throws JSONException, MslEncodingException, MslCryptoException, MslKeyExchangeException { thrown.expect(MslKeyExchangeException.class); thrown.expectMslError(MslError.UNIDENTIFIED_KEYX_MECHANISM); final RequestData req = new RequestData(KEYPAIR_ID, mechanism, publicKey, privateKey); final JSONObject keydata = req.getKeydata(); keydata.put(KEY_MECHANISM, "x"); new RequestData(keydata); } @Test public void missingPublicKey() throws JSONException, MslEncodingException, MslCryptoException, MslKeyExchangeException { thrown.expect(MslEncodingException.class); thrown.expectMslError(MslError.JSON_PARSE_ERROR); final RequestData req = new RequestData(KEYPAIR_ID, mechanism, publicKey, privateKey); final JSONObject keydata = req.getKeydata(); assertNotNull(keydata.remove(KEY_PUBLIC_KEY)); new RequestData(keydata); } @Test public void invalidPublicKey() throws JSONException, MslEncodingException, MslCryptoException, MslKeyExchangeException { thrown.expect(MslCryptoException.class); thrown.expectMslError(MslError.INVALID_PUBLIC_KEY); final RequestData req = new RequestData(KEYPAIR_ID, mechanism, publicKey, privateKey); final JSONObject keydata = req.getKeydata(); final byte[] encodedKey = publicKey.getEncoded(); final byte[] shortKey = Arrays.copyOf(encodedKey, encodedKey.length / 2); keydata.put(KEY_PUBLIC_KEY, DatatypeConverter.printBase64Binary(shortKey)); new RequestData(keydata); } } @Test public void equalsKeyPairId() throws MslEncodingException, MslCryptoException, MslKeyExchangeException, JSONException { final RequestData dataA = new RequestData(KEYPAIR_ID + "A", Mechanism.JWE_RSA, RSA_PUBLIC_KEY, RSA_PRIVATE_KEY); final RequestData dataB = new RequestData(KEYPAIR_ID + "B", Mechanism.JWE_RSA, RSA_PUBLIC_KEY, RSA_PRIVATE_KEY); final RequestData dataA2 = new RequestData(dataA.getKeydata()); assertTrue(dataA.equals(dataA)); assertEquals(dataA.hashCode(), dataA.hashCode()); assertFalse(dataA.equals(dataB)); assertFalse(dataB.equals(dataA)); assertTrue(dataA.hashCode() != dataB.hashCode()); // The private keys don't transfer via the JSON constructor. assertFalse(dataA.equals(dataA2)); assertFalse(dataA2.equals(dataA)); assertTrue(dataA.hashCode() != dataA2.hashCode()); } @Test public void equalsMechanism() throws MslEncodingException, MslCryptoException, MslKeyExchangeException, JSONException { final RequestData dataA = new RequestData(KEYPAIR_ID, Mechanism.JWE_RSA, RSA_PUBLIC_KEY, RSA_PRIVATE_KEY); final RequestData dataB = new RequestData(KEYPAIR_ID, Mechanism.ECC, RSA_PUBLIC_KEY, RSA_PRIVATE_KEY); final RequestData dataA2 = new RequestData(dataA.getKeydata()); assertTrue(dataA.equals(dataA)); assertEquals(dataA.hashCode(), dataA.hashCode()); assertFalse(dataA.equals(dataB)); assertFalse(dataB.equals(dataA)); assertTrue(dataA.hashCode() != dataB.hashCode()); // The private keys don't transfer via the JSON constructor. assertFalse(dataA.equals(dataA2)); assertFalse(dataA2.equals(dataA)); assertTrue(dataA.hashCode() != dataA2.hashCode()); } @Test public void equalsPublicKey() throws MslEncodingException, MslCryptoException, MslKeyExchangeException, JSONException { final RequestData dataA = new RequestData(KEYPAIR_ID, Mechanism.JWE_RSA, RSA_PUBLIC_KEY, RSA_PRIVATE_KEY); final RequestData dataB = new RequestData(KEYPAIR_ID, Mechanism.JWE_RSA, ECC_PUBLIC_KEY, RSA_PRIVATE_KEY); final RequestData dataA2 = new RequestData(dataA.getKeydata()); assertTrue(dataA.equals(dataA)); assertEquals(dataA.hashCode(), dataA.hashCode()); assertFalse(dataA.equals(dataB)); assertFalse(dataB.equals(dataA)); assertTrue(dataA.hashCode() != dataB.hashCode()); // The private keys don't transfer via the JSON constructor. assertFalse(dataA.equals(dataA2)); assertFalse(dataA2.equals(dataA)); assertTrue(dataA.hashCode() != dataA2.hashCode()); } @Test public void equalsPrivateKey() throws MslEncodingException, MslCryptoException, MslKeyExchangeException, JSONException { final RequestData dataA = new RequestData(KEYPAIR_ID, Mechanism.JWE_RSA, RSA_PUBLIC_KEY, RSA_PRIVATE_KEY); final RequestData dataB = new RequestData(KEYPAIR_ID, Mechanism.JWE_RSA, RSA_PUBLIC_KEY, ECC_PRIVATE_KEY); final RequestData dataA2 = new RequestData(dataA.getKeydata()); assertTrue(dataA.equals(dataA)); assertEquals(dataA.hashCode(), dataA.hashCode()); assertFalse(dataA.equals(dataB)); assertFalse(dataB.equals(dataA)); assertTrue(dataA.hashCode() != dataB.hashCode()); // The private keys don't transfer via the JSON constructor. assertFalse(dataA.equals(dataA2)); assertFalse(dataA2.equals(dataA)); assertTrue(dataA.hashCode() != dataA2.hashCode()); } @Test public void equalsObject() { final RequestData data = new RequestData(KEYPAIR_ID, Mechanism.JWE_RSA, RSA_PUBLIC_KEY, RSA_PRIVATE_KEY); assertFalse(data.equals(null)); assertFalse(data.equals(IDENTITY)); assertTrue(data.hashCode() != IDENTITY.hashCode()); } } /** Response data unit tests. */ public static class ResponseDataTest { /** JSON key master token. */ private static final String KEY_MASTER_TOKEN = "mastertoken"; @Rule public ExpectedMslException thrown = ExpectedMslException.none(); @Test public void ctors() throws MslEncodingException, JSONException, MslKeyExchangeException { final ResponseData resp = new ResponseData(MASTER_TOKEN, KEYPAIR_ID, ENCRYPTION_KEY, HMAC_KEY); assertArrayEquals(ENCRYPTION_KEY, resp.getEncryptionKey()); assertArrayEquals(HMAC_KEY, resp.getHmacKey()); assertEquals(KeyExchangeScheme.ASYMMETRIC_WRAPPED, resp.getKeyExchangeScheme()); assertEquals(KEYPAIR_ID, resp.getKeyPairId()); assertEquals(MASTER_TOKEN, resp.getMasterToken()); final JSONObject keydata = resp.getKeydata(); assertNotNull(keydata); final ResponseData joResp = new ResponseData(MASTER_TOKEN, keydata); assertArrayEquals(resp.getEncryptionKey(), joResp.getEncryptionKey()); assertArrayEquals(resp.getHmacKey(), joResp.getHmacKey()); assertEquals(resp.getKeyExchangeScheme(), joResp.getKeyExchangeScheme()); assertEquals(resp.getKeyPairId(), joResp.getKeyPairId()); assertEquals(resp.getMasterToken(), joResp.getMasterToken()); final JSONObject joKeydata = joResp.getKeydata(); assertNotNull(joKeydata); assertTrue(JsonUtils.equals(keydata, joKeydata)); } @Test public void jsonString() throws JSONException, MslEncodingException, MslCryptoException, MslException { final ResponseData resp = new ResponseData(MASTER_TOKEN, KEYPAIR_ID, ENCRYPTION_KEY, HMAC_KEY); final JSONObject jo = new JSONObject(resp.toJSONString()); assertEquals(KeyExchangeScheme.ASYMMETRIC_WRAPPED.toString(), jo.getString(KEY_SCHEME)); final MasterToken masterToken = new MasterToken(ctx, jo.getJSONObject(KEY_MASTER_TOKEN)); assertEquals(MASTER_TOKEN, masterToken); final JSONObject keydata = jo.getJSONObject(KEY_KEYDATA); assertEquals(KEYPAIR_ID, keydata.getString(KEY_KEY_PAIR_ID)); assertArrayEquals(ENCRYPTION_KEY, DatatypeConverter.parseBase64Binary(keydata.getString(KEY_ENCRYPTION_KEY))); assertArrayEquals(HMAC_KEY, DatatypeConverter.parseBase64Binary(keydata.getString(KEY_HMAC_KEY))); } @Test public void create() throws JSONException, MslException { final ResponseData data = new ResponseData(MASTER_TOKEN, KEYPAIR_ID, ENCRYPTION_KEY, HMAC_KEY); final String jsonString = data.toJSONString(); final JSONObject jo = new JSONObject(jsonString); final KeyResponseData keyResponseData = KeyResponseData.create(ctx, jo); assertNotNull(keyResponseData); assertTrue(keyResponseData instanceof ResponseData); final ResponseData joData = (ResponseData) keyResponseData; assertArrayEquals(data.getEncryptionKey(), joData.getEncryptionKey()); assertArrayEquals(data.getHmacKey(), joData.getHmacKey()); assertEquals(data.getKeyExchangeScheme(), joData.getKeyExchangeScheme()); assertEquals(data.getKeyPairId(), joData.getKeyPairId()); assertEquals(data.getMasterToken(), joData.getMasterToken()); } @Test public void missingKeyPairId() throws MslEncodingException, JSONException, MslKeyExchangeException { thrown.expect(MslEncodingException.class); thrown.expectMslError(MslError.JSON_PARSE_ERROR); final ResponseData resp = new ResponseData(MASTER_TOKEN, KEYPAIR_ID, ENCRYPTION_KEY, HMAC_KEY); final JSONObject keydata = resp.getKeydata(); assertNotNull(keydata.remove(KEY_KEY_PAIR_ID)); new ResponseData(MASTER_TOKEN, keydata); } @Test public void missingEncryptionKey() throws JSONException, MslEncodingException, MslKeyExchangeException { thrown.expect(MslEncodingException.class); thrown.expectMslError(MslError.JSON_PARSE_ERROR); final ResponseData resp = new ResponseData(MASTER_TOKEN, KEYPAIR_ID, ENCRYPTION_KEY, HMAC_KEY); final JSONObject keydata = resp.getKeydata(); assertNotNull(keydata.remove(KEY_ENCRYPTION_KEY)); new ResponseData(MASTER_TOKEN, keydata); } @Test public void missingHmacKey() throws JSONException, MslEncodingException, MslKeyExchangeException { thrown.expect(MslEncodingException.class); thrown.expectMslError(MslError.JSON_PARSE_ERROR); final ResponseData resp = new ResponseData(MASTER_TOKEN, KEYPAIR_ID, ENCRYPTION_KEY, HMAC_KEY); final JSONObject keydata = resp.getKeydata(); assertNotNull(keydata.remove(KEY_HMAC_KEY)); new ResponseData(MASTER_TOKEN, keydata); } @Test public void equalsMasterToken() throws MslEncodingException, JSONException, MslCryptoException, MslKeyExchangeException { final MasterToken masterTokenA = MslTestUtils.getMasterToken(ctx, 1, 1); final MasterToken masterTokenB = MslTestUtils.getMasterToken(ctx, 1, 2); final ResponseData dataA = new ResponseData(masterTokenA, KEYPAIR_ID, ENCRYPTION_KEY, HMAC_KEY); final ResponseData dataB = new ResponseData(masterTokenB, KEYPAIR_ID, ENCRYPTION_KEY, HMAC_KEY); final ResponseData dataA2 = new ResponseData(masterTokenA, dataA.getKeydata()); assertTrue(dataA.equals(dataA)); assertEquals(dataA.hashCode(), dataA.hashCode()); assertFalse(dataA.equals(dataB)); assertFalse(dataB.equals(dataA)); assertTrue(dataA.hashCode() != dataB.hashCode()); assertTrue(dataA.equals(dataA2)); assertTrue(dataA2.equals(dataA)); assertEquals(dataA.hashCode(), dataA2.hashCode()); } @Test public void equalsKeyPairId() throws MslEncodingException, JSONException, MslKeyExchangeException { final ResponseData dataA = new ResponseData(MASTER_TOKEN, KEYPAIR_ID + "A", ENCRYPTION_KEY, HMAC_KEY); final ResponseData dataB = new ResponseData(MASTER_TOKEN, KEYPAIR_ID + "B", ENCRYPTION_KEY, HMAC_KEY); final ResponseData dataA2 = new ResponseData(MASTER_TOKEN, dataA.getKeydata()); assertTrue(dataA.equals(dataA)); assertEquals(dataA.hashCode(), dataA.hashCode()); assertFalse(dataA.equals(dataB)); assertFalse(dataB.equals(dataA)); assertTrue(dataA.hashCode() != dataB.hashCode()); assertTrue(dataA.equals(dataA2)); assertTrue(dataA2.equals(dataA)); assertEquals(dataA.hashCode(), dataA2.hashCode()); } @Test public void equalsEncryptionKey() throws MslEncodingException, JSONException, MslKeyExchangeException { final byte[] encryptionKeyA = Arrays.copyOf(ENCRYPTION_KEY, ENCRYPTION_KEY.length); final byte[] encryptionKeyB = Arrays.copyOf(ENCRYPTION_KEY, ENCRYPTION_KEY.length); ++encryptionKeyB[0]; final ResponseData dataA = new ResponseData(MASTER_TOKEN, KEYPAIR_ID, encryptionKeyA, HMAC_KEY); final ResponseData dataB = new ResponseData(MASTER_TOKEN, KEYPAIR_ID, encryptionKeyB, HMAC_KEY); final ResponseData dataA2 = new ResponseData(MASTER_TOKEN, dataA.getKeydata()); assertTrue(dataA.equals(dataA)); assertEquals(dataA.hashCode(), dataA.hashCode()); assertFalse(dataA.equals(dataB)); assertFalse(dataB.equals(dataA)); assertTrue(dataA.hashCode() != dataB.hashCode()); assertTrue(dataA.equals(dataA2)); assertTrue(dataA2.equals(dataA)); assertEquals(dataA.hashCode(), dataA2.hashCode()); } @Test public void equalsHmacKey() throws MslEncodingException, JSONException, MslKeyExchangeException { final byte[] hmacKeyA = Arrays.copyOf(HMAC_KEY, HMAC_KEY.length); final byte[] hmacKeyB = Arrays.copyOf(HMAC_KEY, HMAC_KEY.length); ++hmacKeyB[0]; final ResponseData dataA = new ResponseData(MASTER_TOKEN, KEYPAIR_ID, ENCRYPTION_KEY, hmacKeyA); final ResponseData dataB = new ResponseData(MASTER_TOKEN, KEYPAIR_ID, ENCRYPTION_KEY, hmacKeyB); final ResponseData dataA2 = new ResponseData(MASTER_TOKEN, dataA.getKeydata()); assertTrue(dataA.equals(dataA)); assertEquals(dataA.hashCode(), dataA.hashCode()); assertFalse(dataA.equals(dataB)); assertFalse(dataB.equals(dataA)); assertTrue(dataA.hashCode() != dataB.hashCode()); assertTrue(dataA.equals(dataA2)); assertTrue(dataA2.equals(dataA)); assertEquals(dataA.hashCode(), dataA2.hashCode()); } @Test public void equalsObject() { final ResponseData data = new ResponseData(MASTER_TOKEN, KEYPAIR_ID, ENCRYPTION_KEY, HMAC_KEY); assertFalse(data.equals(null)); assertFalse(data.equals(IDENTITY)); assertTrue(data.hashCode() != IDENTITY.hashCode()); } } /** Key exchange factory unit tests. */ public static class KeyExchangeFactoryTest { /** * Fake key request data for the asymmetric wrapped key exchange * scheme. */ private static class FakeKeyRequestData extends KeyRequestData { /** Create a new fake key request data. */ protected FakeKeyRequestData() { super(KeyExchangeScheme.ASYMMETRIC_WRAPPED); } /* (non-Javadoc) * @see com.netflix.msl.keyx.KeyRequestData#getKeydata() */ @Override protected JSONObject getKeydata() throws JSONException { return null; } } /** * Fake key response data for the asymmetric wrapped key exchange * scheme. */ private static class FakeKeyResponseData extends KeyResponseData { /** Create a new fake key response data. */ protected FakeKeyResponseData() { super(MASTER_TOKEN, KeyExchangeScheme.ASYMMETRIC_WRAPPED); } /* (non-Javadoc) * @see com.netflix.msl.keyx.KeyResponseData#getKeydata() */ @Override protected JSONObject getKeydata() { return null; } } /** * @param ctx MSL context. * @param encryptionKey master token encryption key. * @param hmacKey master token HMAC key. * @return a new master token. * @throws MslEncodingException if there is an error encoding the JSON * data. * @throws MslCryptoException if there is an error encrypting or signing * the token data. * @throws MslException if the master token is constructed incorrectly. * @throws JSONException if there is an error editing the JSON data. */ private static MasterToken getUntrustedMasterToken(final MslContext ctx, final SecretKey encryptionKey, final SecretKey hmacKey) throws MslEncodingException, MslCryptoException, JSONException, MslException { final Date renewalWindow = new Date(System.currentTimeMillis() + 1000); final Date expiration = new Date(System.currentTimeMillis() + 2000); final String identity = MockPresharedAuthenticationFactory.PSK_ESN; final MasterToken masterToken = new MasterToken(ctx, renewalWindow, expiration, 1L, 1L, null, identity, encryptionKey, hmacKey); final String json = masterToken.toJSONString(); final JSONObject jo = new JSONObject(json); final byte[] signature = DatatypeConverter.parseBase64Binary(jo.getString("signature")); ++signature[1]; jo.put("signature", DatatypeConverter.printBase64Binary(signature)); final MasterToken untrustedMasterToken = new MasterToken(ctx, jo); return untrustedMasterToken; } @Rule public ExpectedMslException thrown = ExpectedMslException.none(); @BeforeClass public static synchronized void setup() { Security.addProvider(new BouncyCastleProvider()); random = new Random(); authutils = new MockAuthenticationUtils(); factory = new AsymmetricWrappedExchange(authutils); } @AfterClass public static void teardown() { // Do not cleanup so the static instances are available to // subclasses. } @Before public void reset() { authutils.reset(); ctx.getMslStore().clearCryptoContexts(); ctx.getMslStore().clearServiceTokens(); } @RunWith(Parameterized.class) public static class Params { @Rule public ExpectedMslException thrown = ExpectedMslException.none(); @Parameters public static Collection<Object[]> data() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, MslEncodingException, MslCryptoException { AsymmetricWrappedExchangeSuite.setup(); return Arrays.asList(new Object[][] { { Mechanism.JWE_RSA, RSA_PUBLIC_KEY, RSA_PRIVATE_KEY }, { Mechanism.JWEJS_RSA, RSA_PUBLIC_KEY, RSA_PRIVATE_KEY }, { Mechanism.JWK_RSA, RSA_PUBLIC_KEY, RSA_PRIVATE_KEY }, { Mechanism.JWK_RSAES, RSA_PUBLIC_KEY, RSA_PRIVATE_KEY }, }); } /** Key exchange mechanism. */ private final Mechanism mechanism; /** Public key. */ private final PublicKey publicKey; /** Private key. */ private final PrivateKey privateKey; /** * Create a new request data test instance with the specified key * exchange parameters. * * @param mechanism key exchange mechanism. * @param publicKey public key. * @param privateKey private key. */ public Params(final Mechanism mechanism, final PublicKey publicKey, final PrivateKey privateKey) { this.mechanism = mechanism; this.publicKey = publicKey; this.privateKey = privateKey; } @Test public void generateInitialResponse() throws MslException, JSONException { final KeyRequestData keyRequestData = new RequestData(KEYPAIR_ID, mechanism, publicKey, privateKey); final KeyExchangeData keyxData = factory.generateResponse(ctx, keyRequestData, IDENTITY); assertNotNull(keyxData); assertNotNull(keyxData.cryptoContext); assertNotNull(keyxData.keyResponseData); final KeyResponseData keyResponseData = keyxData.keyResponseData; assertEquals(KeyExchangeScheme.ASYMMETRIC_WRAPPED, keyResponseData.getKeyExchangeScheme()); final MasterToken masterToken = keyResponseData.getMasterToken(); assertNotNull(masterToken); assertEquals(IDENTITY, masterToken.getIdentity()); } @Test public void generateSubsequentResponse() throws MslException { final KeyRequestData keyRequestData = new RequestData(KEYPAIR_ID, mechanism, publicKey, privateKey); final KeyExchangeData keyxData = factory.generateResponse(ctx, keyRequestData, MASTER_TOKEN); assertNotNull(keyxData); assertNotNull(keyxData.cryptoContext); assertNotNull(keyxData.keyResponseData); final KeyResponseData keyResponseData = keyxData.keyResponseData; assertEquals(KeyExchangeScheme.ASYMMETRIC_WRAPPED, keyResponseData.getKeyExchangeScheme()); final MasterToken masterToken = keyResponseData.getMasterToken(); assertNotNull(masterToken); assertEquals(MASTER_TOKEN.getIdentity(), masterToken.getIdentity()); assertEquals(MASTER_TOKEN.getSerialNumber(), masterToken.getSerialNumber()); assertEquals(MASTER_TOKEN.getSequenceNumber() + 1, masterToken.getSequenceNumber()); } @Test public void untrustedMasterTokenSubsequentResponse() throws MslEncodingException, MslCryptoException, JSONException, MslException { thrown.expect(MslMasterTokenException.class); final KeyRequestData keyRequestData = new RequestData(KEYPAIR_ID, mechanism, publicKey, privateKey); final SecretKey encryptionKey = MockPresharedAuthenticationFactory.KPE; final SecretKey hmacKey = MockPresharedAuthenticationFactory.KPH; final MasterToken masterToken = getUntrustedMasterToken(ctx, encryptionKey, hmacKey); factory.generateResponse(ctx, keyRequestData, masterToken); } @Test public void getCryptoContext() throws MslException { final KeyRequestData keyRequestData = new RequestData(KEYPAIR_ID, mechanism, publicKey, privateKey); final KeyExchangeData keyxData = factory.generateResponse(ctx, keyRequestData, IDENTITY); final ICryptoContext requestCryptoContext = keyxData.cryptoContext; final KeyResponseData keyResponseData = keyxData.keyResponseData; final ICryptoContext responseCryptoContext = factory.getCryptoContext(ctx, keyRequestData, keyResponseData, null); assertNotNull(responseCryptoContext); final byte[] data = new byte[32]; random.nextBytes(data); // Ciphertext won't always be equal depending on how it was // enveloped. So we cannot check for equality or inequality. final byte[] requestCiphertext = requestCryptoContext.encrypt(data); final byte[] responseCiphertext = responseCryptoContext.encrypt(data); assertFalse(Arrays.equals(data, requestCiphertext)); assertFalse(Arrays.equals(data, responseCiphertext)); // Signatures should always be equal. final byte[] requestSignature = requestCryptoContext.sign(data); final byte[] responseSignature = responseCryptoContext.sign(data); assertFalse(Arrays.equals(data, requestSignature)); assertFalse(Arrays.equals(data, responseSignature)); assertArrayEquals(requestSignature, responseSignature); // Plaintext should always be equal to the original message. final byte[] requestPlaintext = requestCryptoContext.decrypt(responseCiphertext); final byte[] responsePlaintext = responseCryptoContext.decrypt(requestCiphertext); assertNotNull(requestPlaintext); assertArrayEquals(data, requestPlaintext); assertArrayEquals(requestPlaintext, responsePlaintext); // Verification should always succeed. assertTrue(requestCryptoContext.verify(data, responseSignature)); assertTrue(responseCryptoContext.verify(data, requestSignature)); } @Test public void invalidWrappedEncryptionKeyCryptoContext() throws JSONException, MslException { thrown.expect(MslCryptoException.class); final KeyRequestData keyRequestData = new RequestData(KEYPAIR_ID, mechanism, publicKey, privateKey); final KeyExchangeData keyxData = factory.generateResponse(ctx, keyRequestData, IDENTITY); final KeyResponseData keyResponseData = keyxData.keyResponseData; final MasterToken masterToken = keyResponseData.getMasterToken(); final JSONObject keydata = keyResponseData.getKeydata(); final byte[] wrappedEncryptionKey = DatatypeConverter .parseBase64Binary(keydata.getString(KEY_ENCRYPTION_KEY)); // I think I have to change length - 2 because of padding. ++wrappedEncryptionKey[wrappedEncryptionKey.length - 2]; keydata.put(KEY_ENCRYPTION_KEY, DatatypeConverter.printBase64Binary(wrappedEncryptionKey)); final byte[] wrappedHmacKey = DatatypeConverter.parseBase64Binary(keydata.getString(KEY_HMAC_KEY)); final KeyResponseData invalidKeyResponseData = new ResponseData(masterToken, KEYPAIR_ID, wrappedEncryptionKey, wrappedHmacKey); factory.getCryptoContext(ctx, keyRequestData, invalidKeyResponseData, null); } @Test public void invalidWrappedHmacKeyCryptoContext() throws JSONException, MslException { thrown.expect(MslCryptoException.class); final KeyRequestData keyRequestData = new RequestData(KEYPAIR_ID, mechanism, publicKey, privateKey); final KeyExchangeData keyxData = factory.generateResponse(ctx, keyRequestData, IDENTITY); final KeyResponseData keyResponseData = keyxData.keyResponseData; final MasterToken masterToken = keyResponseData.getMasterToken(); final JSONObject keydata = keyResponseData.getKeydata(); final byte[] wrappedHmacKey = DatatypeConverter.parseBase64Binary(keydata.getString(KEY_HMAC_KEY)); // I think I have to change length - 2 because of padding. ++wrappedHmacKey[wrappedHmacKey.length - 2]; keydata.put(KEY_HMAC_KEY, DatatypeConverter.printBase64Binary(wrappedHmacKey)); final byte[] wrappedEncryptionKey = DatatypeConverter .parseBase64Binary(keydata.getString(KEY_ENCRYPTION_KEY)); final KeyResponseData invalidKeyResponseData = new ResponseData(masterToken, KEYPAIR_ID, wrappedEncryptionKey, wrappedHmacKey); factory.getCryptoContext(ctx, keyRequestData, invalidKeyResponseData, null); } } @Test public void factory() { assertEquals(KeyExchangeScheme.ASYMMETRIC_WRAPPED, factory.getScheme()); } @Test(expected = MslInternalException.class) public void wrongRequestInitialResponse() throws MslInternalException, MslException { final KeyRequestData keyRequestData = new FakeKeyRequestData(); factory.generateResponse(ctx, keyRequestData, IDENTITY); } @Test(expected = MslInternalException.class) public void wrongRequestSubsequentResponse() throws MslInternalException, MslException { final KeyRequestData keyRequestData = new FakeKeyRequestData(); factory.generateResponse(ctx, keyRequestData, MASTER_TOKEN); } @Test(expected = MslInternalException.class) public void wrongRequestCryptoContext() throws MslException { final KeyRequestData keyRequestData = new RequestData(KEYPAIR_ID, Mechanism.JWE_RSA, RSA_PUBLIC_KEY, RSA_PRIVATE_KEY); final KeyExchangeData keyxData = factory.generateResponse(ctx, keyRequestData, IDENTITY); final KeyResponseData keyResponseData = keyxData.keyResponseData; final KeyRequestData fakeKeyRequestData = new FakeKeyRequestData(); factory.getCryptoContext(ctx, fakeKeyRequestData, keyResponseData, null); } @Test(expected = MslInternalException.class) public void wrongResponseCryptoContext() throws MslKeyExchangeException, MslCryptoException, MslEncodingException, MslMasterTokenException, MslEntityAuthException { final KeyRequestData keyRequestData = new RequestData(KEYPAIR_ID, Mechanism.JWE_RSA, RSA_PUBLIC_KEY, RSA_PRIVATE_KEY); final KeyResponseData fakeKeyResponseData = new FakeKeyResponseData(); factory.getCryptoContext(ctx, keyRequestData, fakeKeyResponseData, null); } @Test public void keyIdMismatchCryptoContext() throws MslException { thrown.expect(MslKeyExchangeException.class); thrown.expectMslError(MslError.KEYX_RESPONSE_REQUEST_MISMATCH); final KeyRequestData keyRequestData = new RequestData(KEYPAIR_ID + "A", Mechanism.JWE_RSA, RSA_PUBLIC_KEY, RSA_PRIVATE_KEY); final KeyExchangeData keyxData = factory.generateResponse(ctx, keyRequestData, IDENTITY); final KeyResponseData keyResponseData = keyxData.keyResponseData; final MasterToken masterToken = keyResponseData.getMasterToken(); final KeyResponseData mismatchedKeyResponseData = new ResponseData(masterToken, KEYPAIR_ID + "B", ENCRYPTION_KEY, HMAC_KEY); factory.getCryptoContext(ctx, keyRequestData, mismatchedKeyResponseData, null); } @Test public void missingPrivateKeyCryptoContext() throws MslException { thrown.expect(MslKeyExchangeException.class); thrown.expectMslError(MslError.KEYX_PRIVATE_KEY_MISSING); final KeyRequestData keyRequestData = new RequestData(KEYPAIR_ID + "B", Mechanism.JWE_RSA, RSA_PUBLIC_KEY, null); final KeyExchangeData keyxData = factory.generateResponse(ctx, keyRequestData, IDENTITY); final KeyResponseData keyResponseData = keyxData.keyResponseData; factory.getCryptoContext(ctx, keyRequestData, keyResponseData, null); } /** Random. */ private static Random random; /** Authentication utilities. */ private static MockAuthenticationUtils authutils; /** Key exchange factory. */ private static KeyExchangeFactory factory; } }