<commit_before>/**
 * Copyright (c) 2013-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.crypto;

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.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;

import com.netflix.msl.MslCryptoException;
import com.netflix.msl.MslEncodingException;
import com.netflix.msl.MslError;
import com.netflix.msl.MslInternalException;
import com.netflix.msl.crypto.JsonWebKey.Algorithm;
import com.netflix.msl.crypto.JsonWebKey.KeyOp;
import com.netflix.msl.crypto.JsonWebKey.Type;
import com.netflix.msl.crypto.JsonWebKey.Usage;
import com.netflix.msl.test.ExpectedMslException;
import com.netflix.msl.util.JsonUtils;

/**
 * JSON web key unit tests.
 * 
 * @author <NAME> <<EMAIL>>
 */
public class JsonWebKeyTest {
    /** JSON key key type. */
    private static final String KEY_TYPE = "kty";
    /** JSON key usage. */
    private static final String KEY_USAGE = "use";
    /** JSON key key operations. */
    private static final String KEY_KEY_OPS = "key_ops";
    /** JSON key algorithm. */
    private static final String KEY_ALGORITHM = "alg";
    /** JSON key extractable. */
    private static final String KEY_EXTRACTABLE = "extractable";
    /** JSON key key ID. */
    private static final String KEY_KEY_ID = "kid";
    
    // RSA keys.
    /** JSON key modulus. */
    private static final String KEY_MODULUS = "n";
    /** JSON key public exponent. */
    private static final String KEY_PUBLIC_EXPONENT = "e";
    /** JSON key private exponent. */
    private static final String KEY_PRIVATE_EXPONENT = "d";
    
    // Symmetric keys.
    /** JSON key key. */
    private static final String KEY_KEY = "k";
    
    // Key operations.
    /** Encrypt/decrypt key operations. */
    private static final Set<KeyOp> ENCRYPT_DECRYPT = new HashSet<KeyOp>(Arrays.asList(KeyOp.encrypt, KeyOp.decrypt));
    /** Wrap/unwrap key operations. */
    private static final Set<KeyOp> WRAP_UNWRAP = new HashSet<KeyOp>(Arrays.asList(KeyOp.wrapKey, KeyOp.unwrapKey));
    /** Sign/verify key operations. */
    private static final Set<KeyOp> SIGN_VERIFY = new HashSet<KeyOp>(Arrays.asList(KeyOp.sign, KeyOp.verify));
    
    // Expected key operations JSON arrays.
    /** Sign/verify. */
    private static final JSONArray JA_SIGN_VERIFY = new JSONArray(Arrays.asList(KeyOp.sign.name(), KeyOp.verify.name()));
    /** Encrypt/decrypt. */
    private static final JSONArray JA_ENCRYPT_DECRYPT = new JSONArray(Arrays.asList(KeyOp.encrypt.name(), KeyOp.verify.name()));
    /** Wrap/unwrap. */
    private static final JSONArray JA_WRAP_UNWRAP = new JSONArray(Arrays.asList(KeyOp.wrapKey.name(), KeyOp.unwrapKey.name()));
    
    /** Null usage. */
    private static final Usage NULL_USAGE = null;
    /** Null key operations. */
    private static final Set<KeyOp> NULL_KEYOPS = null;
    
    /**
     * Returns the big integer in big-endian format without any leading sign
     * bits.
     * 
     * @param bi the big integer.
     * @return the big integer in big-endian form.
     */
    private static byte[] bi2bytes(final BigInteger bi) {
        final byte[] bib = bi.toByteArray();
        final int len = (int)Math.ceil((double)bi.bitLength() / Byte.SIZE);
        return Arrays.copyOfRange(bib, bib.length - len, bib.length);
    }
    
    private static final boolean EXTRACTABLE = true;
    private static final String KEY_ID = "kid";
    private static RSAPublicKey PUBLIC_KEY;
    private static RSAPrivateKey PRIVATE_KEY;
    private static SecretKey SECRET_KEY;
    
    private static final Random random = new Random();
    
    @Rule
    public ExpectedMslException thrown = ExpectedMslException.none();
    
    @BeforeClass
    public static void setup() throws NoSuchAlgorithmException {
        Security.addProvider(new BouncyCastleProvider());
        
        final KeyPairGenerator keypairGenerator = KeyPairGenerator.getInstance("RSA");
        keypairGenerator.initialize(512);
        final KeyPair keypair = keypairGenerator.generateKeyPair();
        PRIVATE_KEY = (RSAPrivateKey)keypair.getPrivate();
        PUBLIC_KEY = (RSAPublicKey)keypair.getPublic();
        
        final byte[] keydata = new byte[16];
        random.nextBytes(keydata);
        SECRET_KEY = new SecretKeySpec(keydata, JcaAlgorithm.AES);
    }
    
    @Test
    public void rsaUsageCtor() throws MslCryptoException, MslEncodingException, JSONException {
        final JsonWebKey jwk = new JsonWebKey(Usage.sig, Algorithm.RSA1_5, EXTRACTABLE, KEY_ID, PUBLIC_KEY, PRIVATE_KEY);
        assertEquals(EXTRACTABLE, jwk.isExtractable());
        assertEquals(Algorithm.RSA1_5, jwk.getAlgorithm());
        assertEquals(KEY_ID, jwk.getId());
        final KeyPair keypair = jwk.getRsaKeyPair();
        assertNotNull(keypair);
        final RSAPublicKey pubkey = (RSAPublicKey)keypair.getPublic();
        assertEquals(PUBLIC_KEY.getModulus(), pubkey.getModulus());
        assertEquals(PUBLIC_KEY.getPublicExponent(), pubkey.getPublicExponent());
        final RSAPrivateKey privkey = (RSAPrivateKey)keypair.getPrivate();
        assertEquals(PRIVATE_KEY.getModulus(), privkey.getModulus());
        assertEquals(PRIVATE_KEY.getPrivateExponent(), privkey.getPrivateExponent());
        assertNull(jwk.getSecretKey());
        assertEquals(Type.rsa, jwk.getType());
        assertEquals(Usage.sig, jwk.getUsage());
        assertNull(jwk.getKeyOps());
        final String json = jwk.toJSONString();
        assertNotNull(json);
        
        final JsonWebKey joJwk = new JsonWebKey(new JSONObject(json));
        assertEquals(jwk.isExtractable(), joJwk.isExtractable());
        assertEquals(jwk.getAlgorithm(), joJwk.getAlgorithm());
        assertEquals(jwk.getId(), joJwk.getId());
        final KeyPair joKeypair = joJwk.getRsaKeyPair();
        assertNotNull(joKeypair);
        final RSAPublicKey joPubkey = (RSAPublicKey)joKeypair.getPublic();
        assertEquals(pubkey.getModulus(), joPubkey.getModulus());
        assertEquals(pubkey.getPublicExponent(), joPubkey.getPublicExponent());
        final RSAPrivateKey joPrivkey = (RSAPrivateKey)joKeypair.getPrivate();
        assertEquals(privkey.getModulus(), joPrivkey.getModulus());
        assertEquals(privkey.getPrivateExponent(), joPrivkey.getPrivateExponent());
        assertNull(joJwk.getSecretKey());
        assertEquals(jwk.getType(), joJwk.getType());
        assertEquals(jwk.getUsage(), joJwk.getUsage());
        assertEquals(jwk.getKeyOps(), joJwk.getKeyOps());
        final String joJson = joJwk.toJSONString();
        assertNotNull(joJson);
        assertEquals(json, joJson);
    }
    
    @Test
    public void rsaKeyOpsCtor() throws MslCryptoException, MslEncodingException, JSONException {
        final JsonWebKey jwk = new JsonWebKey(SIGN_VERIFY, Algorithm.RSA1_5, EXTRACTABLE, KEY_ID, PUBLIC_KEY, PRIVATE_KEY);
        assertEquals(EXTRACTABLE, jwk.isExtractable());
        assertEquals(Algorithm.RSA1_5, jwk.getAlgorithm());
        assertEquals(KEY_ID, jwk.getId());
        final KeyPair keypair = jwk.getRsaKeyPair();
        assertNotNull(keypair);
        final RSAPublicKey pubkey = (RSAPublicKey)keypair.getPublic();
        assertEquals(PUBLIC_KEY.getModulus(), pubkey.getModulus());
        assertEquals(PUBLIC_KEY.getPublicExponent(), pubkey.getPublicExponent());
        final RSAPrivateKey privkey = (RSAPrivateKey)keypair.getPrivate();
        assertEquals(PRIVATE_KEY.getModulus(), privkey.getModulus());
        assertEquals(PRIVATE_KEY.getPrivateExponent(), privkey.getPrivateExponent());
        assertNull(jwk.getSecretKey());
        assertEquals(Type.rsa, jwk.getType());
        assertNull(jwk.getUsage());
        assertEquals(SIGN_VERIFY, jwk.getKeyOps());
        final String json = jwk.toJSONString();
        assertNotNull(json);
        
        final JsonWebKey joJwk = new JsonWebKey(new JSONObject(json));
        assertEquals(jwk.isExtractable(), joJwk.isExtractable());
        assertEquals(jwk.getAlgorithm(), joJwk.getAlgorithm());
        assertEquals(jwk.getId(), joJwk.getId());
        final KeyPair joKeypair = joJwk.getRsaKeyPair();
        assertNotNull(joKeypair);
        final RSAPublicKey joPubkey = (RSAPublicKey)joKeypair.getPublic();
        assertEquals(pubkey.getModulus(), joPubkey.getModulus());
        assertEquals(pubkey.getPublicExponent(), joPubkey.getPublicExponent());
        final RSAPrivateKey joPrivkey = (RSAPrivateKey)joKeypair.getPrivate();
        assertEquals(privkey.getModulus(), joPrivkey.getModulus());
        assertEquals(privkey.getPrivateExponent(), joPrivkey.getPrivateExponent());
        assertNull(joJwk.getSecretKey());
        assertEquals(jwk.getType(), joJwk.getType());
        assertEquals(jwk.getUsage(), joJwk.getUsage());
        assertEquals(jwk.getKeyOps(), joJwk.getKeyOps());
        final String joJson = joJwk.toJSONString();
        assertNotNull(joJson);
        // This test will not always pass since the key operations are
        // unordered.
        //assertEquals(json, joJson);
    }
    
    @Test
    public void rsaUsageJson() throws JSONException {
        final JsonWebKey jwk = new JsonWebKey(Usage.sig, Algorithm.RSA1_5, EXTRACTABLE, KEY_ID, PUBLIC_KEY, PRIVATE_KEY);
        final JSONObject jo = new JSONObject(jwk.toJSONString());
        
        assertEquals(EXTRACTABLE, jo.optBoolean(KEY_EXTRACTABLE));
        assertEquals(Algorithm.RSA1_5.name(), jo.getString(KEY_ALGORITHM));
        assertEquals(KEY_ID, jo.getString(KEY_KEY_ID));
        assertEquals(Type.rsa.name(), jo.getString(KEY_TYPE));
        assertEquals(Usage.sig.name(), jo.getString(KEY_USAGE));
        assertFalse(jo.has(KEY_KEY_OPS));
        
        final String modulus = JsonUtils.b64urlEncode(bi2bytes(PUBLIC_KEY.getModulus()));
        final String pubexp = JsonUtils.b64urlEncode(bi2bytes(PUBLIC_KEY.getPublicExponent()));
        final String privexp = JsonUtils.b64urlEncode(bi2bytes(PRIVATE_KEY.getPrivateExponent()));
        
        assertEquals(modulus, jo.getString(KEY_MODULUS));
        assertEquals(pubexp, jo.getString(KEY_PUBLIC_EXPONENT));
        assertEquals(privexp, jo.getString(KEY_PRIVATE_EXPONENT));
        
        assertFalse(jo.has(KEY_KEY));
    }
    
    @Test
    public void rsaKeyOpsJson() throws JSONException {
        final JsonWebKey jwk = new JsonWebKey(SIGN_VERIFY, Algorithm.RSA1_5, EXTRACTABLE, KEY_ID, PUBLIC_KEY, PRIVATE_KEY);
        final JSONObject jo = new JSONObject(jwk.toJSONString());
        
        assertEquals(EXTRACTABLE, jo.optBoolean(KEY_EXTRACTABLE));
        assertEquals(Algorithm.RSA1_5.name(), jo.getString(KEY_ALGORITHM));
        assertEquals(KEY_ID, jo.getString(KEY_KEY_ID));
        assertEquals(Type.rsa.name(), jo.getString(KEY_TYPE));
        assertFalse(jo.has(KEY_USAGE));
        assertTrue(JsonUtils.equalSets(JA_SIGN_VERIFY, jo.getJSONArray(KEY_KEY_OPS)));
        
        final String modulus = JsonUtils.b64urlEncode(bi2bytes(PUBLIC_KEY.getModulus()));
        final String pubexp = JsonUtils.b64urlEncode(bi2bytes(PUBLIC_KEY.getPublicExponent()));
        final String privexp = JsonUtils.b64urlEncode(bi2bytes(PRIVATE_KEY.getPrivateExponent()));
        
        assertEquals(modulus, jo.getString(KEY_MODULUS));
        assertEquals(pubexp, jo.getString(KEY_PUBLIC_EXPONENT));
        assertEquals(privexp, jo.getString(KEY_PRIVATE_EXPONENT));
        
        assertFalse(jo.has(KEY_KEY));
    }
    
    @Test
    public void rsaNullCtorPublic() throws MslCryptoException, MslEncodingException, JSONException {
        final JsonWebKey jwk = new JsonWebKey(NULL_USAGE, null, false, null, PUBLIC_KEY, null);
        assertFalse(jwk.isExtractable());
        assertNull(jwk.getAlgorithm());
        assertNull(jwk.getId());
        final KeyPair keypair = jwk.getRsaKeyPair();
        assertNotNull(keypair);
        final RSAPublicKey pubkey = (RSAPublicKey)keypair.getPublic();
        assertEquals(PUBLIC_KEY.getModulus(), pubkey.getModulus());
        assertEquals(PUBLIC_KEY.getPublicExponent(), pubkey.getPublicExponent());
        final RSAPrivateKey privkey = (RSAPrivateKey)keypair.getPrivate();
        assertNull(privkey);
        assertNull(jwk.getSecretKey());
        assertEquals(Type.rsa, jwk.getType());
        assertNull(jwk.getUsage());
        assertNull(jwk.getKeyOps());
        final String json = jwk.toJSONString();
        assertNotNull(json);
        
        final JsonWebKey joJwk = new JsonWebKey(new JSONObject(json));
        assertEquals(jwk.isExtractable(), joJwk.isExtractable());
        assertEquals(jwk.getAlgorithm(), joJwk.getAlgorithm());
        assertEquals(jwk.getId(), joJwk.getId());
        final KeyPair joKeypair = joJwk.getRsaKeyPair();
        assertNotNull(joKeypair);
        final RSAPublicKey joPubkey = (RSAPublicKey)joKeypair.getPublic();
        assertEquals(pubkey.getModulus(), joPubkey.getModulus());
        assertEquals(pubkey.getPublicExponent(), joPubkey.getPublicExponent());
        final RSAPrivateKey joPrivkey = (RSAPrivateKey)joKeypair.getPrivate();
        assertNull(joPrivkey);
        assertNull(joJwk.getSecretKey());
        assertEquals(jwk.getType(), joJwk.getType());
        assertEquals(jwk.getUsage(), joJwk.getUsage());
        assertEquals(jwk.getKeyOps(), joJwk.getKeyOps());
        final String joJson = joJwk.toJSONString();
        assertNotNull(joJson);
        assertEquals(json, joJson);
    }
    
    @Test
    public void rsaNullCtorPrivate() throws MslCryptoException, MslEncodingException, JSONException {
        final JsonWebKey jwk = new JsonWebKey(NULL_USAGE, null, false, null, null, PRIVATE_KEY);
        assertFalse(jwk.isExtractable());
        assertNull(jwk.getAlgorithm());
        assertNull(jwk.getId());
        final KeyPair keypair = jwk.getRsaKeyPair();
        assertNotNull(keypair);
        final RSAPublicKey pubkey = (RSAPublicKey)keypair.getPublic();
        assertNull(pubkey);
        final RSAPrivateKey privkey = (RSAPrivateKey)keypair.getPrivate();
        assertEquals(PRIVATE_KEY.getModulus(), privkey.getModulus());
        assertEquals(PRIVATE_KEY.getPrivateExponent(), privkey.getPrivateExponent());
        assertNull(jwk.getSecretKey());
        assertEquals(Type.rsa, jwk.getType());
        assertNull(jwk.getUsage());
        assertNull(jwk.getKeyOps());
        final String json = jwk.toJSONString();
        assertNotNull(json);
        
        final JsonWebKey joJwk = new JsonWebKey(new JSONObject(json));
        assertEquals(jwk.isExtractable(), joJwk.isExtractable());
        assertEquals(jwk.getAlgorithm(), joJwk.getAlgorithm());
        assertEquals(jwk.getId(), joJwk.getId());
        final KeyPair joKeypair = joJwk.getRsaKeyPair();
        assertNotNull(joKeypair);
        final RSAPublicKey joPubkey = (RSAPublicKey)joKeypair.getPublic();
        assertNull(joPubkey);
        final RSAPrivateKey joPrivkey = (RSAPrivateKey)joKeypair.getPrivate();
        assertEquals(privkey.getModulus(), joPrivkey.getModulus());
        assertEquals(privkey.getPrivateExponent(), joPrivkey.getPrivateExponent());
        assertNull(joJwk.getSecretKey());
        assertEquals(jwk.getType(), joJwk.getType());
        assertEquals(jwk.getUsage(), joJwk.getUsage());
        assertEquals(jwk.getKeyOps(), joJwk.getKeyOps());
        final String joJson = joJwk.toJSONString();
        assertNotNull(joJson);
        assertEquals(json, joJson);
    }
    
    @Test
    public void rsaNullJsonPublic() throws JSONException {
        final JsonWebKey jwk = new JsonWebKey(NULL_USAGE, null, false, null, PUBLIC_KEY, null);
        final String json = jwk.toJSONString();
        final JSONObject jo = new JSONObject(json);
        
        assertFalse(jo.getBoolean(KEY_EXTRACTABLE));
        assertFalse(jo.has(KEY_ALGORITHM));
        assertFalse(jo.has(KEY_KEY_ID));
        assertEquals(Type.rsa.name(), jo.getString(KEY_TYPE));
        assertFalse(jo.has(KEY_USAGE));
        assertFalse(jo.has(KEY_KEY_OPS));
        
        final String modulus = JsonUtils.b64urlEncode(bi2bytes(PUBLIC_KEY.getModulus()));
        final String pubexp = JsonUtils.b64urlEncode(bi2bytes(PUBLIC_KEY.getPublicExponent()));
        
        assertEquals(modulus, jo.getString(KEY_MODULUS));
        assertEquals(pubexp, jo.getString(KEY_PUBLIC_EXPONENT));
        assertFalse(jo.has(KEY_PRIVATE_EXPONENT));

        assertFalse(jo.has(KEY_KEY));
    }
    
    @Test
    public void rsaNullJsonPrivate() throws JSONException {
        final JsonWebKey jwk = new JsonWebKey(NULL_USAGE, null, false, null, null, PRIVATE_KEY);
        final JSONObject jo = new JSONObject(jwk.toJSONString());
        
        assertFalse(jo.getBoolean(KEY_EXTRACTABLE));
        assertFalse(jo.has(KEY_ALGORITHM));
        assertFalse(jo.has(KEY_KEY_ID));
        assertEquals(Type.rsa.name(), jo.getString(KEY_TYPE));
        assertFalse(jo.has(KEY_USAGE));
        assertFalse(jo.has(KEY_KEY_OPS));
        
        final String modulus = JsonUtils.b64urlEncode(bi2bytes(PUBLIC_KEY.getModulus()));
        final String privexp = JsonUtils.b64urlEncode(bi2bytes(PRIVATE_KEY.getPrivateExponent()));
        
        assertEquals(modulus, jo.getString(KEY_MODULUS));
        assertFalse(jo.has(KEY_PUBLIC_EXPONENT));
        assertEquals(privexp, jo.getString(KEY_PRIVATE_EXPONENT));

        assertFalse(jo.has(KEY_KEY));
    }
    
    @Test(expected = MslInternalException.class)
    public void rsaCtorNullKeys() {
        new JsonWebKey(NULL_USAGE, null, false, null, null, null);
    }
    
    @Test(expected = MslInternalException.class)
    public void rsaCtorMismatchedAlgorithm() {
        new JsonWebKey(NULL_USAGE, Algorithm.A128CBC, false, null, PUBLIC_KEY, PRIVATE_KEY);
    }
    
    @Test
    public void octUsageCtor() throws MslCryptoException, MslEncodingException, JSONException {
        final JsonWebKey jwk = new JsonWebKey(Usage.enc, Algorithm.A128CBC, EXTRACTABLE, KEY_ID, SECRET_KEY);
        assertEquals(EXTRACTABLE, jwk.isExtractable());
        assertEquals(Algorithm.A128CBC, jwk.getAlgorithm());
        assertEquals(KEY_ID, jwk.getId());
        assertNull(jwk.getRsaKeyPair());
        assertArrayEquals(SECRET_KEY.getEncoded(), jwk.getSecretKey().getEncoded());
        assertEquals(Type.oct, jwk.getType());
        assertEquals(Usage.enc, jwk.getUsage());
        assertNull(jwk.getKeyOps());
        final String json = jwk.toJSONString();
        assertNotNull(json);
        
        final JsonWebKey joJwk = new JsonWebKey(new JSONObject(json));
        assertEquals(jwk.isExtractable(), joJwk.isExtractable());
        assertEquals(jwk.getAlgorithm(), joJwk.getAlgorithm());
        assertEquals(jwk.getId(), joJwk.getId());
        assertNull(joJwk.getRsaKeyPair());
        assertArrayEquals(jwk.getSecretKey().getEncoded(), joJwk.getSecretKey().getEncoded());
        assertEquals(jwk.getType(), joJwk.getType());
        assertEquals(jwk.getUsage(), joJwk.getUsage());
        assertEquals(jwk.getKeyOps(), joJwk.getKeyOps());
        final String joJson = joJwk.toJSONString();
        assertNotNull(joJson);
        assertEquals(json, joJson);
    }
    
    @Test
    public void octKeyOpsCtor() throws MslCryptoException, MslEncodingException, JSONException {
        final JsonWebKey jwk = new JsonWebKey(ENCRYPT_DECRYPT, Algorithm.A128CBC, EXTRACTABLE, KEY_ID, SECRET_KEY);
        assertEquals(EXTRACTABLE, jwk.isExtractable());
        assertEquals(Algorithm.A128CBC, jwk.getAlgorithm());
        assertEquals(KEY_ID, jwk.getId());
        assertNull(jwk.getRsaKeyPair());
        assertArrayEquals(SECRET_KEY.getEncoded(), jwk.getSecretKey().getEncoded());
        assertEquals(Type.oct, jwk.getType());
        assertNull(jwk.getUsage());
        assertEquals(ENCRYPT_DECRYPT, jwk.getKeyOps());
        final String json = jwk.toJSONString();
        assertNotNull(json);
        
        final JsonWebKey joJwk = new JsonWebKey(new JSONObject(json));
        assertEquals(jwk.isExtractable(), joJwk.isExtractable());
        assertEquals(jwk.getAlgorithm(), joJwk.getAlgorithm());
        assertEquals(jwk.getId(), joJwk.getId());
        assertNull(joJwk.getRsaKeyPair());
        assertArrayEquals(jwk.getSecretKey().getEncoded(), joJwk.getSecretKey().getEncoded());
        assertEquals(jwk.getType(), joJwk.getType());
        assertEquals(jwk.getUsage(), joJwk.getUsage());
        assertEquals(jwk.getKeyOps(), joJwk.getKeyOps());
        final String joJson = joJwk.toJSONString();
        assertNotNull(joJson);
        // This test will not always pass since the key operations are
        // unordered.
        //assertEquals(json, joJson);
    }
    
    @Test
    public void octUsageJson() throws JSONException {
        final JsonWebKey jwk = new JsonWebKey(Usage.wrap, Algorithm.A128KW, EXTRACTABLE, KEY_ID, SECRET_KEY);
        final JSONObject jo = new JSONObject(jwk.toJSONString());
        
        assertEquals(EXTRACTABLE, jo.optBoolean(KEY_EXTRACTABLE));
        assertEquals(Algorithm.A128KW.name(), jo.getString(KEY_ALGORITHM));
        assertEquals(KEY_ID, jo.getString(KEY_KEY_ID));
        assertEquals(Type.oct.name(), jo.getString(KEY_TYPE));
        assertEquals(Usage.wrap.name(), jo.getString(KEY_USAGE));
        assertFalse(jo.has(KEY_KEY_OPS));
        
        assertFalse(jo.has(KEY_MODULUS));
        assertFalse(jo.has(KEY_PUBLIC_EXPONENT));
        assertFalse(jo.has(KEY_PRIVATE_EXPONENT));
        
        final String key = JsonUtils.b64urlEncode(SECRET_KEY.getEncoded());
        
        assertEquals(key, jo.getString(KEY_KEY));
    }
    
    @Test
    public void octKeyOpsJson() throws JSONException {
        final JsonWebKey jwk = new JsonWebKey(WRAP_UNWRAP, Algorithm.A128KW, EXTRACTABLE, KEY_ID, SECRET_KEY);
        final JSONObject jo = new JSONObject(jwk.toJSONString());
        
        assertEquals(EXTRACTABLE, jo.optBoolean(KEY_EXTRACTABLE));
        assertEquals(Algorithm.A128KW.name(), jo.getString(KEY_ALGORITHM));
        assertEquals(KEY_ID, jo.getString(KEY_KEY_ID));
        assertEquals(Type.oct.name(), jo.getString(KEY_TYPE));
        assertFalse(jo.has(KEY_USAGE));
        assertTrue(JsonUtils.equalSets(JA_WRAP_UNWRAP, jo.getJSONArray(KEY_KEY_OPS)));
        
        assertFalse(jo.has(KEY_MODULUS));
        assertFalse(jo.has(KEY_PUBLIC_EXPONENT));
        assertFalse(jo.has(KEY_PRIVATE_EXPONENT));
        
        final String key = JsonUtils.b64urlEncode(SECRET_KEY.getEncoded());
        
        assertEquals(key, jo.getString(KEY_KEY));
    }
    
    @Test
    public void octNullCtor() throws MslCryptoException, MslEncodingException, JSONException {
        final JsonWebKey jwk = new JsonWebKey(NULL_USAGE, null, false, null, SECRET_KEY);
        assertFalse(jwk.isExtractable());
        assertNull(jwk.getAlgorithm());
        assertNull(jwk.getId());
        assertNull(jwk.getRsaKeyPair());
        assertArrayEquals(SECRET_KEY.getEncoded(), jwk.getSecretKey().getEncoded());
        assertEquals(Type.oct, jwk.getType());
        assertNull(jwk.getUsage());
        assertNull(jwk.getKeyOps());
        final String json = jwk.toJSONString();
        assertNotNull(json);
        
        final JsonWebKey joJwk = new JsonWebKey(new JSONObject(json));
        assertEquals(jwk.isExtractable(), joJwk.isExtractable());
        assertEquals(jwk.getAlgorithm(), joJwk.getAlgorithm());
        assertEquals(jwk.getId(), joJwk.getId());
        assertNull(joJwk.getRsaKeyPair());
        assertArrayEquals(jwk.getSecretKey(SECRET_KEY.getAlgorithm()).getEncoded(), joJwk.getSecretKey(SECRET_KEY.getAlgorithm()).getEncoded());
        assertEquals(jwk.getType(), joJwk.getType());
        assertEquals(jwk.getUsage(), joJwk.getUsage());
        assertEquals(jwk.getKeyOps(), joJwk.getKeyOps());
        final String joJson = joJwk.toJSONString();
        assertNotNull(joJson);
        assertEquals(json, joJson);
    }
    
    @Test
    public void octNullJson() throws JSONException {
        final JsonWebKey jwk = new JsonWebKey(NULL_USAGE, null, false, null, SECRET_KEY);
        final JSONObject jo = new JSONObject(jwk.toJSONString());
        
        assertFalse(jo.getBoolean(KEY_EXTRACTABLE));
        assertFalse(jo.has(KEY_ALGORITHM));
        assertFalse(jo.has(KEY_KEY_ID));
        assertEquals(Type.oct.name(), jo.getString(KEY_TYPE));
        assertFalse(jo.has(KEY_USAGE));
        assertFalse(jo.has(KEY_KEY_OPS));
        
        assertFalse(jo.has(KEY_MODULUS));
        assertFalse(jo.has(KEY_PUBLIC_EXPONENT));
        assertFalse(jo.has(KEY_PRIVATE_EXPONENT));
        
        final String key = JsonUtils.b64urlEncode(SECRET_KEY.getEncoded());
        
        assertEquals(key, jo.getString(KEY_KEY));
    }
    
    public void usageOnly() throws JSONException, MslCryptoException, MslEncodingException {
        final JsonWebKey jwk = new JsonWebKey(NULL_USAGE, null, false, null, SECRET_KEY);
        final JSONObject jo = new JSONObject(jwk.toJSONString());
        
        jo.put(KEY_USAGE, Usage.enc.name());
        
        final JsonWebKey joJwk = new JsonWebKey(jo);
        assertEquals(Usage.enc, joJwk.getUsage());
        assertNull(joJwk.getKeyOps());
    }
    
    public void keyOpsOnly() throws JSONException, MslCryptoException, MslEncodingException {
        final JsonWebKey jwk = new JsonWebKey(NULL_KEYOPS, null, false, null, SECRET_KEY);
        final JSONObject jo = new JSONObject(jwk.toJSONString());
        
        jo.put(KEY_KEY_OPS, JA_ENCRYPT_DECRYPT);
        
        final JsonWebKey joJwk = new JsonWebKey(jo);
        assertNull(joJwk.getUsage());
        assertEquals(new HashSet<KeyOp>(Arrays.asList(KeyOp.encrypt, KeyOp.decrypt)), joJwk.getKeyOps());
    }
    
    @Test(expected = MslInternalException.class)
    public void octCtorMismatchedAlgo() {
        new JsonWebKey(NULL_USAGE, Algorithm.RSA1_5, false, null, SECRET_KEY);
    }
    
    @Test
    public void missingType() throws JSONException, MslCryptoException, MslEncodingException {
        thrown.expect(MslEncodingException.class);
        thrown.expectMslError(MslError.JSON_PARSE_ERROR);

        final JsonWebKey jwk = new JsonWebKey(NULL_USAGE, null, false, null, SECRET_KEY);
        final JSONObject jo = new JSONObject(jwk.toJSONString());
        
        jo.remove(KEY_TYPE);
        
        new JsonWebKey(jo);
    }
    
    @Test
    public void invalidType() throws MslCryptoException, MslEncodingException, JSONException {
        thrown.expect(MslCryptoException.class);
        thrown.expectMslError(MslError.UNIDENTIFIED_JWK_TYPE);

        final JsonWebKey jwk = new JsonWebKey(NULL_USAGE, null, false, null, SECRET_KEY);
        final JSONObject jo = new JSONObject(jwk.toJSONString());
        
        jo.put(KEY_TYPE, "x");
        
        new JsonWebKey(jo);
    }
    
    @Test
    public void invalidUsage() throws MslCryptoException, MslEncodingException, JSONException {
        thrown.expect(MslCryptoException.class);
        thrown.expectMslError(MslError.UNIDENTIFIED_JWK_USAGE);

        final JsonWebKey jwk = new JsonWebKey(NULL_USAGE, null, false, null, SECRET_KEY);
        final JSONObject jo = new JSONObject(jwk.toJSONString());
        
        jo.put(KEY_USAGE, "x");
        
        new JsonWebKey(jo);
    }
    
    @Test
    public void invalidKeyOp() throws JSONException, MslCryptoException, MslEncodingException {
        thrown.expect(MslCryptoException.class);
        thrown.expectMslError(MslError.UNIDENTIFIED_JWK_KEYOP);

        final JsonWebKey jwk = new JsonWebKey(NULL_KEYOPS, null, false, null, SECRET_KEY);
        final JSONObject jo = new JSONObject(jwk.toJSONString());
        
        jo.put(KEY_KEY_OPS, new JSONArray(Arrays.asList(KeyOp.encrypt.name(), "x", KeyOp.decrypt.name())));
        
        new JsonWebKey(jo);
    }
    
    @Test
    public void invalidAlgorithm() throws MslCryptoException, MslEncodingException, JSONException {
        thrown.expect(MslCryptoException.class);
        thrown.expectMslError(MslError.UNIDENTIFIED_JWK_ALGORITHM);

        final JsonWebKey jwk = new JsonWebKey(NULL_USAGE, null, false, null, SECRET_KEY);
        final JSONObject jo = new JSONObject(jwk.toJSONString());
        
        jo.put(KEY_ALGORITHM, "x");
        <commit_msg>Force use of JSONArray(Object array) since JSONArray(Collection) isn't getting used anymore for some reason.<commit_after>/**
 * Copyright (c) 2013-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.crypto;

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.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;

import com.netflix.msl.MslCryptoException;
import com.netflix.msl.MslEncodingException;
import com.netflix.msl.MslError;
import com.netflix.msl.MslInternalException;
import com.netflix.msl.crypto.JsonWebKey.Algorithm;
import com.netflix.msl.crypto.JsonWebKey.KeyOp;
import com.netflix.msl.crypto.JsonWebKey.Type;
import com.netflix.msl.crypto.JsonWebKey.Usage;
import com.netflix.msl.test.ExpectedMslException;
import com.netflix.msl.util.JsonUtils;

/**
 * JSON web key unit tests.
 * 
 * @author <NAME> <<EMAIL>>
 */
public class JsonWebKeyTest {
    /** JSON key key type. */
    private static final String KEY_TYPE = "kty";
    /** JSON key usage. */
    private static final String KEY_USAGE = "use";
    /** JSON key key operations. */
    private static final String KEY_KEY_OPS = "key_ops";
    /** JSON key algorithm. */
    private static final String KEY_ALGORITHM = "alg";
    /** JSON key extractable. */
    private static final String KEY_EXTRACTABLE = "extractable";
    /** JSON key key ID. */
    private static final String KEY_KEY_ID = "kid";
    
    // RSA keys.
    /** JSON key modulus. */
    private static final String KEY_MODULUS = "n";
    /** JSON key public exponent. */
    private static final String KEY_PUBLIC_EXPONENT = "e";
    /** JSON key private exponent. */
    private static final String KEY_PRIVATE_EXPONENT = "d";
    
    // Symmetric keys.
    /** JSON key key. */
    private static final String KEY_KEY = "k";
    
    // Key operations.
    /** Encrypt/decrypt key operations. */
    private static final Set<KeyOp> ENCRYPT_DECRYPT = new HashSet<KeyOp>(Arrays.asList(KeyOp.encrypt, KeyOp.decrypt));
    /** Wrap/unwrap key operations. */
    private static final Set<KeyOp> WRAP_UNWRAP = new HashSet<KeyOp>(Arrays.asList(KeyOp.wrapKey, KeyOp.unwrapKey));
    /** Sign/verify key operations. */
    private static final Set<KeyOp> SIGN_VERIFY = new HashSet<KeyOp>(Arrays.asList(KeyOp.sign, KeyOp.verify));
    
    // Expected key operations JSON arrays.
    /** Sign/verify. */
    private static final JSONArray JA_SIGN_VERIFY = new JSONArray(Arrays.asList(KeyOp.sign.name(), KeyOp.verify.name()).toArray());
    /** Encrypt/decrypt. */
    private static final JSONArray JA_ENCRYPT_DECRYPT = new JSONArray(Arrays.asList(KeyOp.encrypt.name(), KeyOp.verify.name()).toArray());
    /** Wrap/unwrap. */
    private static final JSONArray JA_WRAP_UNWRAP = new JSONArray(Arrays.asList(KeyOp.wrapKey.name(), KeyOp.unwrapKey.name()).toArray());
    
    /** Null usage. */
    private static final Usage NULL_USAGE = null;
    /** Null key operations. */
    private static final Set<KeyOp> NULL_KEYOPS = null;
    
    /**
     * Returns the big integer in big-endian format without any leading sign
     * bits.
     * 
     * @param bi the big integer.
     * @return the big integer in big-endian form.
     */
    private static byte[] bi2bytes(final BigInteger bi) {
        final byte[] bib = bi.toByteArray();
        final int len = (int)Math.ceil((double)bi.bitLength() / Byte.SIZE);
        return Arrays.copyOfRange(bib, bib.length - len, bib.length);
    }
    
    private static final boolean EXTRACTABLE = true;
    private static final String KEY_ID = "kid";
    private static RSAPublicKey PUBLIC_KEY;
    private static RSAPrivateKey PRIVATE_KEY;
    private static SecretKey SECRET_KEY;
    
    private static final Random random = new Random();
    
    @Rule
    public ExpectedMslException thrown = ExpectedMslException.none();
    
    @BeforeClass
    public static void setup() throws NoSuchAlgorithmException {
        Security.addProvider(new BouncyCastleProvider());
        
        final KeyPairGenerator keypairGenerator = KeyPairGenerator.getInstance("RSA");
        keypairGenerator.initialize(512);
        final KeyPair keypair = keypairGenerator.generateKeyPair();
        PRIVATE_KEY = (RSAPrivateKey)keypair.getPrivate();
        PUBLIC_KEY = (RSAPublicKey)keypair.getPublic();
        
        final byte[] keydata = new byte[16];
        random.nextBytes(keydata);
        SECRET_KEY = new SecretKeySpec(keydata, JcaAlgorithm.AES);
    }
    
    @Test
    public void rsaUsageCtor() throws MslCryptoException, MslEncodingException, JSONException {
        final JsonWebKey jwk = new JsonWebKey(Usage.sig, Algorithm.RSA1_5, EXTRACTABLE, KEY_ID, PUBLIC_KEY, PRIVATE_KEY);
        assertEquals(EXTRACTABLE, jwk.isExtractable());
        assertEquals(Algorithm.RSA1_5, jwk.getAlgorithm());
        assertEquals(KEY_ID, jwk.getId());
        final KeyPair keypair = jwk.getRsaKeyPair();
        assertNotNull(keypair);
        final RSAPublicKey pubkey = (RSAPublicKey)keypair.getPublic();
        assertEquals(PUBLIC_KEY.getModulus(), pubkey.getModulus());
        assertEquals(PUBLIC_KEY.getPublicExponent(), pubkey.getPublicExponent());
        final RSAPrivateKey privkey = (RSAPrivateKey)keypair.getPrivate();
        assertEquals(PRIVATE_KEY.getModulus(), privkey.getModulus());
        assertEquals(PRIVATE_KEY.getPrivateExponent(), privkey.getPrivateExponent());
        assertNull(jwk.getSecretKey());
        assertEquals(Type.rsa, jwk.getType());
        assertEquals(Usage.sig, jwk.getUsage());
        assertNull(jwk.getKeyOps());
        final String json = jwk.toJSONString();
        assertNotNull(json);
        
        final JsonWebKey joJwk = new JsonWebKey(new JSONObject(json));
        assertEquals(jwk.isExtractable(), joJwk.isExtractable());
        assertEquals(jwk.getAlgorithm(), joJwk.getAlgorithm());
        assertEquals(jwk.getId(), joJwk.getId());
        final KeyPair joKeypair = joJwk.getRsaKeyPair();
        assertNotNull(joKeypair);
        final RSAPublicKey joPubkey = (RSAPublicKey)joKeypair.getPublic();
        assertEquals(pubkey.getModulus(), joPubkey.getModulus());
        assertEquals(pubkey.getPublicExponent(), joPubkey.getPublicExponent());
        final RSAPrivateKey joPrivkey = (RSAPrivateKey)joKeypair.getPrivate();
        assertEquals(privkey.getModulus(), joPrivkey.getModulus());
        assertEquals(privkey.getPrivateExponent(), joPrivkey.getPrivateExponent());
        assertNull(joJwk.getSecretKey());
        assertEquals(jwk.getType(), joJwk.getType());
        assertEquals(jwk.getUsage(), joJwk.getUsage());
        assertEquals(jwk.getKeyOps(), joJwk.getKeyOps());
        final String joJson = joJwk.toJSONString();
        assertNotNull(joJson);
        assertEquals(json, joJson);
    }
    
    @Test
    public void rsaKeyOpsCtor() throws MslCryptoException, MslEncodingException, JSONException {
        final JsonWebKey jwk = new JsonWebKey(SIGN_VERIFY, Algorithm.RSA1_5, EXTRACTABLE, KEY_ID, PUBLIC_KEY, PRIVATE_KEY);
        assertEquals(EXTRACTABLE, jwk.isExtractable());
        assertEquals(Algorithm.RSA1_5, jwk.getAlgorithm());
        assertEquals(KEY_ID, jwk.getId());
        final KeyPair keypair = jwk.getRsaKeyPair();
        assertNotNull(keypair);
        final RSAPublicKey pubkey = (RSAPublicKey)keypair.getPublic();
        assertEquals(PUBLIC_KEY.getModulus(), pubkey.getModulus());
        assertEquals(PUBLIC_KEY.getPublicExponent(), pubkey.getPublicExponent());
        final RSAPrivateKey privkey = (RSAPrivateKey)keypair.getPrivate();
        assertEquals(PRIVATE_KEY.getModulus(), privkey.getModulus());
        assertEquals(PRIVATE_KEY.getPrivateExponent(), privkey.getPrivateExponent());
        assertNull(jwk.getSecretKey());
        assertEquals(Type.rsa, jwk.getType());
        assertNull(jwk.getUsage());
        assertEquals(SIGN_VERIFY, jwk.getKeyOps());
        final String json = jwk.toJSONString();
        assertNotNull(json);
        
        final JsonWebKey joJwk = new JsonWebKey(new JSONObject(json));
        assertEquals(jwk.isExtractable(), joJwk.isExtractable());
        assertEquals(jwk.getAlgorithm(), joJwk.getAlgorithm());
        assertEquals(jwk.getId(), joJwk.getId());
        final KeyPair joKeypair = joJwk.getRsaKeyPair();
        assertNotNull(joKeypair);
        final RSAPublicKey joPubkey = (RSAPublicKey)joKeypair.getPublic();
        assertEquals(pubkey.getModulus(), joPubkey.getModulus());
        assertEquals(pubkey.getPublicExponent(), joPubkey.getPublicExponent());
        final RSAPrivateKey joPrivkey = (RSAPrivateKey)joKeypair.getPrivate();
        assertEquals(privkey.getModulus(), joPrivkey.getModulus());
        assertEquals(privkey.getPrivateExponent(), joPrivkey.getPrivateExponent());
        assertNull(joJwk.getSecretKey());
        assertEquals(jwk.getType(), joJwk.getType());
        assertEquals(jwk.getUsage(), joJwk.getUsage());
        assertEquals(jwk.getKeyOps(), joJwk.getKeyOps());
        final String joJson = joJwk.toJSONString();
        assertNotNull(joJson);
        // This test will not always pass since the key operations are
        // unordered.
        //assertEquals(json, joJson);
    }
    
    @Test
    public void rsaUsageJson() throws JSONException {
        final JsonWebKey jwk = new JsonWebKey(Usage.sig, Algorithm.RSA1_5, EXTRACTABLE, KEY_ID, PUBLIC_KEY, PRIVATE_KEY);
        final JSONObject jo = new JSONObject(jwk.toJSONString());
        
        assertEquals(EXTRACTABLE, jo.optBoolean(KEY_EXTRACTABLE));
        assertEquals(Algorithm.RSA1_5.name(), jo.getString(KEY_ALGORITHM));
        assertEquals(KEY_ID, jo.getString(KEY_KEY_ID));
        assertEquals(Type.rsa.name(), jo.getString(KEY_TYPE));
        assertEquals(Usage.sig.name(), jo.getString(KEY_USAGE));
        assertFalse(jo.has(KEY_KEY_OPS));
        
        final String modulus = JsonUtils.b64urlEncode(bi2bytes(PUBLIC_KEY.getModulus()));
        final String pubexp = JsonUtils.b64urlEncode(bi2bytes(PUBLIC_KEY.getPublicExponent()));
        final String privexp = JsonUtils.b64urlEncode(bi2bytes(PRIVATE_KEY.getPrivateExponent()));
        
        assertEquals(modulus, jo.getString(KEY_MODULUS));
        assertEquals(pubexp, jo.getString(KEY_PUBLIC_EXPONENT));
        assertEquals(privexp, jo.getString(KEY_PRIVATE_EXPONENT));
        
        assertFalse(jo.has(KEY_KEY));
    }
    
    @Test
    public void rsaKeyOpsJson() throws JSONException {
        final JsonWebKey jwk = new JsonWebKey(SIGN_VERIFY, Algorithm.RSA1_5, EXTRACTABLE, KEY_ID, PUBLIC_KEY, PRIVATE_KEY);
        final JSONObject jo = new JSONObject(jwk.toJSONString());
        
        assertEquals(EXTRACTABLE, jo.optBoolean(KEY_EXTRACTABLE));
        assertEquals(Algorithm.RSA1_5.name(), jo.getString(KEY_ALGORITHM));
        assertEquals(KEY_ID, jo.getString(KEY_KEY_ID));
        assertEquals(Type.rsa.name(), jo.getString(KEY_TYPE));
        assertFalse(jo.has(KEY_USAGE));
        assertTrue(JsonUtils.equalSets(JA_SIGN_VERIFY, jo.getJSONArray(KEY_KEY_OPS)));
        
        final String modulus = JsonUtils.b64urlEncode(bi2bytes(PUBLIC_KEY.getModulus()));
        final String pubexp = JsonUtils.b64urlEncode(bi2bytes(PUBLIC_KEY.getPublicExponent()));
        final String privexp = JsonUtils.b64urlEncode(bi2bytes(PRIVATE_KEY.getPrivateExponent()));
        
        assertEquals(modulus, jo.getString(KEY_MODULUS));
        assertEquals(pubexp, jo.getString(KEY_PUBLIC_EXPONENT));
        assertEquals(privexp, jo.getString(KEY_PRIVATE_EXPONENT));
        
        assertFalse(jo.has(KEY_KEY));
    }
    
    @Test
    public void rsaNullCtorPublic() throws MslCryptoException, MslEncodingException, JSONException {
        final JsonWebKey jwk = new JsonWebKey(NULL_USAGE, null, false, null, PUBLIC_KEY, null);
        assertFalse(jwk.isExtractable());
        assertNull(jwk.getAlgorithm());
        assertNull(jwk.getId());
        final KeyPair keypair = jwk.getRsaKeyPair();
        assertNotNull(keypair);
        final RSAPublicKey pubkey = (RSAPublicKey)keypair.getPublic();
        assertEquals(PUBLIC_KEY.getModulus(), pubkey.getModulus());
        assertEquals(PUBLIC_KEY.getPublicExponent(), pubkey.getPublicExponent());
        final RSAPrivateKey privkey = (RSAPrivateKey)keypair.getPrivate();
        assertNull(privkey);
        assertNull(jwk.getSecretKey());
        assertEquals(Type.rsa, jwk.getType());
        assertNull(jwk.getUsage());
        assertNull(jwk.getKeyOps());
        final String json = jwk.toJSONString();
        assertNotNull(json);
        
        final JsonWebKey joJwk = new JsonWebKey(new JSONObject(json));
        assertEquals(jwk.isExtractable(), joJwk.isExtractable());
        assertEquals(jwk.getAlgorithm(), joJwk.getAlgorithm());
        assertEquals(jwk.getId(), joJwk.getId());
        final KeyPair joKeypair = joJwk.getRsaKeyPair();
        assertNotNull(joKeypair);
        final RSAPublicKey joPubkey = (RSAPublicKey)joKeypair.getPublic();
        assertEquals(pubkey.getModulus(), joPubkey.getModulus());
        assertEquals(pubkey.getPublicExponent(), joPubkey.getPublicExponent());
        final RSAPrivateKey joPrivkey = (RSAPrivateKey)joKeypair.getPrivate();
        assertNull(joPrivkey);
        assertNull(joJwk.getSecretKey());
        assertEquals(jwk.getType(), joJwk.getType());
        assertEquals(jwk.getUsage(), joJwk.getUsage());
        assertEquals(jwk.getKeyOps(), joJwk.getKeyOps());
        final String joJson = joJwk.toJSONString();
        assertNotNull(joJson);
        assertEquals(json, joJson);
    }
    
    @Test
    public void rsaNullCtorPrivate() throws MslCryptoException, MslEncodingException, JSONException {
        final JsonWebKey jwk = new JsonWebKey(NULL_USAGE, null, false, null, null, PRIVATE_KEY);
        assertFalse(jwk.isExtractable());
        assertNull(jwk.getAlgorithm());
        assertNull(jwk.getId());
        final KeyPair keypair = jwk.getRsaKeyPair();
        assertNotNull(keypair);
        final RSAPublicKey pubkey = (RSAPublicKey)keypair.getPublic();
        assertNull(pubkey);
        final RSAPrivateKey privkey = (RSAPrivateKey)keypair.getPrivate();
        assertEquals(PRIVATE_KEY.getModulus(), privkey.getModulus());
        assertEquals(PRIVATE_KEY.getPrivateExponent(), privkey.getPrivateExponent());
        assertNull(jwk.getSecretKey());
        assertEquals(Type.rsa, jwk.getType());
        assertNull(jwk.getUsage());
        assertNull(jwk.getKeyOps());
        final String json = jwk.toJSONString();
        assertNotNull(json);
        
        final JsonWebKey joJwk = new JsonWebKey(new JSONObject(json));
        assertEquals(jwk.isExtractable(), joJwk.isExtractable());
        assertEquals(jwk.getAlgorithm(), joJwk.getAlgorithm());
        assertEquals(jwk.getId(), joJwk.getId());
        final KeyPair joKeypair = joJwk.getRsaKeyPair();
        assertNotNull(joKeypair);
        final RSAPublicKey joPubkey = (RSAPublicKey)joKeypair.getPublic();
        assertNull(joPubkey);
        final RSAPrivateKey joPrivkey = (RSAPrivateKey)joKeypair.getPrivate();
        assertEquals(privkey.getModulus(), joPrivkey.getModulus());
        assertEquals(privkey.getPrivateExponent(), joPrivkey.getPrivateExponent());
        assertNull(joJwk.getSecretKey());
        assertEquals(jwk.getType(), joJwk.getType());
        assertEquals(jwk.getUsage(), joJwk.getUsage());
        assertEquals(jwk.getKeyOps(), joJwk.getKeyOps());
        final String joJson = joJwk.toJSONString();
        assertNotNull(joJson);
        assertEquals(json, joJson);
    }
    
    @Test
    public void rsaNullJsonPublic() throws JSONException {
        final JsonWebKey jwk = new JsonWebKey(NULL_USAGE, null, false, null, PUBLIC_KEY, null);
        final String json = jwk.toJSONString();
        final JSONObject jo = new JSONObject(json);
        
        assertFalse(jo.getBoolean(KEY_EXTRACTABLE));
        assertFalse(jo.has(KEY_ALGORITHM));
        assertFalse(jo.has(KEY_KEY_ID));
        assertEquals(Type.rsa.name(), jo.getString(KEY_TYPE));
        assertFalse(jo.has(KEY_USAGE));
        assertFalse(jo.has(KEY_KEY_OPS));
        
        final String modulus = JsonUtils.b64urlEncode(bi2bytes(PUBLIC_KEY.getModulus()));
        final String pubexp = JsonUtils.b64urlEncode(bi2bytes(PUBLIC_KEY.getPublicExponent()));
        
        assertEquals(modulus, jo.getString(KEY_MODULUS));
        assertEquals(pubexp, jo.getString(KEY_PUBLIC_EXPONENT));
        assertFalse(jo.has(KEY_PRIVATE_EXPONENT));

        assertFalse(jo.has(KEY_KEY));
    }
    
    @Test
    public void rsaNullJsonPrivate() throws JSONException {
        final JsonWebKey jwk = new JsonWebKey(NULL_USAGE, null, false, null, null, PRIVATE_KEY);
        final JSONObject jo = new JSONObject(jwk.toJSONString());
        
        assertFalse(jo.getBoolean(KEY_EXTRACTABLE));
        assertFalse(jo.has(KEY_ALGORITHM));
        assertFalse(jo.has(KEY_KEY_ID));
        assertEquals(Type.rsa.name(), jo.getString(KEY_TYPE));
        assertFalse(jo.has(KEY_USAGE));
        assertFalse(jo.has(KEY_KEY_OPS));
        
        final String modulus = JsonUtils.b64urlEncode(bi2bytes(PUBLIC_KEY.getModulus()));
        final String privexp = JsonUtils.b64urlEncode(bi2bytes(PRIVATE_KEY.getPrivateExponent()));
        
        assertEquals(modulus, jo.getString(KEY_MODULUS));
        assertFalse(jo.has(KEY_PUBLIC_EXPONENT));
        assertEquals(privexp, jo.getString(KEY_PRIVATE_EXPONENT));

        assertFalse(jo.has(KEY_KEY));
    }
    
    @Test(expected = MslInternalException.class)
    public void rsaCtorNullKeys() {
        new JsonWebKey(NULL_USAGE, null, false, null, null, null);
    }
    
    @Test(expected = MslInternalException.class)
    public void rsaCtorMismatchedAlgorithm() {
        new JsonWebKey(NULL_USAGE, Algorithm.A128CBC, false, null, PUBLIC_KEY, PRIVATE_KEY);
    }
    
    @Test
    public void octUsageCtor() throws MslCryptoException, MslEncodingException, JSONException {
        final JsonWebKey jwk = new JsonWebKey(Usage.enc, Algorithm.A128CBC, EXTRACTABLE, KEY_ID, SECRET_KEY);
        assertEquals(EXTRACTABLE, jwk.isExtractable());
        assertEquals(Algorithm.A128CBC, jwk.getAlgorithm());
        assertEquals(KEY_ID, jwk.getId());
        assertNull(jwk.getRsaKeyPair());
        assertArrayEquals(SECRET_KEY.getEncoded(), jwk.getSecretKey().getEncoded());
        assertEquals(Type.oct, jwk.getType());
        assertEquals(Usage.enc, jwk.getUsage());
        assertNull(jwk.getKeyOps());
        final String json = jwk.toJSONString();
        assertNotNull(json);
        
        final JsonWebKey joJwk = new JsonWebKey(new JSONObject(json));
        assertEquals(jwk.isExtractable(), joJwk.isExtractable());
        assertEquals(jwk.getAlgorithm(), joJwk.getAlgorithm());
        assertEquals(jwk.getId(), joJwk.getId());
        assertNull(joJwk.getRsaKeyPair());
        assertArrayEquals(jwk.getSecretKey().getEncoded(), joJwk.getSecretKey().getEncoded());
        assertEquals(jwk.getType(), joJwk.getType());
        assertEquals(jwk.getUsage(), joJwk.getUsage());
        assertEquals(jwk.getKeyOps(), joJwk.getKeyOps());
        final String joJson = joJwk.toJSONString();
        assertNotNull(joJson);
        assertEquals(json, joJson);
    }
    
    @Test
    public void octKeyOpsCtor() throws MslCryptoException, MslEncodingException, JSONException {
        final JsonWebKey jwk = new JsonWebKey(ENCRYPT_DECRYPT, Algorithm.A128CBC, EXTRACTABLE, KEY_ID, SECRET_KEY);
        assertEquals(EXTRACTABLE, jwk.isExtractable());
        assertEquals(Algorithm.A128CBC, jwk.getAlgorithm());
        assertEquals(KEY_ID, jwk.getId());
        assertNull(jwk.getRsaKeyPair());
        assertArrayEquals(SECRET_KEY.getEncoded(), jwk.getSecretKey().getEncoded());
        assertEquals(Type.oct, jwk.getType());
        assertNull(jwk.getUsage());
        assertEquals(ENCRYPT_DECRYPT, jwk.getKeyOps());
        final String json = jwk.toJSONString();
        assertNotNull(json);
        
        final JsonWebKey joJwk = new JsonWebKey(new JSONObject(json));
        assertEquals(jwk.isExtractable(), joJwk.isExtractable());
        assertEquals(jwk.getAlgorithm(), joJwk.getAlgorithm());
        assertEquals(jwk.getId(), joJwk.getId());
        assertNull(joJwk.getRsaKeyPair());
        assertArrayEquals(jwk.getSecretKey().getEncoded(), joJwk.getSecretKey().getEncoded());
        assertEquals(jwk.getType(), joJwk.getType());
        assertEquals(jwk.getUsage(), joJwk.getUsage());
        assertEquals(jwk.getKeyOps(), joJwk.getKeyOps());
        final String joJson = joJwk.toJSONString();
        assertNotNull(joJson);
        // This test will not always pass since the key operations are
        // unordered.
        //assertEquals(json, joJson);
    }
    
    @Test
    public void octUsageJson() throws JSONException {
        final JsonWebKey jwk = new JsonWebKey(Usage.wrap, Algorithm.A128KW, EXTRACTABLE, KEY_ID, SECRET_KEY);
        final JSONObject jo = new JSONObject(jwk.toJSONString());
        
        assertEquals(EXTRACTABLE, jo.optBoolean(KEY_EXTRACTABLE));
        assertEquals(Algorithm.A128KW.name(), jo.getString(KEY_ALGORITHM));
        assertEquals(KEY_ID, jo.getString(KEY_KEY_ID));
        assertEquals(Type.oct.name(), jo.getString(KEY_TYPE));
        assertEquals(Usage.wrap.name(), jo.getString(KEY_USAGE));
        assertFalse(jo.has(KEY_KEY_OPS));
        
        assertFalse(jo.has(KEY_MODULUS));
        assertFalse(jo.has(KEY_PUBLIC_EXPONENT));
        assertFalse(jo.has(KEY_PRIVATE_EXPONENT));
        
        final String key = JsonUtils.b64urlEncode(SECRET_KEY.getEncoded());
        
        assertEquals(key, jo.getString(KEY_KEY));
    }
    
    @Test
    public void octKeyOpsJson() throws JSONException {
        final JsonWebKey jwk = new JsonWebKey(WRAP_UNWRAP, Algorithm.A128KW, EXTRACTABLE, KEY_ID, SECRET_KEY);
        final JSONObject jo = new JSONObject(jwk.toJSONString());
        
        assertEquals(EXTRACTABLE, jo.optBoolean(KEY_EXTRACTABLE));
        assertEquals(Algorithm.A128KW.name(), jo.getString(KEY_ALGORITHM));
        assertEquals(KEY_ID, jo.getString(KEY_KEY_ID));
        assertEquals(Type.oct.name(), jo.getString(KEY_TYPE));
        assertFalse(jo.has(KEY_USAGE));
        assertTrue(JsonUtils.equalSets(JA_WRAP_UNWRAP, jo.getJSONArray(KEY_KEY_OPS)));
        
        assertFalse(jo.has(KEY_MODULUS));
        assertFalse(jo.has(KEY_PUBLIC_EXPONENT));
        assertFalse(jo.has(KEY_PRIVATE_EXPONENT));
        
        final String key = JsonUtils.b64urlEncode(SECRET_KEY.getEncoded());
        
        assertEquals(key, jo.getString(KEY_KEY));
    }
    
    @Test
    public void octNullCtor() throws MslCryptoException, MslEncodingException, JSONException {
        final JsonWebKey jwk = new JsonWebKey(NULL_USAGE, null, false, null, SECRET_KEY);
        assertFalse(jwk.isExtractable());
        assertNull(jwk.getAlgorithm());
        assertNull(jwk.getId());
        assertNull(jwk.getRsaKeyPair());
        assertArrayEquals(SECRET_KEY.getEncoded(), jwk.getSecretKey().getEncoded());
        assertEquals(Type.oct, jwk.getType());
        assertNull(jwk.getUsage());
        assertNull(jwk.getKeyOps());
        final String json = jwk.toJSONString();
        assertNotNull(json);
        
        final JsonWebKey joJwk = new JsonWebKey(new JSONObject(json));
        assertEquals(jwk.isExtractable(), joJwk.isExtractable());
        assertEquals(jwk.getAlgorithm(), joJwk.getAlgorithm());
        assertEquals(jwk.getId(), joJwk.getId());
        assertNull(joJwk.getRsaKeyPair());
        assertArrayEquals(jwk.getSecretKey(SECRET_KEY.getAlgorithm()).getEncoded(), joJwk.getSecretKey(SECRET_KEY.getAlgorithm()).getEncoded());
        assertEquals(jwk.getType(), joJwk.getType());
        assertEquals(jwk.getUsage(), joJwk.getUsage());
        assertEquals(jwk.getKeyOps(), joJwk.getKeyOps());
        final String joJson = joJwk.toJSONString();
        assertNotNull(joJson);
        assertEquals(json, joJson);
    }
    
    @Test
    public void octNullJson() throws JSONException {
        final JsonWebKey jwk = new JsonWebKey(NULL_USAGE, null, false, null, SECRET_KEY);
        final JSONObject jo = new JSONObject(jwk.toJSONString());
        
        assertFalse(jo.getBoolean(KEY_EXTRACTABLE));
        assertFalse(jo.has(KEY_ALGORITHM));
        assertFalse(jo.has(KEY_KEY_ID));
        assertEquals(Type.oct.name(), jo.getString(KEY_TYPE));
        assertFalse(jo.has(KEY_USAGE));
        assertFalse(jo.has(KEY_KEY_OPS));
        
        assertFalse(jo.has(KEY_MODULUS));
        assertFalse(jo.has(KEY_PUBLIC_EXPONENT));
        assertFalse(jo.has(KEY_PRIVATE_EXPONENT));
        
        final String key = JsonUtils.b64urlEncode(SECRET_KEY.getEncoded());
        
        assertEquals(key, jo.getString(KEY_KEY));
    }
    
    public void usageOnly() throws JSONException, MslCryptoException, MslEncodingException {
        final JsonWebKey jwk = new JsonWebKey(NULL_USAGE, null, false, null, SECRET_KEY);
        final JSONObject jo = new JSONObject(jwk.toJSONString());
        
        jo.put(KEY_USAGE, Usage.enc.name());
        
        final JsonWebKey joJwk = new JsonWebKey(jo);
        assertEquals(Usage.enc, joJwk.getUsage());
        assertNull(joJwk.getKeyOps());
    }
    
    public void keyOpsOnly() throws JSONException, MslCryptoException, MslEncodingException {
        final JsonWebKey jwk = new JsonWebKey(NULL_KEYOPS, null, false, null, SECRET_KEY);
        final JSONObject jo = new JSONObject(jwk.toJSONString());
        
        jo.put(KEY_KEY_OPS, JA_ENCRYPT_DECRYPT);
        
        final JsonWebKey joJwk = new JsonWebKey(jo);
        assertNull(joJwk.getUsage());
        assertEquals(new HashSet<KeyOp>(Arrays.asList(KeyOp.encrypt, KeyOp.decrypt)), joJwk.getKeyOps());
    }
    
    @Test(expected = MslInternalException.class)
    public void octCtorMismatchedAlgo() {
        new JsonWebKey(NULL_USAGE, Algorithm.RSA1_5, false, null, SECRET_KEY);
    }
    
    @Test
    public void missingType() throws JSONException, MslCryptoException, MslEncodingException {
        thrown.expect(MslEncodingException.class);
        thrown.expectMslError(MslError.JSON_PARSE_ERROR);

        final JsonWebKey jwk = new JsonWebKey(NULL_USAGE, null, false, null, SECRET_KEY);
        final JSONObject jo = new JSONObject(jwk.toJSONString());
        
        jo.remove(KEY_TYPE);
        
        new JsonWebKey(jo);
    }
    
    @Test
    public void invalidType() throws MslCryptoException, MslEncodingException, JSONException {
        thrown.expect(MslCryptoException.class);
        thrown.expectMslError(MslError.UNIDENTIFIED_JWK_TYPE);

        final JsonWebKey jwk = new JsonWebKey(NULL_USAGE, null, false, null, SECRET_KEY);
        final JSONObject jo = new JSONObject(jwk.toJSONString());
        
        jo.put(KEY_TYPE, "x");
        
        new JsonWebKey(jo);
    }
    
    @Test
    public void invalidUsage() throws MslCryptoException, MslEncodingException, JSONException {
        thrown.expect(MslCryptoException.class);
        thrown.expectMslError(MslError.UNIDENTIFIED_JWK_USAGE);

        final JsonWebKey jwk = new JsonWebKey(NULL_USAGE, null, false, null, SECRET_KEY);
        final JSONObject jo = new JSONObject(jwk.toJSONString());
        
        jo.put(KEY_USAGE, "x");
        
        new JsonWebKey(jo);
    }
    
    @Test
    public void invalidKeyOp() throws JSONException, MslCryptoException, MslEncodingException {
        thrown.expect(MslCryptoException.class);
        thrown.expectMslError(MslError.UNIDENTIFIED_JWK_KEYOP);

        final JsonWebKey jwk = new JsonWebKey(NULL_KEYOPS, null, false, null, SECRET_KEY);
        final JSONObject jo = new JSONObject(jwk.toJSONString());
        
        jo.put(KEY_KEY_OPS, new JSONArray(Arrays.asList(KeyOp.encrypt.name(), "x", KeyOp.decrypt.name()).toArray()));
        
        new JsonWebKey(jo);
    }
    
    @Test
    public void invalidAlgorithm() throws MslCryptoException, MslEncodingException, JSONException {
        thrown.expect(MslCryptoException.class);
        thrown.expectMslError(MslError.UNIDENTIFIED_JWK_ALGORITHM);

        final JsonWebKey jwk = new JsonWebKey(NULL_USAGE, null, false, null, SECRET_KEY);
        final JSONObject jo = new JSONObject(jwk.toJSONString());
        
        jo.put(KEY_ALGORITHM, "x");
        
{'fraction_non_alphanumeric': 0.11420027886505284, 'fraction_numerical': 0.003326221714516102, 'mean_word_length': 3.41176906544134, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2272434', 'n_tokens_mistral': 18775, 'n_tokens_neox': 18051, 'n_words': 3261}