/*---
includes: [compatFs.js, compatWebcrypto.js, runTsuite.js, webCryptoUtils.js, compareObjects.js]
flags: [async]
---*/

async function load_key(params) {
    if (params.generate_keys) {
        let type = params.generate_keys.type;
        if (params.generate_keys.keys) {
            return params.generate_keys.keys[type];
        }

        params.generate_keys.keys = await crypto.subtle.generateKey(params.generate_keys.alg,
                                                                    params.generate_keys.extractable,
                                                                    params.generate_keys.usage);

        return params.generate_keys.keys[type];
    }

    if (params.generate_key) {
        return await crypto.subtle.generateKey(params.generate_key.alg,
                                               params.generate_key.extractable,
                                               params.generate_key.usage);
    }

    return await crypto.subtle.importKey(params.key.fmt,
                                         params.key.key,
                                         params.key.alg,
                                         params.key.extractable,
                                         params.key.usage);

}

async function test(params) {
    let key = await load_key(params);
    let exp = await crypto.subtle.exportKey(params.export.fmt, key);

    validate_key(key, params);

    if (params.check && !params.check(exp, params)) {
        throw Error(`failed check`);
    }

    if (exp[Symbol.toStringTag] == 'ArrayBuffer') {
        let buf = Buffer.from(exp);
        exp = "ArrayBuffer:" + buf.toString('base64url');
    }

    if (params.expected && !compareObjects(params.expected, exp)) {
        throw Error(`unexpected export key: ${JSON.stringify(exp)}\n expected: ${JSON.stringify(params.expected)}`);
    }

    if (!params.generate_keys
        && !params.generate_key
        && (exp.startsWith && !exp.startsWith("ArrayBuffer:")))
    {
        /* Check that exported key can be imported back. */
        let imported = await crypto.subtle.importKey(params.export.fmt,
                                                     exp,
                                                     params.key.alg,
                                                     params.key.extractable,
                                                     params.key.usage);
    }

    return 'SUCCESS';
}

function p(args, default_opts) {
    let key, pem;
    let params = merge({}, default_opts);
    params = merge(params, args);

    switch (params.key.fmt) {
    case "spki":
        pem = fs.readFileSync(`test/webcrypto/${params.key.key}`);
        key = pem_to_der(pem, "PUBLIC");
        break;
    case "pkcs8":
        pem = fs.readFileSync(`test/webcrypto/${params.key.key}`);
        key = pem_to_der(pem, "PRIVATE");
        break;
    case "jwk":
        key = load_jwk(params.key.key);
        break;
    case "raw":
        key = Buffer.from(params.key.key, "base64url");
        break;
    default:
        throw Error("Unknown encoding key format");
    }

    params.key.key = key;

    return params;
}

function validate_key(key, params) {
    let opts;

    if (params.generate_keys) {
        opts = params.generate_keys;

    } else if (params.generate_key) {
        opts = params.generate_key;

    } else {
        opts = params.key;
    }

    if (opts.extractable != key.extractable) {
        throw Error(`unexpected generated key.extractable: ${key.extractable}`);
    }

    switch (key.type) {
    case "secret":
        /* AesKeyGenParams */

        switch (key.algorithm.name) {
        case "AES-CBC":
        case "AES-CTR":
        case "AES-GCM":
            let length = key.algorithm.length;

            switch (length) {
            case 128:
            case 192:
            case 256:
                break;
            default:
                throw Error(`unexpected symmetric generated key.algorithm.length: ${length}`);
            }

            /* FALLTHROUGH */

        case "HMAC":
            if (!compareObjects(opts.usage, key.usages)) {
                throw Error(`unexpected symmetric generated key.usages: ${key.usages}`);
            }

            break;

        default:
            throw Error(`unexpected symmetric generated key.algorithm.name: ${key.algorithm.name}`);
        }

        break;
    case "private":
    case "public":
        if (opts.expected && !compareObjects(opts.expected.key_ops, key.usages)) {
            throw Error(`unexpected asymmetric generated key.usages: ${key.usages}`);
        }

        switch (key.algorithm.name) {
        case "RSA-OAEP":
        case "RSA-PSS":
        case "RSASSA-PKCS1-v1_5":
            /* RsaHashedKeyGenParams */

            let mlength = key.algorithm.modulusLength;
            let pexp = key.algorithm.publicExponent;
            let hash = key.algorithm.hash.name;

            switch (mlength) {
            case 1024:
            case 2048:
            case 4096:
                break;
            default:
                throw Error(`unexpected asymmetric generated key.algorithm.modulusLength: ${mlength}`);
            }

            if (!compareObjects(new Uint8Array([1, 0, 1]), pexp)) {
                throw Error(`unexpected asymmetric generated key.algorithm.publicExponent: ${pexp}`);
            }

            switch (hash) {
            case "SHA-1":
            case "SHA-256":
            case "SHA-384":
            case "SHA-512":
                break;
            default:
                throw Error(`unexpected asymmetric generated key.algorithm.hash.name: ${hash}`);
            }

            break;
        case "ECDSA":
        case "ECDH":
            /* EcKeyGenParams */

            let crv = key.algorithm.namedCurve;
            switch (crv) {
            case "P-256":
            case "P-384":
            case "P-521":
                break;
            default:
                throw Error(`unexpected asymmetric generated key.algorithm.namedCurve: ${crv}`);
            }

            break;
        default:
            throw Error(`unexpected asymmetric generated key.algorithm.name: ${key.algorithm.name}`);
        }

        break;
    default:
        throw Error(`unexpected generated key.type: ${key.type}`);
    }
}

function validate_property(exp, p, exp_len) {
    if (!exp[p]) {
        throw Error(`"${p}" is not found in ${JSON.stringify(exp)}`);
    }

    if (typeof exp[p] != 'string') {
        throw Error(`"${p}" is not a string`);
    }

    let len = exp[p].length;

    if (len < exp_len - 4 || len > exp_len + 4) {
        throw Error(`"${p}":"${exp[p]}" length is out of range [${exp_len - 4}, ${exp_len + 4}]`);
    }
}


function validate_rsa_jwk(exp, params) {
    let expected_len = params.generate_keys.alg.modulusLength / 8 * (4 / 3);
    expected_len = Math.round(expected_len);

    validate_property(exp, 'n', expected_len);

    if (params.generate_keys.type == 'privateKey') {
        validate_property(exp, 'd', expected_len);
        validate_property(exp, 'p', expected_len / 2);
        validate_property(exp, 'q', expected_len / 2);

        validate_property(exp, 'dq', expected_len / 2);
        validate_property(exp, 'dp', expected_len / 2);
        validate_property(exp, 'qi', expected_len / 2);
    }

    return true;
}

let rsa_tsuite = {
    name: "RSA exporting",
    skip: () => (!has_webcrypto()),
    T: test,
    prepare_args: p,
    opts: {
        key: {
               fmt: "spki",
               key: "rsa.spki",
               alg: { name: "RSA-OAEP", hash: "SHA-256" },
               extractable: true,
               usage: [ "encrypt" ]
        },
        export: { fmt: "jwk" },
        expected: {
            ext: true,
            kty: "RSA",
            e: "AQAB",
        },
    },

    tests: [
      { expected: { key_ops: [ "encrypt" ],
                    n: "yUmxoJC8VAM5hyYZa-XUBZg1N1ywFMPUpWsF1kaSGed98P3XUgPzgX80wpyzd5qdGuALqnf2lMc7O8PrGBtO5YrvQlI96NX0jUo5bc5wz220ob3AUCeQnTfx-UFqM4pCwjoDSo2PlphJdWgFYymGBaBCJgnENQL9H1N_8_yNiN8",
                    alg: "RSA-OAEP-256" } },
      { export: { fmt: "spki" },
        expected: "ArrayBuffer:MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDJSbGgkLxUAzmHJhlr5dQFmDU3XLAUw9SlawXWRpIZ533w_ddSA_OBfzTCnLN3mp0a4Auqd_aUxzs7w-sYG07liu9CUj3o1fSNSjltznDPbbShvcBQJ5CdN_H5QWozikLCOgNKjY-WmEl1aAVjKYYFoEImCcQ1Av0fU3_z_I2I3wIDAQAB" },

      { key: { fmt: "pkcs8", key: "rsa.pkcs8", usage: [ "decrypt" ], alg: { hash: "SHA-512" } },
        expected: { key_ops: [ "decrypt" ],
                    n: "yUmxoJC8VAM5hyYZa-XUBZg1N1ywFMPUpWsF1kaSGed98P3XUgPzgX80wpyzd5qdGuALqnf2lMc7O8PrGBtO5YrvQlI96NX0jUo5bc5wz220ob3AUCeQnTfx-UFqM4pCwjoDSo2PlphJdWgFYymGBaBCJgnENQL9H1N_8_yNiN8",
                    alg: "RSA-OAEP-512" } },

      { key: { fmt: "pkcs8", key: "rsa.pkcs8", usage: [ "decrypt" ], alg: { hash: "SHA-512" } },
        export: { fmt: "pkcs8" },
        expected: "ArrayBuffer:MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAMlJsaCQvFQDOYcmGWvl1AWYNTdcsBTD1KVrBdZGkhnnffD911ID84F_NMKcs3eanRrgC6p39pTHOzvD6xgbTuWK70JSPejV9I1KOW3OcM9ttKG9wFAnkJ038flBajOKQsI6A0qNj5aYSXVoBWMphgWgQiYJxDUC_R9Tf_P8jYjfAgMBAAECgYEAj06DQyCopFujYoASi0oWmGEUSjUYO8BsrdSzVCnsLLsuZBwlZ4Peouyw4Hl2IIoYniCyzYwZJzVtC5Dh2MjgcrJTG5nX3FfheuabGl4in0583C51ZYWlVpDvBWw8kJTfXjiKH4z6ZA9dWdT5Y3aH_kOf-znUc7eTvuzISs61x_kCQQD0BJvbLDlvx3u6esW47LLgQNw9ufMSlu5UYBJ4c-qQ5HAeyp4Zt_AaWENhJitjQcLBSxIFIVw7dIN67RnTNK8VAkEA0yvzzgHo_PGYSlVj-M3965AwQF2wTXz82MZHv6EfcCHKuBfCSecr-igqLHhzfynAQjjf39VrXuPuRL23REF1IwJBAKVFydo0peJTljXDmc-aYb0JsSINo9jfaSS0vU3gFOt2DYqNaW-56WGujlRqadCcZbBNjDL1WWbbj4HevTMT59ECQEWaKgzPolykwN5XUNE0DCp1ZwIAH1kbBjfo-sMVt0f9S1TsN9SmBl-4l1X7CY5zU3RATMH5FR-8ns83fM1ZieMCQQDZEQ-dFAhouzJrnCXAXDTCHA9oBtNmnaN-C6G2DmCi79iu7sLHP9vzdgU-CgjrG4YTU5exaRFNOhLwW4hYKs0F" },

      { key: { fmt: "pkcs8", key: "rsa.pkcs8", usage: [ "decrypt" ], alg: { hash: "SHA-512" } },
        export: { fmt: "spki" },
        exception: "TypeError: private key of \"RSA-OAEP\" cannot be exported as SPKI" },

      { generate_keys: { alg: { name: "RSA-OAEP",
                                modulusLength: 2048,
                                publicExponent: new Uint8Array([1, 0, 1]),
                                hash: "SHA-256" },
                         extractable: true,
                         type: "publicKey",
                         usage: [ "encrypt", "decrypt" ] },
        check: validate_rsa_jwk,
        expected: { kty: "RSA", ext: true, key_ops: [ "encrypt" ], e: "AQAB", alg: "RSA-OAEP-256" } },
      { generate_keys: { alg: { name: "RSA-OAEP",
                                modulusLength: 1024,
                                publicExponent: new Uint8Array([1, 0, 1]),
                                hash: "SHA-1" },
                         extractable: true,
                         type: "privateKey",
                         usage: [ "encrypt", "decrypt" ] },
        check: validate_rsa_jwk,
        expected: { kty: "RSA", ext: true, key_ops: [ "decrypt" ], e: "AQAB", alg: "RSA-OAEP" } },
      { generate_keys: { alg: { name: "RSA-OAEP",
                                modulusLength: 1024,
                                publicExponent: new Uint8Array([1, 0, 1]),
                                hash: "SHA-1" },
                         extractable: false,
                         type: "privateKey",
                         usage: [ "encrypt", "decrypt" ] },
        check: validate_rsa_jwk,
        exception: "TypeError: provided key cannot be extracted" },
      { generate_keys: { alg: { name: "RSASSA-PKCS1-v1_5",
                                modulusLength: 1024,
                                publicExponent: new Uint8Array([1, 0, 1]),
                                hash: "SHA-512" },
                         extractable: true,
                         type: "publicKey",
                         usage: [ "sign", "verify" ] },
        check: validate_rsa_jwk,
        expected: { kty: "RSA", ext: true, key_ops: [ "verify" ], e: "AQAB", alg: "RS512" } },
      { generate_keys: { alg: { name: "RSASSA-PKCS1-v1_5",
                                modulusLength: 1024,
                                publicExponent: new Uint8Array([1, 0, 1]),
                                hash: "SHA-256" },
                         extractable: true,
                         type: "privateKey",
                         usage: [ "sign", "verify" ] },
        check: validate_rsa_jwk,
        expected: { kty: "RSA", ext: true, key_ops: [ "sign" ], e: "AQAB", alg: "RS256" } },
      { generate_keys: { alg: { name: "RSA-PSS",
                                modulusLength: 1024,
                                publicExponent: new Uint8Array([1, 0, 1]),
                                hash: "SHA-384" },
                         extractable: true,
                         type: "publicKey",
                         usage: [ "sign", "verify" ] },
        check: validate_rsa_jwk,
        expected: { kty: "RSA", ext: true, key_ops: [ "verify" ], e: "AQAB", alg: "PS384" } },
      { generate_keys: { alg: { name: "RSA-PSS",
                                modulusLength: 1024,
                                publicExponent: new Uint8Array([1, 0, 1]),
                                hash: "SHA-1" },
                         extractable: true,
                         type: "privateKey",
                         usage: [ "sign", "verify" ] },
        check: validate_rsa_jwk,
        expected: { kty: "RSA", ext: true, key_ops: [ "sign" ], e: "AQAB", alg: "PS1" } },
]};

function validate_ec_jwk(exp, params) {
    let crv = params.generate_keys.alg.namedCurve;
    let expected_len = Number(crv.slice(2)) / 8 * (4 / 3);
    expected_len = Math.round(expected_len);

    validate_property(exp, 'x', expected_len);
    validate_property(exp, 'y', expected_len);

    if (params.generate_keys.type == 'privateKey') {
        validate_property(exp, 'd', expected_len);
    }

    return true;
}

let ec_tsuite = {
    name: "EC exporting",
    skip: () => (!has_webcrypto()),
    T: test,
    prepare_args: p,
    opts: {
        key: { fmt: "spki",
               key: "ec.spki",
               alg: { name: "ECDSA", namedCurve: "P-256" },
               extractable: true },
        export: { fmt: "jwk" },
        expected: { ext: true, kty: "EC" },
    },

    tests: [
      { key: { usage: [ "verify" ] },
        expected: { key_ops: [ "verify" ],
                    x: "cUUsZRGuVYReGiTQl9MqR7ir6dZIgw-8pM-F0phJWdw",
                    y: "4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI",
                    crv: "P-256" } },
      { key: { alg: { name: "ECDH", namedCurve: "P-256" }, usage: [ ] } },
      { key: { fmt: "pkcs8", key: "ec.pkcs8", usage: [ "sign" ] },
        expected: { key_ops: [ "sign" ],
                    x: "cUUsZRGuVYReGiTQl9MqR7ir6dZIgw-8pM-F0phJWdw",
                    y: "4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI",
                    d: "E2sW0_4a3QXaSTJ0JKbSUbieKTD1UFtr7i_2CuetP6A",
                    crv: "P-256" } },
      { key: { fmt: "pkcs8", key: "ec.pkcs8", usage: [ "sign" ] },
        export: { fmt: "pkcs8" },
        expected: "ArrayBuffer:MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgE2sW0_4a3QXaSTJ0JKbSUbieKTD1UFtr7i_2CuetP6ChRANCAARxRSxlEa5VhF4aJNCX0ypHuKvp1kiDD7ykz4XSmElZ3ODc5_-7jc9AAN1OH4aX1cUg-FOUHIhshKDOK94wu24y" },
      { export: { fmt: "pkcs8" },
        exception: "TypeError: public key of \"ECDSA\" cannot be exported as PKCS8" },
      { key: { alg: { name: "ECDH", namedCurve: "P-256" },
               fmt: "pkcs8", key: "ec.pkcs8",
               usage: [ "deriveKey" ] },
        export: { fmt: "pkcs8" },
        expected: "ArrayBuffer:MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgE2sW0_4a3QXaSTJ0JKbSUbieKTD1UFtr7i_2CuetP6ChRANCAARxRSxlEa5VhF4aJNCX0ypHuKvp1kiDD7ykz4XSmElZ3ODc5_-7jc9AAN1OH4aX1cUg-FOUHIhshKDOK94wu24y" },
      { key: { usage: [ "verify" ] },
        export: { fmt: "spki" },
        expected: "ArrayBuffer:MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEcUUsZRGuVYReGiTQl9MqR7ir6dZIgw-8pM-F0phJWdzg3Of_u43PQADdTh-Gl9XFIPhTlByIbISgziveMLtuMg"},
      { key: { alg: { name: "ECDH", namedCurve: "P-256" }, usage: [ ] },
        export: { fmt: "spki" },
        expected: "ArrayBuffer:MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEcUUsZRGuVYReGiTQl9MqR7ir6dZIgw-8pM-F0phJWdzg3Of_u43PQADdTh-Gl9XFIPhTlByIbISgziveMLtuMg"},
      { key: { usage: [ "verify" ] },
        export: { fmt: "raw" },
        expected: "ArrayBuffer:BHFFLGURrlWEXhok0JfTKke4q-nWSIMPvKTPhdKYSVnc4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI" },
      { key: { alg: { name: "ECDH", namedCurve: "P-256" }, usage: [ ] },
        export: { fmt: "raw" },
        expected: "ArrayBuffer:BHFFLGURrlWEXhok0JfTKke4q-nWSIMPvKTPhdKYSVnc4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI" },
      { key: { fmt: "pkcs8", key: "ec.pkcs8", usage: [ "sign" ] },
        export: { fmt: "raw" },
        exception: "TypeError: private key of \"ECDSA\" cannot be exported in \"raw\" format" },
      { generate_keys: { alg: { name: "ECDSA",
                                namedCurve: "P-256" },
                         extractable: true,
                         type: "publicKey",
                         usage: [ "sign", "verify" ] },
        check: validate_ec_jwk,
        expected: { kty: "EC", ext: true, key_ops: [ "verify" ], crv: "P-256" } },
      { generate_keys: { alg: { name: "ECDSA",
                                namedCurve: "P-384" },
                         extractable: true,
                         type: "privateKey",
                         usage: [ "sign", "verify" ] },
        check: validate_ec_jwk,
        expected: { kty: "EC", ext: true, key_ops: [ "sign" ], crv: "P-384" } },
]};

function validate_hmac_jwk(exp, params) {
    let hash = params.generate_key.alg.hash;
    let expected_len = Number(hash.slice(2)) / 8 * (4 / 3);
    expected_len = Math.round(expected_len);

    validate_property(exp, 'k', expected_len);

    return true;
}

let hmac_tsuite = {
    name: "HMAC exporting",
    skip: () => (!has_webcrypto()),
    T: test,
    prepare_args: p,
    opts: {
        key: { fmt: "raw",
               key: "c2VjcmV0LUtleTE",
               alg: { name: "HMAC", hash: "SHA-256" },
               extractable: true,
               usage: [ "sign", "verify" ] },
        export: { fmt: "jwk" },
        expected: { kty: "oct", ext: true },
    },

    tests: [
      { expected: { key_ops: [ "sign", "verify" ],
                    alg: "HS256",
                    k: "c2VjcmV0LUtleTE" } },
      { export: { fmt: "raw" },
        expected: "ArrayBuffer:c2VjcmV0LUtleTE" },
      { export: { fmt: "spki" },
        exception: "TypeError: unsupported key fmt \"spki\" for \"HMAC\"" },
      { export: { fmt: "pksc8" },
        exception: "TypeError: unsupported key fmt \"pksc8\" for \"HMAC\"" },
      { key: { key: "cDBzc3dE",
               alg: { hash: "SHA-384" } },
        expected: { key_ops: [ "sign", "verify" ],
                    alg: "HS384",
                    k: "cDBzc3dE" } },
      { key: { extractable: false },
        exception: "TypeError: provided key cannot be extracted" },

      { key: { fmt: "jwk",
               key: { kty: "oct", ext: true, k: "c2VjcmV0LUtleTE", alg: "HS256" } },
        expected: { key_ops: [ "sign", "verify" ],
                    alg: "HS256",
                    k: "c2VjcmV0LUtleTE" } },
      { key: { fmt: "jwk",
               alg: { hash: "SHA-512" },
               key: { kty: "oct", ext: true, k: "c2VjcmV0LUtleTE", alg: "HS512" } },
        expected: { key_ops: [ "sign", "verify" ],
                    alg: "HS512",
                    k: "c2VjcmV0LUtleTE" } },
      { key: { fmt: "jwk",
               key: { kty: "oct", ext: true, k: "c2VjcmV0LUtleTE", alg: "HS256" },
               alg: { hash: "SHA-384" } },
        exception: "TypeError: HMAC JWK hash mismatch" },

      { generate_key: { alg: { name: "HMAC",
                               hash: "SHA-256" },
                        extractable: true,
                        usage: [ "sign", "verify" ] },
        check: validate_hmac_jwk,
        expected: { key_ops: [ "sign", "verify" ], alg: "HS256" } },
      { generate_key: { alg: { name: "HMAC",
                               hash: "SHA-1" },
                        extractable: true,
                        usage: [ "verify" ] },
        check: validate_hmac_jwk,
        expected: { key_ops: [ "verify" ], alg: "HS1" } },
      { generate_key: { alg: { name: "HMAC",
                               hash: "SHA-384" },
                        extractable: true,
                        usage: [ "sign" ] },
        check: validate_hmac_jwk,
        expected: { key_ops: [ "sign" ], alg: "HS384" } },
      { generate_key: { alg: { name: "HMAC",
                               hash: "SHA-512" },
                        extractable: true,
                        usage: [ "sign" ] },
        check: validate_hmac_jwk,
        expected: { key_ops: [ "sign" ], alg: "HS512" } },
]};

function validate_aes_jwk(exp, params) {
    let expected_len = params.generate_key.alg.length;
    expected_len = expected_len / 8 * (4 / 3);
    expected_len = Math.round(expected_len);

    validate_property(exp, 'k', expected_len);

    return true;
}

let aes_tsuite = {
    name: "AES exporting",
    skip: () => (!has_webcrypto()),
    T: test,
    prepare_args: p,
    opts: {
        key: { fmt: "raw",
               key: "ABEiMwARIjMAESIzABEiMw",
               alg: { name: "AES-GCM" },
               extractable: true,
               usage: [ "encrypt" ] },
        export: { fmt: "jwk" },
        expected: { kty: "oct", ext: true },
    },

    tests: [
      { expected: { key_ops: [ "encrypt" ],
                    alg: "A128GCM",
                    k: "ABEiMwARIjMAESIzABEiMw" } },
      { export: { fmt: "raw" },
        expected: "ArrayBuffer:ABEiMwARIjMAESIzABEiMw" },
      { key: { key: "ABEiMwARIjMAESIzABEiMwARIjMAESIz",
               alg: { name: "AES-CBC" },
               usage: [ "decrypt" ] },
        expected: { key_ops: [ "decrypt" ],
                    alg: "A192CBC",
                    k: "ABEiMwARIjMAESIzABEiMwARIjMAESIz" } },
      { key: { key: "ABEiMwARIjMAESIzABEiMwARIjMAESIz",
               alg: { name: "AES-CBC" },
               usage: [ "decrypt" ] },
        export: { fmt: "raw" },
        expected: "ArrayBuffer:ABEiMwARIjMAESIzABEiMwARIjMAESIz" },
      { key: { key: "ABEiMwARIjMAESIzABEiMwARIjMAESIzABEiMwARIjM",
               alg: { name: "AES-CTR" },
               usage: [ "decrypt" ] },
        expected: { key_ops: [ "decrypt" ],
                    alg: "A256CTR",
                    k: "ABEiMwARIjMAESIzABEiMwARIjMAESIzABEiMwARIjM" } },
      { key: { key: "ABEiMwARIjMAESIzABEiMwARIjMAESIzABEiMwARIjM",
               alg: { name: "AES-CTR" },
               usage: [ "decrypt" ] },
        export: { fmt: "raw" },
        expected: "ArrayBuffer:ABEiMwARIjMAESIzABEiMwARIjMAESIzABEiMwARIjM" },

      { generate_key: { alg: { name: "AES-GCM", length: 128 },
                        extractable: true,
                        usage: [ "encrypt" ] },
        check: validate_aes_jwk,
        expected: { key_ops: [ "encrypt" ], alg: "A128GCM" } },
      { generate_key: { alg: { name: "AES-CTR", length: 192 },
                        extractable: true,
                        usage: [ "decrypt" ] },
        check: validate_aes_jwk,
        expected: { key_ops: [ "decrypt" ], alg: "A192CTR" } },
      { generate_key: { alg: { name: "AES-CBC", length: 256 },
                        extractable: true,
                        usage: [ "decrypt" ] },
        check: validate_aes_jwk,
        expected: { key_ops: [ "decrypt" ], alg: "A256CBC" } },
      { generate_key: { alg: { name: "AES-GCM", length: 128 },
                        extractable: false,
                        usage: [ "decrypt" ] },
        exception: "TypeError: provided key cannot be extracted" },
      { generate_key: { alg: { name: "AES-GCM" },
                        extractable: false,
                        usage: [ "decrypt" ] },
        exception: "TypeError: length for \"AES-GCM\" key should be one of 128, 192, 256" },
      { generate_key: { alg: { name: "AES-GCM", length: 25 },
                        extractable: false,
                        usage: [ "decrypt" ] },
        exception: "TypeError: length for \"AES-GCM\" key should be one of 128, 192, 256" },
]};

run([
    rsa_tsuite,
    ec_tsuite,
    hmac_tsuite,
    aes_tsuite,
])
.then($DONE, $DONE);
