[GCrypt] ECDH bit derivation support
authorzandobersek@gmail.com <zandobersek@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 2 May 2017 06:34:03 +0000 (06:34 +0000)
committerzandobersek@gmail.com <zandobersek@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 2 May 2017 06:34:03 +0000 (06:34 +0000)
https://bugs.webkit.org/show_bug.cgi?id=171070

Reviewed by Michael Catanzaro.

Source/WebCore:

Implement bit derivation support for the ECDH algorithm for configurations
that use libgcrypt.

This is done by retrieving the private key data and wrapping it in a new
data s-expression. That's then encrypted with the public key, and the
returned s-expression contains the shared point data. That data is then
decoded into an EC point, from which the x-coordinate is retrieved. This
coordinate data is finally our bit derivation result.

* crypto/gcrypt/CryptoAlgorithmECDHGCrypt.cpp:
(WebCore::gcryptDerive):
(WebCore::CryptoAlgorithmECDH::platformDeriveBits):

Source/WebCore/PAL:

Add PAL::GCrypt::HandleDeleter specialization for the
gcry_mpi_point_t type.

* pal/crypto/gcrypt/Handle.h:
(PAL::GCrypt::HandleDeleter<gcry_mpi_point_t>::operator()):

LayoutTests:

The crypto/subtle/ecdh-derive-bits-length-limits.html test is added, testing the
corner-case length values for which the bit derivation operation must succeed or
fail. When specifying 0 as the length, the returned result must match the EC key
size in length. 8, tested as the minimum non-zero value, and the key size for
each key must also correctly resolve and return a resulting ArrayBuffer whose
length in bytes must match the requested length. Derivations for byte-aligned
values should start rejecting immediately for length values that are bigger than
the EC key's length.

* crypto/subtle/ecdh-derive-bits-length-limits-expected.txt: Added.
* crypto/subtle/ecdh-derive-bits-length-limits.html: Added.
* platform/gtk/TestExpectations: Enable Web Crypto ECDH tests under crypto/subtle/
that are passing now that the proper implementation has been added. The remaining
failing ECDH tests use PKCS #8 and SPKI key import/export formats, which are not
supported yet.

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@216060 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/crypto/subtle/ecdh-derive-bits-length-limits-expected.txt [new file with mode: 0644]
LayoutTests/crypto/subtle/ecdh-derive-bits-length-limits.html [new file with mode: 0644]
LayoutTests/platform/gtk/TestExpectations
Source/WebCore/ChangeLog
Source/WebCore/PAL/ChangeLog
Source/WebCore/PAL/pal/crypto/gcrypt/Handle.h
Source/WebCore/crypto/gcrypt/CryptoAlgorithmECDHGCrypt.cpp

index 866d51f..dbf491c 100644 (file)
@@ -1,3 +1,26 @@
+2017-05-01  Zan Dobersek  <zdobersek@igalia.com>
+
+        [GCrypt] ECDH bit derivation support
+        https://bugs.webkit.org/show_bug.cgi?id=171070
+
+        Reviewed by Michael Catanzaro.
+
+        The crypto/subtle/ecdh-derive-bits-length-limits.html test is added, testing the
+        corner-case length values for which the bit derivation operation must succeed or
+        fail. When specifying 0 as the length, the returned result must match the EC key
+        size in length. 8, tested as the minimum non-zero value, and the key size for
+        each key must also correctly resolve and return a resulting ArrayBuffer whose
+        length in bytes must match the requested length. Derivations for byte-aligned
+        values should start rejecting immediately for length values that are bigger than
+        the EC key's length.
+
+        * crypto/subtle/ecdh-derive-bits-length-limits-expected.txt: Added.
+        * crypto/subtle/ecdh-derive-bits-length-limits.html: Added.
+        * platform/gtk/TestExpectations: Enable Web Crypto ECDH tests under crypto/subtle/
+        that are passing now that the proper implementation has been added. The remaining
+        failing ECDH tests use PKCS #8 and SPKI key import/export formats, which are not
+        supported yet.
+
 2017-05-01  Saam Barati  <sbarati@apple.com>
 
         REGRESSION: LayoutTest workers/wasm-hashset-many.html is a flaky timeout
diff --git a/LayoutTests/crypto/subtle/ecdh-derive-bits-length-limits-expected.txt b/LayoutTests/crypto/subtle/ecdh-derive-bits-length-limits-expected.txt
new file mode 100644 (file)
index 0000000..d31b252
--- /dev/null
@@ -0,0 +1,19 @@
+Test ECDH deriveBits operation for corner-case length values.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS deriveBits(..., 0) successfully derived 256 bits for a P-256 curve
+PASS deriveBits(..., 8) successfully derived 8 bits for a P-256 curve
+PASS deriveBits(..., 256) successfully derived 256 bits for a P-256 curve
+PASS Bit derivations for EC P-256 with minimum and maximum lengths succeeded
+PASS deriveBits(P256, 256 + 8) rejected promise  with OperationError (DOM Exception 34): The operation failed for an operation-specific reason.
+PASS deriveBits(..., 0) successfully derived 384 bits for a P-384 curve
+PASS deriveBits(..., 8) successfully derived 8 bits for a P-384 curve
+PASS deriveBits(..., 384) successfully derived 384 bits for a P-384 curve
+PASS Bit derivations for EC P-384 with minimum and maximum lengths succeeded
+PASS deriveBits(P384, 384 + 8) rejected promise  with OperationError (DOM Exception 34): The operation failed for an operation-specific reason.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/crypto/subtle/ecdh-derive-bits-length-limits.html b/LayoutTests/crypto/subtle/ecdh-derive-bits-length-limits.html
new file mode 100644 (file)
index 0000000..719945c
--- /dev/null
@@ -0,0 +1,98 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/common.js"></script>
+</head>
+<body>
+<p id="description"></p>
+<div id="console"></div>
+
+<script>
+description("Test ECDH deriveBits operation for corner-case length values.");
+
+jsTestIsAsync = true;
+
+var extractable = true;
+var jwkPrivateKeyP256 = {
+    kty: "EC",
+    crv: "P-256",
+    x: "FwdrcvxdtrNOUpB62zEcz1-3QfjsCa1zruXCLbO_FPw",
+    y: "CCInS0rHpEVdk24HtBqZxNhKojYcD7orq2AA7Wp2NhA",
+    d: "0hdPU8a1XmA_EVSflLVW4BqiMqtn5hreJN18h4dFRb8",
+};
+var jwkPublicKeyP256 = {
+    kty: "EC",
+    crv: "P-256",
+    x: "FwdrcvxdtrNOUpB62zEcz1-3QfjsCa1zruXCLbO_FPw",
+    y: "CCInS0rHpEVdk24HtBqZxNhKojYcD7orq2AA7Wp2NhA"
+};
+var jwkPrivateKeyP384 = {
+    kty: "EC",
+    crv: "P-384",
+    x: "H9zo2G4WwHFSUqdN7jaChKg5fdmBxRLMP-_xWbcGDd4GzQBZBS72qKU_W-NLMoh_",
+    y: "-VBlr88R42IGLDBvzFeUvH1cjPOlIxWgnfa6NzhKcYZQXFi9vFq4sdJX5cQfnc6H",
+    d: "U8zJ5-tgWY2iAPf_2fML48SXrekRUO7rjOphRiCt450-mEIqJ0j5pCyKCtJsd-M4",
+};
+var jwkPublicKeyP384 = {
+    kty: "EC",
+    crv: "P-384",
+    x: "H9zo2G4WwHFSUqdN7jaChKg5fdmBxRLMP-_xWbcGDd4GzQBZBS72qKU_W-NLMoh_",
+    y: "-VBlr88R42IGLDBvzFeUvH1cjPOlIxWgnfa6NzhKcYZQXFi9vFq4sdJX5cQfnc6H",
+};
+
+var P256 = { curveName: "P-256" };
+var P384 = { curveName: "P-384" };
+
+crypto.subtle.importKey("jwk", jwkPrivateKeyP256, { name: "ECDH", namedCurve: "P-256" }, extractable, ["deriveBits"]).then(function(result) {
+    P256.privateKey = result;
+    return crypto.subtle.importKey("jwk", jwkPublicKeyP256, { name: "ECDH", namedCurve: "P-256" }, extractable, [ ]);
+}).then(function(result) {
+    P256.publicKey = result;
+    return crypto.subtle.importKey("jwk", jwkPrivateKeyP384, { name: "ECDH", namedCurve: "P-384" }, extractable, ["deriveBits"]);
+}).then(function(result) {
+    P384.privateKey = result;
+    return crypto.subtle.importKey("jwk", jwkPublicKeyP384, { name: "ECDH", namedCurve: "P-384" }, extractable, [ ]);
+}).then(function(result) {
+    P384.publicKey = result;
+
+    deriveBits = function(keyData, length, expectedLength) {
+        return crypto.subtle.deriveBits({ name: "ecdh", public: keyData.publicKey }, keyData.privateKey, length).then(function(result) {
+            if (result.byteLength * 8 != expectedLength)
+                return Promise.reject();
+
+            testPassed("deriveBits(..., " + length + ") successfully derived " + expectedLength + " bits for a " + keyData.curveName + " curve");
+            return Promise.resolve();
+        });
+    };
+
+    // For each of the supported curves, we check that
+
+    return Promise.resolve().then(function(result) {
+        // P-256
+        return Promise.all([
+            deriveBits(P256, 0, 256),
+            deriveBits(P256, 8, 8),
+            deriveBits(P256, 256, 256),
+        ]).then(function(result) {
+            testPassed("Bit derivations for EC P-256 with minimum and maximum lengths succeeded");
+            return shouldReject('deriveBits(P256, 256 + 8)');
+        });
+    }).then(function(result) {
+        // P-384
+        return Promise.all([
+            deriveBits(P384, 0, 384),
+            deriveBits(P384, 8, 8),
+            deriveBits(P384, 384, 384),
+        ]).then(function(result) {
+            testPassed("Bit derivations for EC P-384 with minimum and maximum lengths succeeded");
+            return shouldReject('deriveBits(P384, 384 + 8)');
+        });
+    });
+}).then(finishJSTest, finishJSTest);
+
+</script>
+
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
index 8eb4d8f..0d6b5df 100644 (file)
@@ -724,6 +724,29 @@ Bug(GTK) editing/secure-input [ Failure ]
 webkit.org/b/133122 crypto/subtle [ Skip ]
 webkit.org/b/133122 crypto/webkitSubtle [ Skip ]
 webkit.org/b/133122 crypto/workers/subtle [ Skip ]
+crypto/subtle/ecdh-derive-bits-length-limits.html [ Pass ]
+crypto/subtle/ecdh-generate-export-jwk-key-p256.html [ Pass ]
+crypto/subtle/ecdh-generate-export-jwk-key-p384.html [ Pass ]
+crypto/subtle/ecdh-generate-export-key-raw-p256.html [ Pass ]
+crypto/subtle/ecdh-generate-export-key-raw-p384.html [ Pass ]
+crypto/subtle/ecdh-generate-key-derive-bits.html [ Pass ]
+crypto/subtle/ecdh-generate-key-extractable.html [ Pass ]
+crypto/subtle/ecdh-generate-key-p256.html [ Pass ]
+crypto/subtle/ecdh-generate-key-p384.html [ Pass ]
+crypto/subtle/ecdh-generate-key-single-usage.html [ Pass ]
+crypto/subtle/ecdh-import-jwk-key-minimum.html [ Pass ]
+crypto/subtle/ecdh-import-jwk-private-key-p256.html [ Pass ]
+crypto/subtle/ecdh-import-jwk-private-key-p384.html [ Pass ]
+crypto/subtle/ecdh-import-jwk-public-key-p256.html [ Pass ]
+crypto/subtle/ecdh-import-jwk-public-key-p384.html [ Pass ]
+crypto/subtle/ecdh-import-key-derive-aes-key.html [ Pass ]
+crypto/subtle/ecdh-import-key-derive-bits-custom-length.html [ Pass ]
+crypto/subtle/ecdh-import-key-derive-bits-null-length.html [ Pass ]
+crypto/subtle/ecdh-import-key-derive-hkdf-key.html [ Pass ]
+crypto/subtle/ecdh-import-key-derive-hmac-key-custom-length.html [ Pass ]
+crypto/subtle/ecdh-import-key-derive-pbkdf2-key.html [ Pass ]
+crypto/subtle/ecdh-import-raw-key-p256.html [ Pass ]
+crypto/subtle/ecdh-import-raw-key-p384.html [ Pass ]
 crypto/subtle/hmac-export-key-malformed-parameters.html [ Pass ]
 crypto/subtle/hmac-generate-export-key-jwk-sha1.html [ Pass ]
 crypto/subtle/hmac-generate-export-key-jwk-sha224.html [ Pass ]
index dfecdc4..e13957f 100644 (file)
@@ -1,3 +1,23 @@
+2017-05-01  Zan Dobersek  <zdobersek@igalia.com>
+
+        [GCrypt] ECDH bit derivation support
+        https://bugs.webkit.org/show_bug.cgi?id=171070
+
+        Reviewed by Michael Catanzaro.
+
+        Implement bit derivation support for the ECDH algorithm for configurations
+        that use libgcrypt.
+
+        This is done by retrieving the private key data and wrapping it in a new
+        data s-expression. That's then encrypted with the public key, and the
+        returned s-expression contains the shared point data. That data is then
+        decoded into an EC point, from which the x-coordinate is retrieved. This
+        coordinate data is finally our bit derivation result.
+
+        * crypto/gcrypt/CryptoAlgorithmECDHGCrypt.cpp:
+        (WebCore::gcryptDerive):
+        (WebCore::CryptoAlgorithmECDH::platformDeriveBits):
+
 2017-05-01  Youenn Fablet  <youenn@apple.com>
 
         Ensure RealtimeOutgoingVideoSource sends a black frame when its related source is muted
index 064f3a7..acfd990 100644 (file)
@@ -1,3 +1,16 @@
+2017-05-01  Zan Dobersek  <zdobersek@igalia.com>
+
+        [GCrypt] ECDH bit derivation support
+        https://bugs.webkit.org/show_bug.cgi?id=171070
+
+        Reviewed by Michael Catanzaro.
+
+        Add PAL::GCrypt::HandleDeleter specialization for the
+        gcry_mpi_point_t type.
+
+        * pal/crypto/gcrypt/Handle.h:
+        (PAL::GCrypt::HandleDeleter<gcry_mpi_point_t>::operator()):
+
 2017-04-25  Daniel Bates  <dabates@apple.com>
 
         [Cocoa][Win] Enable of X-Content-Type-Options: nosniff header
index 5caf9c3..4bd18be 100644 (file)
@@ -115,6 +115,14 @@ struct HandleDeleter<gcry_mpi_t> {
 };
 
 template<>
+struct HandleDeleter<gcry_mpi_point_t> {
+    void operator()(gcry_mpi_point_t handle)
+    {
+        gcry_mpi_point_release(handle);
+    }
+};
+
+template<>
 struct HandleDeleter<gcry_sexp_t> {
     void operator()(gcry_sexp_t handle)
     {
index afe6a12..c5e6d66 100644 (file)
@@ -1,5 +1,7 @@
 /*
  * Copyright (C) 2017 Apple Inc. All rights reserved.
+ * Copyright (C) 2017 Metrological Group B.V.
+ * Copyright (C) 2017 Igalia S.L.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
 
 #if ENABLE(SUBTLE_CRYPTO)
 
-#include "NotImplemented.h"
+#include "CryptoKeyEC.h"
+#include "ScriptExecutionContext.h"
+#include <pal/crypto/gcrypt/Handle.h>
+#include <pal/crypto/gcrypt/Utilities.h>
 
 namespace WebCore {
 
-void CryptoAlgorithmECDH::platformDeriveBits(Ref<CryptoKey>&&, Ref<CryptoKey>&&, size_t, Callback&&, ScriptExecutionContext&, WorkQueue&)
+static std::optional<Vector<uint8_t>> gcryptDerive(gcry_sexp_t baseKeySexp, gcry_sexp_t publicKeySexp)
 {
-    notImplemented();
+    // First, retrieve private key data, which is roughly of the following form:
+    // (private-key
+    //   (ecc
+    //     ...
+    //     (d ...)))
+    PAL::GCrypt::Handle<gcry_sexp_t> dataSexp;
+    {
+        PAL::GCrypt::Handle<gcry_sexp_t> dSexp(gcry_sexp_find_token(baseKeySexp, "d", 0));
+        if (!dSexp)
+            return std::nullopt;
+
+        size_t dataLength = 0;
+        const char* data = gcry_sexp_nth_data(dSexp, 1, &dataLength);
+        if (!data)
+            return std::nullopt;
+
+        gcry_sexp_build(&dataSexp, nullptr, "(data(flags raw)(value %b))", dataLength, data);
+        if (!dataSexp)
+            return std::nullopt;
+    }
+
+    // Encrypt the data s-expression with the public key.
+    PAL::GCrypt::Handle<gcry_sexp_t> cipherSexp;
+    gcry_error_t error = gcry_pk_encrypt(&cipherSexp, dataSexp, publicKeySexp);
+    if (error != GPG_ERR_NO_ERROR) {
+        PAL::GCrypt::logError(error);
+        return std::nullopt;
+    }
+
+    // Retrieve the shared point value from the generated s-expression, which is of the following form:
+    // (enc-val
+    //   (ecdh
+    //     (s ...)
+    //     (e ...)))
+    Vector<uint8_t> output;
+    {
+        PAL::GCrypt::Handle<gcry_sexp_t> sSexp(gcry_sexp_find_token(cipherSexp, "s", 0));
+        if (!sSexp)
+            return std::nullopt;
+
+        PAL::GCrypt::Handle<gcry_mpi_t> sMPI(gcry_sexp_nth_mpi(sSexp, 1, GCRYMPI_FMT_USG));
+        if (!sMPI)
+            return std::nullopt;
+
+        PAL::GCrypt::Handle<gcry_mpi_point_t> point(gcry_mpi_point_new(0));
+        if (!point)
+            return std::nullopt;
+
+        error = gcry_mpi_ec_decode_point(point, sMPI, nullptr);
+        if (error != GPG_ERR_NO_ERROR)
+            return std::nullopt;
+
+        PAL::GCrypt::Handle<gcry_mpi_t> xMPI(gcry_mpi_new(0));
+        if (!xMPI)
+            return std::nullopt;
+
+        // We're only interested in the x-coordinate.
+        gcry_mpi_point_snatch_get(xMPI, nullptr, nullptr, point.release());
+
+        size_t dataLength = 0;
+        error = gcry_mpi_print(GCRYMPI_FMT_USG, nullptr, 0, &dataLength, xMPI);
+        if (error != GPG_ERR_NO_ERROR) {
+            PAL::GCrypt::logError(error);
+            return std::nullopt;
+        }
+
+        output.resize(dataLength);
+        error = gcry_mpi_print(GCRYMPI_FMT_USG, output.data(), output.size(), nullptr, xMPI);
+        if (error != GPG_ERR_NO_ERROR) {
+            PAL::GCrypt::logError(error);
+            return std::nullopt;
+        }
+    }
+
+    return output;
+}
+
+void CryptoAlgorithmECDH::platformDeriveBits(Ref<CryptoKey>&& baseKey, Ref<CryptoKey>&& publicKey, size_t length, Callback&& callback, ScriptExecutionContext& context, WorkQueue& workQueue)
+{
+    context.ref();
+    workQueue.dispatch(
+        [baseKey = WTFMove(baseKey), publicKey = WTFMove(publicKey), length, callback = WTFMove(callback), &context]() mutable {
+            auto& ecBaseKey = downcast<CryptoKeyEC>(baseKey.get());
+            auto& ecPublicKey = downcast<CryptoKeyEC>(publicKey.get());
+
+            auto output = gcryptDerive(ecBaseKey.platformKey(), ecPublicKey.platformKey());
+            context.postTask(
+                [output = WTFMove(output), length, callback = WTFMove(callback)](ScriptExecutionContext& context) mutable {
+                    callback(WTFMove(output), length);
+                    context.deref();
+                });
+        });
 }
 
 } // namespace WebCore