current commit
260805.9 · test: update WPT for WebCryptoAPI to 82c3d9069c
// META: title=WebCryptoAPI: importKey() for EC keys
// META: timeout=long
// META: script=../util/helpers.js
// META: script=../util/ec_key_fixtures.js

// Test importKey and exportKey for EC algorithms. Only "happy paths" are
// currently tested - those where the operation should succeed.

    var subtle = crypto.subtle;

    var curves = ['P-256', 'P-384', 'P-521'];

    var keyData = ecKeyData;

    // combinations to test
    var testVectors = [
        {name: "ECDSA", privateUsages: ["sign"], publicUsages: ["verify"]},
        {name: "ECDH",  privateUsages: ["deriveKey", "deriveBits"], publicUsages: []}
    ];

    // TESTS ARE HERE:
    // Test every test vector, along with all available key data
    testVectors.forEach(function(vector) {
        curves.forEach(function(curve) {

            [true, false].forEach(function(extractable) {

                // Test public keys first
                allValidUsages(vector.publicUsages, true).forEach(function(usages) {
                    ['spki', 'spki_compressed', 'jwk', 'raw', 'raw_compressed'].forEach(function(format) {
                        var algorithm = {name: vector.name, namedCurve: curve};
                        var data = keyData[curve];
                        if (format === "jwk") { // Not all fields used for public keys
                            data = {jwk: {kty: keyData[curve].jwk.kty, crv: keyData[curve].jwk.crv, x: keyData[curve].jwk.x, y: keyData[curve].jwk.y}};
                        }

                        testFormat(format, algorithm, data, curve, usages, extractable);
                        if (vector.name === 'ECDH' && format === 'jwk') {
                            testEcdhJwkAlg(algorithm, { ...data.jwk, alg: 'any alg works here' }, curve, usages, extractable);
                        }
                    });

                });

                // Next, test private keys
                ['pkcs8', 'jwk'].forEach(function(format) {
                    var algorithm = {name: vector.name, namedCurve: curve};
                    var data = keyData[curve];
                    allValidUsages(vector.privateUsages).forEach(function(usages) {
                        testFormat(format, algorithm, data, curve, usages, extractable);
                        if (vector.name === 'ECDH' && format === 'jwk') {
                            testEcdhJwkAlg(algorithm, { ...data.jwk, alg: 'any alg works here' }, curve, usages, extractable);
                        }
                    });
                    testEmptyUsages(format, algorithm, data, curve, extractable);
                });
            });

            {
                var algorithm = {name: vector.name, namedCurve: curve};
                var data = keyData[curve];
                allValidUsages(vector.privateUsages).forEach(function(usages) {
                    testPkcs8PrivateOnly(algorithm, data, curve, usages);
                });
            }
        });
    });


    // Test importKey with a given key format and other parameters. If
    // extrable is true, export the key and verify that it matches the input.
    function testFormat(format, algorithm, data, keySize, usages, extractable) {
        const keyData = data[format];
        const compressed = format.endsWith("_compressed");
        if (compressed) {
            [format] = format.split("_compressed");
        }
        promise_test(function(test) {
            return subtle.importKey(format, keyData, algorithm, extractable, usages).
            then(function(key) {
                assert_equals(key.constructor, CryptoKey, "Imported a CryptoKey object");
                assert_goodCryptoKey(key, algorithm, extractable, usages, (format === 'pkcs8' || (format === 'jwk' && keyData.d)) ? 'private' : 'public');
                if (!extractable) {
                    return;
                }

                return subtle.exportKey(format, key).
                then(function(result) {
                    if (format !== "jwk") {
                        assert_true(equalBuffers(data[format], result), "Round trip works");
                    } else {
                        assert_true(equalJwk(data[format], result), "Round trip works");
                    }
                }, function(err) {
                    assert_unreached("Threw an unexpected error: " + err.toString());
                });
            }, function(err) {
                if (compressed && err.name === "DataError") {
                    assert_implements_optional(false, "Compressed point format not supported: " + err.toString());
                } else {
                    assert_unreached("Threw an unexpected error: " + err.toString());
                }
            });
        }, "Good parameters: " + keySize.toString() + " bits " + parameterString(format, compressed, keyData, algorithm, extractable, usages));
    }

    // Test importKey with a given key format and other parameters but with empty usages.
    // Should fail with SyntaxError
    function testEmptyUsages(format, algorithm, data, keySize, extractable) {
        const keyData = data[format];
        const usages = [];
        promise_test(function(test) {
            return subtle.importKey(format, keyData, algorithm, extractable, usages).
            then(function(key) {
                assert_unreached("importKey succeeded but should have failed with SyntaxError");
            }, function(err) {
                assert_equals(err.name, "SyntaxError", "Should throw correct error, not " + err.name + ": " + err.message);
            });
        }, "Empty Usages: " + keySize.toString() + " bits " + parameterString(format, false, keyData, algorithm, extractable, usages));
    }

    // Test ECDH importKey with a JWK format
    // Should succeed with any "alg" value
    function testEcdhJwkAlg(algorithm, keyData, keySize, usages, extractable) {
        const format = "jwk";
        promise_test(function(test) {
            return subtle.importKey(format, keyData, algorithm, extractable, usages).
            then(function(key) {
                assert_equals(key.constructor, CryptoKey, "Imported a CryptoKey object");
                assert_goodCryptoKey(key, algorithm, extractable, usages, keyData.d ? 'private' : 'public');
            }, function(err) {
                assert_unreached("Threw an unexpected error: " + err.toString());
            });
        }, "ECDH any JWK alg: " + keySize.toString() + " bits " + parameterString(format, false, keyData, algorithm, extractable, usages));
    }

    // Test importKey with pkcs8 without public key
    // Should succeed in import, re-export as pkcs8, and export as jwk
    function testPkcs8PrivateOnly(algorithm, data, keySize, usages) {
        const format = "pkcs8";
        const keyData = data.pkcs8_private_only;
        promise_test(function(test) {
            return subtle.importKey(format, keyData, algorithm, true, usages).
            then(function(key) {
                assert_equals(key.constructor, CryptoKey, "Imported a CryptoKey object");
                assert_goodCryptoKey(key, algorithm, true, usages, 'private');

                // Test re-export as pkcs8
                return subtle.exportKey(format, key).
                then(function(result) {
                    assert_true(result instanceof ArrayBuffer, "Re-exported pkcs8 is an ArrayBuffer");
                    const equal = equalBuffers(result, keyData.buffer) || equalBuffers(result, data.pkcs8.buffer);
                    assert_true(equal, "Round trip works");
                    // Test export as jwk
                    return subtle.exportKey('jwk', key);
                }).then(function(jwkResult) {
                    assert_equals(jwkResult.kty, "EC", "Exported JWK has correct kty");
                    assert_equals(jwkResult.crv, algorithm.namedCurve, "Exported JWK has correct crv");
                    assert_true('x' in jwkResult, "Exported JWK has x");
                    assert_true('y' in jwkResult, "Exported JWK has y");
                    assert_true('d' in jwkResult, "Exported JWK has d");
                }, function(err) {
                    assert_unreached("Export threw an unexpected error: " + err.toString());
                });
            }, function(err) {
                assert_unreached("Import threw an unexpected error: " + err.toString());
            });
        }, "PKCS8 private-only: " + keySize.toString() + " bits " + parameterString(format, false, keyData, algorithm, true, usages));
    }



    // Helper methods follow:

    // Build minimal Jwk objects from raw key data and algorithm specifications
    function jwkData(keyData, algorithm) {
        var result = {
            kty: "oct",
            k: byteArrayToUnpaddedBase64(keyData)
        };

        if (algorithm.name.substring(0, 3) === "AES") {
            result.alg = "A" + (8 * keyData.byteLength).toString() + algorithm.name.substring(4);
        } else if (algorithm.name === "HMAC") {
            result.alg = "HS" + algorithm.hash.substring(4);
        }
        return result;
    }

    // Convert method parameters to a string to uniquely name each test
    function parameterString(format, compressed, data, algorithm, extractable, usages) {
        if ("byteLength" in data) {
            data = "buffer(" + data.byteLength.toString() + (compressed ? ", compressed" : "") + ")";
        } else {
            data = "object(" + Object.keys(data).join(", ") + ")";
        }
        var result = "(" +
                        objectToString(format) + ", " +
                        objectToString(data) + ", " +
                        objectToString(algorithm) + ", " +
                        objectToString(extractable) + ", " +
                        objectToString(usages) +
                     ")";

        return result;
    }