[GCrypt] Implement CryptoKeyEC PKCS#8 imports
authorzandobersek@gmail.com <zandobersek@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 4 Aug 2017 06:13:23 +0000 (06:13 +0000)
committerzandobersek@gmail.com <zandobersek@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 4 Aug 2017 06:13:23 +0000 (06:13 +0000)
https://bugs.webkit.org/show_bug.cgi?id=173647

Reviewed by Jiewen Tan.

Source/WebCore:

No new tests -- affected tests are now passing and are unskipped.

Implement libgcrypt-based support for PKCS#8 imports of EC keys.

Existing libtasn1 utilities are used to achieve this. First, the provided key data
is decoded against the PrivateKeyInfo ASN.1 definition. First, the version member
of that structure is validated, followed by the algorithm member. The latter is
also properly tested depending on this being an import of an ECDSA or ECDH key.

Data of the parameters member is decoded against the ECParameters ASN.1 definition,
and the namedCurve object identifier is validated, making sure it represents a
valid EC curve and that this curve maches the one specified for the import
operation.

Data of the privateKey member is decoded against the ECPrivateKey ASN.1 definition.
The version member of that structure is properly validated. The optional parameters
member of that structure is already decoded against the ECParameters ASN.1
definition. If present, it is checked to contain a valid EC curve identifier that
matches the specified curve.

The optional publicKey member of the ECPrivateKey structure is validated, testing
that its data matches in size an uncompressed EC point, and that the first byte
of this data is 0x04, as expected for an uncompressed EC point.

What's left is the private key data on the initial ECPrivateKey structure. That
data is retrieved and validated, making sure its size matches the size of the
specified curve. The `private-key` s-expression is then constructed, embedding
the curve name and the validated private key data. This s-expression is then used
to construct an EC context.

If the optional publicKey data was provided, it's used to set the `q` parameter
for this EC context. Otherwise, the value for `q` is computed on-the-fly for the
specified EC and the provided private key. The `q` point is then tested through
the gcry_mpi_ec_curve_point() function, making sure that the derived point is
indeed located on the given EC.

Finally, with the private key properly validated, a new CryptoKeyEC object is
constructed, using the `private-key` s-expression and the parameters that were
specified for this import operation.

* crypto/gcrypt/CryptoKeyECGCrypt.cpp:
(WebCore::CryptoKeyEC::platformImportPkcs8):
* crypto/gcrypt/GCryptUtilities.h:

LayoutTests:

* platform/wpe/TestExpectations:
Unskip passing Web Crypto tests that cover PKCS#8 imports of EC keys.

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

LayoutTests/ChangeLog
LayoutTests/platform/wpe/TestExpectations
Source/WebCore/ChangeLog
Source/WebCore/crypto/gcrypt/CryptoKeyECGCrypt.cpp
Source/WebCore/crypto/gcrypt/GCryptUtilities.h

index bf5e941136df2671dd465dde392d1805c111f95e..d616e169e3d8241cde47219c8478dc92c3ea87bd 100644 (file)
@@ -1,3 +1,13 @@
+2017-08-03  Zan Dobersek  <zdobersek@igalia.com>
+
+        [GCrypt] Implement CryptoKeyEC PKCS#8 imports
+        https://bugs.webkit.org/show_bug.cgi?id=173647
+
+        Reviewed by Jiewen Tan.
+
+        * platform/wpe/TestExpectations:
+        Unskip passing Web Crypto tests that cover PKCS#8 imports of EC keys.
+
 2017-08-03  Chris Dumez  <cdumez@apple.com>
 
         Fix parsing of <meta http-equiv=refresh> to allow time starting with a '.' without a leading 0
index 6c812341f70acbf51f587e6910004da4532fabbf..646749a633f17c49b08578eb37e37a34ec5c0a9c 100644 (file)
@@ -647,12 +647,6 @@ webkit.org/b/133122 crypto/subtle/aes-cfb-import-key-unwrap-raw-key.html [ Skip
 webkit.org/b/133122 crypto/subtle/aes-cfb-import-key-wrap-jwk-key.html [ Skip ]
 webkit.org/b/133122 crypto/subtle/aes-cfb-import-key-wrap-raw-key.html [ Skip ]
 webkit.org/b/133122 crypto/subtle/aes-cfb-import-raw-key.html [ Skip ]
-webkit.org/b/133122 crypto/subtle/ecdh-import-pkcs8-key-p256.html [ Skip ]
-webkit.org/b/133122 crypto/subtle/ecdh-import-pkcs8-key-p384.html [ Skip ]
-webkit.org/b/133122 crypto/subtle/ecdsa-import-pkcs8-key.html [ Skip ]
-webkit.org/b/133122 crypto/subtle/ec-import-pkcs8-key-export-jwk-key.html [ Skip ]
-webkit.org/b/133122 crypto/subtle/ec-import-pkcs8-key-export-pkcs8-key-p256.html [ Skip ]
-webkit.org/b/133122 crypto/subtle/ec-import-pkcs8-key-export-pkcs8-key-p384.html [ Skip ]
 webkit.org/b/133122 crypto/subtle/rsa-indexeddb-non-exportable-private.html [ Skip ]
 webkit.org/b/133122 crypto/subtle/rsa-indexeddb-non-exportable.html [ Skip ]
 webkit.org/b/133122 crypto/subtle/rsa-indexeddb-private.html [ Skip ]
@@ -661,7 +655,6 @@ webkit.org/b/133122 crypto/workers/subtle/aes-cfb-import-key-decrypt.html [ Skip
 webkit.org/b/133122 crypto/workers/subtle/aes-cfb-import-key-encrypt.html [ Skip ]
 webkit.org/b/133122 crypto/workers/subtle/aes-cfb-import-key-unwrap-key.html [ Skip ]
 webkit.org/b/133122 crypto/workers/subtle/aes-cfb-import-key-wrap-key.html [ Skip ]
-webkit.org/b/133122 crypto/workers/subtle/ec-import-pkcs8-key.html [ Skip ]
 webkit.org/b/133122 crypto/workers/subtle/ec-postMessage-worker.html [ Skip ]
 
 imported/w3c/web-platform-tests/WebCryptoAPI [ Pass Slow ]
@@ -683,6 +676,10 @@ webkit.org/b/133122 imported/w3c/web-platform-tests/WebCryptoAPI/wrapKey_unwrapK
 webkit.org/b/133122 imported/w3c/web-platform-tests/WebCryptoAPI/generateKey/successes.worker.html [ Failure ]
 
 # RSA-PSS tests are for now skipped on all ports, so we for now explicitly enable the passing ones here.
+crypto/subtle/ecdh-import-pkcs8-key-p256-validate-ecprivatekey-parameters-publickey.html [ Pass ]
+crypto/subtle/ecdh-import-pkcs8-key-p384-validate-ecprivatekey-parameters-publickey.html [ Pass ]
+crypto/subtle/ecdsa-import-pkcs8-key-p256-validate-ecprivatekey-parameters-publickey.html [ Pass ]
+crypto/subtle/ecdsa-import-pkcs8-key-p384-validate-ecprivatekey-parameters-publickey.html [ Pass ]
 crypto/subtle/rsa-pss-generate-export-key-jwk-sha1.html [ Pass ]
 crypto/subtle/rsa-pss-generate-export-key-jwk-sha224.html [ Pass ]
 crypto/subtle/rsa-pss-generate-export-key-jwk-sha256.html [ Pass ]
index e626ea7495067a7a8af165bfdc6d777010b17d09..83c93e211cf27687efb58c758bbd98cb34d37ba6 100644 (file)
@@ -1,3 +1,54 @@
+2017-08-03  Zan Dobersek  <zdobersek@igalia.com>
+
+        [GCrypt] Implement CryptoKeyEC PKCS#8 imports
+        https://bugs.webkit.org/show_bug.cgi?id=173647
+
+        Reviewed by Jiewen Tan.
+
+        No new tests -- affected tests are now passing and are unskipped.
+
+        Implement libgcrypt-based support for PKCS#8 imports of EC keys.
+
+        Existing libtasn1 utilities are used to achieve this. First, the provided key data
+        is decoded against the PrivateKeyInfo ASN.1 definition. First, the version member
+        of that structure is validated, followed by the algorithm member. The latter is
+        also properly tested depending on this being an import of an ECDSA or ECDH key.
+
+        Data of the parameters member is decoded against the ECParameters ASN.1 definition,
+        and the namedCurve object identifier is validated, making sure it represents a
+        valid EC curve and that this curve maches the one specified for the import
+        operation.
+
+        Data of the privateKey member is decoded against the ECPrivateKey ASN.1 definition.
+        The version member of that structure is properly validated. The optional parameters
+        member of that structure is already decoded against the ECParameters ASN.1
+        definition. If present, it is checked to contain a valid EC curve identifier that
+        matches the specified curve.
+
+        The optional publicKey member of the ECPrivateKey structure is validated, testing
+        that its data matches in size an uncompressed EC point, and that the first byte
+        of this data is 0x04, as expected for an uncompressed EC point.
+
+        What's left is the private key data on the initial ECPrivateKey structure. That
+        data is retrieved and validated, making sure its size matches the size of the
+        specified curve. The `private-key` s-expression is then constructed, embedding
+        the curve name and the validated private key data. This s-expression is then used
+        to construct an EC context.
+
+        If the optional publicKey data was provided, it's used to set the `q` parameter
+        for this EC context. Otherwise, the value for `q` is computed on-the-fly for the
+        specified EC and the provided private key. The `q` point is then tested through
+        the gcry_mpi_ec_curve_point() function, making sure that the derived point is
+        indeed located on the given EC.
+
+        Finally, with the private key properly validated, a new CryptoKeyEC object is
+        constructed, using the `private-key` s-expression and the parameters that were
+        specified for this import operation.
+
+        * crypto/gcrypt/CryptoKeyECGCrypt.cpp:
+        (WebCore::CryptoKeyEC::platformImportPkcs8):
+        * crypto/gcrypt/GCryptUtilities.h:
+
 2017-08-03  Chris Dumez  <cdumez@apple.com>
 
         Fix parsing of <meta http-equiv=refresh> to allow time starting with a '.' without a leading 0
index 49a8f2058ac65a8816594a1d2792243eaf06f0bd..00ff8201667b77be358972ca0773a47f30332d47 100644 (file)
@@ -335,11 +335,149 @@ RefPtr<CryptoKeyEC> CryptoKeyEC::platformImportSpki(CryptoAlgorithmIdentifier id
     return create(identifier, curve, CryptoKeyType::Public, platformKey.release(), extractable, usages);
 }
 
-RefPtr<CryptoKeyEC> CryptoKeyEC::platformImportPkcs8(CryptoAlgorithmIdentifier, NamedCurve, Vector<uint8_t>&&, bool, CryptoKeyUsageBitmap)
+RefPtr<CryptoKeyEC> CryptoKeyEC::platformImportPkcs8(CryptoAlgorithmIdentifier identifier, NamedCurve curve, Vector<uint8_t>&& keyData, bool extractable, CryptoKeyUsageBitmap usages)
 {
-    notImplemented();
+    // Decode the `PrivateKeyInfo` structure using the provided key data.
+    PAL::TASN1::Structure pkcs8;
+    if (!PAL::TASN1::decodeStructure(&pkcs8, "WebCrypto.PrivateKeyInfo", keyData))
+        return nullptr;
 
-    return nullptr;
+    // Validate `version`.
+    {
+        auto version = PAL::TASN1::elementData(pkcs8, "version");
+        if (!version)
+            return nullptr;
+
+        if (!CryptoConstants::matches(version->data(), version->size(), CryptoConstants::s_asn1Version0))
+            return nullptr;
+    }
+
+    // Validate `privateKeyAlgorithm.algorithm`.
+    {
+        auto algorithm = PAL::TASN1::elementData(pkcs8, "privateKeyAlgorithm.algorithm");
+        if (!algorithm)
+            return nullptr;
+
+        if (!supportedAlgorithmIdentifier(identifier, *algorithm))
+            return nullptr;
+    }
+
+    // Validate `privateKeyAlgorithm.parameters` and therein embedded `ECParameters`.
+    {
+        auto parameters = PAL::TASN1::elementData(pkcs8, "privateKeyAlgorithm.parameters");
+        if (!parameters)
+            return nullptr;
+
+        PAL::TASN1::Structure ecParameters;
+        if (!PAL::TASN1::decodeStructure(&ecParameters, "WebCrypto.ECParameters", *parameters))
+            return nullptr;
+
+        auto namedCurve = PAL::TASN1::elementData(ecParameters, "namedCurve");
+        if (!namedCurve)
+            return nullptr;
+
+        auto parameterCurve = curveForIdentifier(*namedCurve);
+        if (!parameterCurve || *parameterCurve != curve)
+            return nullptr;
+    }
+
+    // Decode the `ECPrivateKey` structure using the `privateKey` data.
+    PAL::TASN1::Structure ecPrivateKey;
+    {
+        auto privateKey = PAL::TASN1::elementData(pkcs8, "privateKey");
+        if (!privateKey)
+            return nullptr;
+
+        if (!PAL::TASN1::decodeStructure(&ecPrivateKey, "WebCrypto.ECPrivateKey", *privateKey))
+            return nullptr;
+    }
+
+    // Validate `privateKey.version`.
+    {
+        auto version = PAL::TASN1::elementData(ecPrivateKey, "version");
+        if (!version)
+            return nullptr;
+
+        if (!CryptoConstants::matches(version->data(), version->size(), CryptoConstants::s_asn1Version1))
+            return nullptr;
+    }
+
+    // Validate `privateKey.parameters.namedCurve`, if any.
+    {
+        auto namedCurve = PAL::TASN1::elementData(ecPrivateKey, "parameters.namedCurve");
+        if (namedCurve) {
+            auto parameterCurve = curveForIdentifier(*namedCurve);
+            if (!parameterCurve || *parameterCurve != curve)
+                return nullptr;
+        }
+    }
+
+    // Validate `privateKey.publicKey`, if any, and scan the data into an MPI.
+    PAL::GCrypt::Handle<gcry_mpi_t> publicKeyMPI;
+    {
+        auto publicKey = PAL::TASN1::elementData(ecPrivateKey, "publicKey");
+        if (publicKey) {
+            if (publicKey->size() != curveUncompressedPointSize(curve)
+                || !CryptoConstants::matches(publicKey->data(), 1, CryptoConstants::s_ecUncompressedFormatLeadingByte))
+                return nullptr;
+
+            gcry_error_t error = gcry_mpi_scan(&publicKeyMPI, GCRYMPI_FMT_USG, publicKey->data(), publicKey->size(), nullptr);
+            if (error != GPG_ERR_NO_ERROR) {
+                PAL::GCrypt::logError(error);
+                return nullptr;
+            }
+        }
+    }
+
+    // Retrieve the `privateKey.privateKey` data and embed it into the `private-key` s-expression.
+    PAL::GCrypt::Handle<gcry_sexp_t> platformKey;
+    {
+        auto privateKey = PAL::TASN1::elementData(ecPrivateKey, "privateKey");
+        if (!privateKey)
+            return nullptr;
+
+        // Validate the size of `privateKey`, making sure it fits the size of the specified EC curve.
+        if (privateKey->size() * 8 != curveSize(curve))
+            return nullptr;
+
+        // Construct the `private-key` expression that will also be used for the EC context.
+        gcry_error_t error = gcry_sexp_build(&platformKey, nullptr, "(private-key(ecc(curve %s)(d %b)))",
+            curveName(curve), privateKey->size(), privateKey->data());
+        if (error != GPG_ERR_NO_ERROR) {
+            PAL::GCrypt::logError(error);
+            return nullptr;
+        }
+
+        // Create an EC context for the specified curve.
+        PAL::GCrypt::Handle<gcry_ctx_t> context;
+        error = gcry_mpi_ec_new(&context, platformKey, nullptr);
+        if (error != GPG_ERR_NO_ERROR) {
+            PAL::GCrypt::logError(error);
+            return nullptr;
+        }
+
+        // Set the 'q' value on the EC context if public key data was provided through the import.
+        if (publicKeyMPI) {
+            error = gcry_mpi_ec_set_mpi("q", publicKeyMPI, context);
+            if (error != GPG_ERR_NO_ERROR) {
+                PAL::GCrypt::logError(error);
+                return nullptr;
+            }
+        }
+
+        // Retrieve the `q` point. If the public key was provided through the PKCS#8 import, that
+        // key value will be retrieved as an gcry_mpi_point_t. Otherwise, the `q` point value will
+        // be computed on-the-fly by libgcrypt for the specified elliptic curve.
+        PAL::GCrypt::Handle<gcry_mpi_point_t> point(gcry_mpi_ec_get_point("q", context, 1));
+        if (!point)
+            return nullptr;
+
+        // Bail if the retrieved `q` MPI point is not on the specified EC curve.
+        if (!gcry_mpi_ec_curve_point(point, context))
+            return nullptr;
+    }
+
+    return create(identifier, curve, CryptoKeyType::Private, platformKey.release(), extractable, usages);
 }
 
 Vector<uint8_t> CryptoKeyEC::platformExportRaw() const
index 583ae581efdc4c7bd68567be52a1ea4ce2682b80..f7492bb8a9a5e4e5fd44adf92a86df378392c965 100644 (file)
@@ -50,6 +50,7 @@ static const std::array<uint8_t, 22> s_RSASSA_PSSIdentifier { { "1.2.840.113549.
 
 static const std::array<uint8_t, 2> s_asn1NullValue { { 0x05, 0x00 } };
 static const std::array<uint8_t, 1> s_asn1Version0 { { 0x00 } };
+static const std::array<uint8_t, 1> s_asn1Version1 { { 0x01 } };
 
 static const std::array<uint8_t, 1> s_ecUncompressedFormatLeadingByte { { 0x04 } };