[GCrypt] RSASSA-PKCS1-v1_5 support
authorzandobersek@gmail.com <zandobersek@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 30 May 2017 07:27:35 +0000 (07:27 +0000)
committerzandobersek@gmail.com <zandobersek@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 30 May 2017 07:27:35 +0000 (07:27 +0000)
https://bugs.webkit.org/show_bug.cgi?id=171220

Reviewed by Michael Catanzaro.

Source/WebCore:

Add RSASSA-PKSC1-V_15 support for configurations that use libgcrypt.

The signing operation first digests data with the specified hash algorithm.
That's then embedded in the data s-expression. That's then passed to the
gcry_pk_sign() call together with the specified key, returning a sig-val
s-expression containing the signature. The signature data is then retrieved
from the relevant MPI and returned to the caller.

The verification operation first digests data with the specified hash
algorithm. That's then embedded in the data s-expression. A sig-val
s-expression is constructed, embedding the signature data. The data and
sig-val s-expressions are passed to the gcry_sexp_verify() call which returns
the resulting error code. The verification succeeds if the returned value is
GPG_ERR_NO_ERROR, fails if it's GPG_ERR_BAD_SIGNATURE, or errors out with an
OperationError otherwise.

No new tests -- the revelant tests are passing and are unskipped, apart from
the ones using PKCS#8 and SPKI formats.

* crypto/gcrypt/CryptoAlgorithmRSASSA_PKCS1_v1_5GCrypt.cpp:
(WebCore::hashCryptoDigestAlgorithm):
(WebCore::hashAlgorithmName):
(WebCore::mpiData):
(WebCore::gcryptSign):
(WebCore::gcryptVerify):
(WebCore::CryptoAlgorithmRSASSA_PKCS1_v1_5::platformSign):
(WebCore::CryptoAlgorithmRSASSA_PKCS1_v1_5::platformVerify):

LayoutTests:

* platform/gtk/TestExpectations: Enable the RSASSA-PKCS1-v1_5 layout tests that we pass.

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

LayoutTests/ChangeLog
LayoutTests/platform/gtk/TestExpectations
Source/WebCore/ChangeLog
Source/WebCore/crypto/gcrypt/CryptoAlgorithmRSASSA_PKCS1_v1_5GCrypt.cpp

index 8d083a6..105856e 100644 (file)
@@ -1,3 +1,12 @@
+2017-05-30  Zan Dobersek  <zdobersek@igalia.com>
+
+        [GCrypt] RSASSA-PKCS1-v1_5 support
+        https://bugs.webkit.org/show_bug.cgi?id=171220
+
+        Reviewed by Michael Catanzaro.
+
+        * platform/gtk/TestExpectations: Enable the RSASSA-PKCS1-v1_5 layout tests that we pass.
+
 2017-05-29  Claudio Saavedra  <csaavedra@igalia.com>
 
         [WPE] Mark a couple of tests as slow/timing out
 2017-05-29  Claudio Saavedra  <csaavedra@igalia.com>
 
         [WPE] Mark a couple of tests as slow/timing out
index 4674f03..3678efa 100644 (file)
@@ -881,6 +881,31 @@ crypto/subtle/hmac-import-raw-key-customized-length.html [ Pass ]
 crypto/subtle/hmac-import-raw-key-export-jwk-key.html [ Pass ]
 crypto/subtle/hmac-import-raw-key-export-raw-key.html [ Pass ]
 crypto/subtle/hmac-import-raw-key.html [ Pass ]
 crypto/subtle/hmac-import-raw-key-export-jwk-key.html [ Pass ]
 crypto/subtle/hmac-import-raw-key-export-raw-key.html [ Pass ]
 crypto/subtle/hmac-import-raw-key.html [ Pass ]
+crypto/subtle/rsassa-pkcs1-v1_5-generate-export-key-jwk-sha1.html [ Pass ]
+crypto/subtle/rsassa-pkcs1-v1_5-generate-export-key-jwk-sha224.html [ Pass ]
+crypto/subtle/rsassa-pkcs1-v1_5-generate-export-key-jwk-sha256.html [ Pass ]
+crypto/subtle/rsassa-pkcs1-v1_5-generate-export-key-jwk-sha384.html [ Pass ]
+crypto/subtle/rsassa-pkcs1-v1_5-generate-export-key-jwk-sha512.html [ Pass ]
+crypto/subtle/rsassa-pkcs1-v1_5-generate-key.html [ Pass ]
+crypto/subtle/rsassa-pkcs1-v1_5-generate-key-sign-verify.html [ Pass ]
+crypto/subtle/rsassa-pkcs1-v1_5-generate-key-with-leading-zeroes-in-exponent.html [ Pass ]
+crypto/subtle/rsassa-pkcs1-v1_5-import-jwk-private-key.html [ Pass ]
+crypto/subtle/rsassa-pkcs1-v1_5-import-jwk-public-key-empty-usages.html [ Pass ]
+crypto/subtle/rsassa-pkcs1-v1_5-import-jwk-public-key-sha1.html [ Pass ]
+crypto/subtle/rsassa-pkcs1-v1_5-import-jwk-public-key-sha224.html [ Pass ]
+crypto/subtle/rsassa-pkcs1-v1_5-import-jwk-public-key-sha256.html [ Pass ]
+crypto/subtle/rsassa-pkcs1-v1_5-import-jwk-public-key-sha384.html [ Pass ]
+crypto/subtle/rsassa-pkcs1-v1_5-import-jwk-public-key-sha512.html [ Pass ]
+crypto/subtle/rsassa-pkcs1-v1_5-import-key-sign-sha1.html [ Pass ]
+crypto/subtle/rsassa-pkcs1-v1_5-import-key-sign-sha224.html [ Pass ]
+crypto/subtle/rsassa-pkcs1-v1_5-import-key-sign-sha256.html [ Pass ]
+crypto/subtle/rsassa-pkcs1-v1_5-import-key-sign-sha384.html [ Pass ]
+crypto/subtle/rsassa-pkcs1-v1_5-import-key-sign-sha512.html [ Pass ]
+crypto/subtle/rsassa-pkcs1-v1_5-import-key-verify-sha1.html [ Pass ]
+crypto/subtle/rsassa-pkcs1-v1_5-import-key-verify-sha224.html [ Pass ]
+crypto/subtle/rsassa-pkcs1-v1_5-import-key-verify-sha256.html [ Pass ]
+crypto/subtle/rsassa-pkcs1-v1_5-import-key-verify-sha384.html [ Pass ]
+crypto/subtle/rsassa-pkcs1-v1_5-import-key-verify-sha512.html [ Pass ]
 crypto/subtle/import-key-malformed-parameters.html [ Pass ]
 crypto/subtle/pbkdf2-derive-bits-malformed-parametrs.html [ Pass ]
 crypto/subtle/pbkdf2-import-key-derive-bits.html [ Pass ]
 crypto/subtle/import-key-malformed-parameters.html [ Pass ]
 crypto/subtle/pbkdf2-derive-bits-malformed-parametrs.html [ Pass ]
 crypto/subtle/pbkdf2-import-key-derive-bits.html [ Pass ]
index c0294a3..b4e1515 100644 (file)
@@ -1,3 +1,38 @@
+2017-05-30  Zan Dobersek  <zdobersek@igalia.com>
+
+        [GCrypt] RSASSA-PKCS1-v1_5 support
+        https://bugs.webkit.org/show_bug.cgi?id=171220
+
+        Reviewed by Michael Catanzaro.
+
+        Add RSASSA-PKSC1-V_15 support for configurations that use libgcrypt.
+
+        The signing operation first digests data with the specified hash algorithm.
+        That's then embedded in the data s-expression. That's then passed to the
+        gcry_pk_sign() call together with the specified key, returning a sig-val
+        s-expression containing the signature. The signature data is then retrieved
+        from the relevant MPI and returned to the caller.
+
+        The verification operation first digests data with the specified hash
+        algorithm. That's then embedded in the data s-expression. A sig-val
+        s-expression is constructed, embedding the signature data. The data and
+        sig-val s-expressions are passed to the gcry_sexp_verify() call which returns
+        the resulting error code. The verification succeeds if the returned value is
+        GPG_ERR_NO_ERROR, fails if it's GPG_ERR_BAD_SIGNATURE, or errors out with an
+        OperationError otherwise.
+
+        No new tests -- the revelant tests are passing and are unskipped, apart from
+        the ones using PKCS#8 and SPKI formats.
+
+        * crypto/gcrypt/CryptoAlgorithmRSASSA_PKCS1_v1_5GCrypt.cpp:
+        (WebCore::hashCryptoDigestAlgorithm):
+        (WebCore::hashAlgorithmName):
+        (WebCore::mpiData):
+        (WebCore::gcryptSign):
+        (WebCore::gcryptVerify):
+        (WebCore::CryptoAlgorithmRSASSA_PKCS1_v1_5::platformSign):
+        (WebCore::CryptoAlgorithmRSASSA_PKCS1_v1_5::platformVerify):
+
 2017-05-29  Emilio Cobos Álvarez  <ecobos@igalia.com>
 
         Use the parent box style to adjust RenderStyle for alignment.
 2017-05-29  Emilio Cobos Álvarez  <ecobos@igalia.com>
 
         Use the parent box style to adjust RenderStyle for alignment.
index 3c535ac..27934e9 100644 (file)
 #include "CryptoKeyRSA.h"
 #include "ExceptionCode.h"
 #include "NotImplemented.h"
 #include "CryptoKeyRSA.h"
 #include "ExceptionCode.h"
 #include "NotImplemented.h"
+#include "ScriptExecutionContext.h"
+#include <pal/crypto/CryptoDigest.h>
+#include <pal/crypto/gcrypt/Handle.h>
+#include <pal/crypto/gcrypt/Utilities.h>
 
 namespace WebCore {
 
 
 namespace WebCore {
 
-void CryptoAlgorithmRSASSA_PKCS1_v1_5::platformSign(Ref<CryptoKey>&&, Vector<uint8_t>&&, VectorCallback&&, ExceptionCallback&&, ScriptExecutionContext&, WorkQueue&)
+static std::optional<PAL::CryptoDigest::Algorithm> hashCryptoDigestAlgorithm(CryptoAlgorithmIdentifier identifier)
 {
 {
-    notImplemented();
+    switch (identifier) {
+    case CryptoAlgorithmIdentifier::SHA_1:
+        return PAL::CryptoDigest::Algorithm::SHA_1;
+    case CryptoAlgorithmIdentifier::SHA_224:
+        return PAL::CryptoDigest::Algorithm::SHA_224;
+    case CryptoAlgorithmIdentifier::SHA_256:
+        return PAL::CryptoDigest::Algorithm::SHA_256;
+    case CryptoAlgorithmIdentifier::SHA_384:
+        return PAL::CryptoDigest::Algorithm::SHA_384;
+    case CryptoAlgorithmIdentifier::SHA_512:
+        return PAL::CryptoDigest::Algorithm::SHA_512;
+    default:
+        return std::nullopt;
+    }
 }
 
 }
 
-void CryptoAlgorithmRSASSA_PKCS1_v1_5::platformVerify(Ref<CryptoKey>&&, Vector<uint8_t>&&, Vector<uint8_t>&&, BoolCallback&&, ExceptionCallback&&, ScriptExecutionContext&, WorkQueue&)
+static std::optional<const char*> hashAlgorithmName(CryptoAlgorithmIdentifier identifier)
 {
 {
-    notImplemented();
+    switch (identifier) {
+    case CryptoAlgorithmIdentifier::SHA_1:
+        return "sha1";
+    case CryptoAlgorithmIdentifier::SHA_224:
+        return "sha224";
+    case CryptoAlgorithmIdentifier::SHA_256:
+        return "sha256";
+    case CryptoAlgorithmIdentifier::SHA_384:
+        return "sha384";
+    case CryptoAlgorithmIdentifier::SHA_512:
+        return "sha512";
+    default:
+        return std::nullopt;
+    }
+}
+
+static std::optional<Vector<uint8_t>> mpiData(gcry_sexp_t paramSexp)
+{
+    // Retrieve the MPI value stored in the s-expression: (name mpi-data)
+    PAL::GCrypt::Handle<gcry_mpi_t> paramMPI(gcry_sexp_nth_mpi(paramSexp, 1, GCRYMPI_FMT_USG));
+    if (!paramMPI)
+        return std::nullopt;
+
+    // Query the data length first to properly prepare the buffer.
+    size_t dataLength = 0;
+    gcry_error_t error = gcry_mpi_print(GCRYMPI_FMT_USG, nullptr, 0, &dataLength, paramMPI);
+    if (error != GPG_ERR_NO_ERROR) {
+        PAL::GCrypt::logError(error);
+        return std::nullopt;
+    }
+
+    // Finally, copy the MPI data into a properly-sized buffer.
+    Vector<uint8_t> output(dataLength);
+    error = gcry_mpi_print(GCRYMPI_FMT_USG, output.data(), output.size(), nullptr, paramMPI);
+    if (error != GPG_ERR_NO_ERROR) {
+        PAL::GCrypt::logError(error);
+        return std::nullopt;
+    }
+
+    return output;
+}
+
+static std::optional<Vector<uint8_t>> gcryptSign(gcry_sexp_t keySexp, const Vector<uint8_t>& data, CryptoAlgorithmIdentifier hashAlgorithmIdentifier)
+{
+    // Perform digest operation with the specified algorithm on the given data.
+    Vector<uint8_t> dataHash;
+    {
+        auto digestAlgorithm = hashCryptoDigestAlgorithm(hashAlgorithmIdentifier);
+        if (!digestAlgorithm)
+            return std::nullopt;
+
+        auto digest = PAL::CryptoDigest::create(*digestAlgorithm);
+        if (!digest)
+            return std::nullopt;
+
+        digest->addBytes(data.data(), data.size());
+        dataHash = digest->computeHash();
+    }
+
+    // Construct the data s-expression that contains PKCS#1-padded hashed data.
+    PAL::GCrypt::Handle<gcry_sexp_t> dataSexp;
+    {
+        auto shaAlgorithm = hashAlgorithmName(hashAlgorithmIdentifier);
+        if (!shaAlgorithm)
+            return std::nullopt;
+
+        gcry_error_t error = gcry_sexp_build(&dataSexp, nullptr, "(data(flags pkcs1)(hash %s %b))",
+            *shaAlgorithm, dataHash.size(), dataHash.data());
+        if (error != GPG_ERR_NO_ERROR) {
+            PAL::GCrypt::logError(error);
+            return std::nullopt;
+        }
+    }
+
+    // Perform the PK signing, retrieving a sig-val s-expression of the following form:
+    // (sig-val
+    //   (rsa
+    //     (s s-mpi)))
+    PAL::GCrypt::Handle<gcry_sexp_t> signatureSexp;
+    gcry_error_t error = gcry_pk_sign(&signatureSexp, dataSexp, keySexp);
+    if (error != GPG_ERR_NO_ERROR) {
+        PAL::GCrypt::logError(error);
+        return std::nullopt;
+    }
+
+    // Return MPI data of the embedded s integer.
+    PAL::GCrypt::Handle<gcry_sexp_t> sSexp(gcry_sexp_find_token(signatureSexp, "s", 0));
+    if (!sSexp)
+        return std::nullopt;
+
+    return mpiData(sSexp);
+}
+
+static std::optional<bool> gcryptVerify(gcry_sexp_t keySexp, const Vector<uint8_t>& signature, const Vector<uint8_t>& data, CryptoAlgorithmIdentifier hashAlgorithmIdentifier)
+{
+    // Perform digest operation with the specified algorithm on the given data.
+    Vector<uint8_t> dataHash;
+    {
+        auto digestAlgorithm = hashCryptoDigestAlgorithm(hashAlgorithmIdentifier);
+        if (!digestAlgorithm)
+            return std::nullopt;
+
+        auto digest = PAL::CryptoDigest::create(*digestAlgorithm);
+        if (!digest)
+            return std::nullopt;
+
+        digest->addBytes(data.data(), data.size());
+        dataHash = digest->computeHash();
+    }
+
+    // Construct the sig-val s-expression that contains the signature data.
+    PAL::GCrypt::Handle<gcry_sexp_t> signatureSexp;
+    gcry_error_t error = gcry_sexp_build(&signatureSexp, nullptr, "(sig-val(rsa(s %b)))",
+        signature.size(), signature.data());
+    if (error != GPG_ERR_NO_ERROR) {
+        PAL::GCrypt::logError(error);
+        return std::nullopt;
+    }
+
+    // Construct the data s-expression that contains PKCS#1-padded hashed data.
+    PAL::GCrypt::Handle<gcry_sexp_t> dataSexp;
+    {
+        auto shaAlgorithm = hashAlgorithmName(hashAlgorithmIdentifier);
+        if (!shaAlgorithm)
+            return std::nullopt;
+
+        error = gcry_sexp_build(&dataSexp, nullptr, "(data(flags pkcs1)(hash %s %b))",
+            *shaAlgorithm, dataHash.size(), dataHash.data());
+        if (error != GPG_ERR_NO_ERROR) {
+            PAL::GCrypt::logError(error);
+            return std::nullopt;
+        }
+    }
+
+    // Perform the PK verification. We report success if there's no error returned, or
+    // a failure in any other case. OperationError should not be returned at this point.
+    error = gcry_pk_verify(signatureSexp, dataSexp, keySexp);
+    return { error == GPG_ERR_NO_ERROR };
+}
+
+void CryptoAlgorithmRSASSA_PKCS1_v1_5::platformSign(Ref<CryptoKey>&& key, Vector<uint8_t>&& data, VectorCallback&& callback, ExceptionCallback&& exceptionCallback, ScriptExecutionContext& context, WorkQueue& workQueue)
+{
+    context.ref();
+    workQueue.dispatch(
+        [key = WTFMove(key), data = WTFMove(data), callback = WTFMove(callback), exceptionCallback = WTFMove(exceptionCallback), &context]() mutable {
+            auto& rsaKey = downcast<CryptoKeyRSA>(key.get());
+
+            auto output = gcryptSign(rsaKey.platformKey(), data, rsaKey.hashAlgorithmIdentifier());
+            if (!output) {
+                // We should only dereference callbacks after being back to the Document/Worker threads.
+                context.postTask(
+                    [callback = WTFMove(callback), exceptionCallback = WTFMove(exceptionCallback)](ScriptExecutionContext& context) {
+                        exceptionCallback(OperationError);
+                        context.deref();
+                    });
+                return;
+            }
+
+            // We should only dereference callbacks after being back to the Document/Worker threads.
+            context.postTask(
+                [output = WTFMove(*output), callback = WTFMove(callback), exceptionCallback = WTFMove(exceptionCallback)](ScriptExecutionContext& context) {
+                    callback(output);
+                    context.deref();
+                });
+        });
+}
+
+void CryptoAlgorithmRSASSA_PKCS1_v1_5::platformVerify(Ref<CryptoKey>&& key, Vector<uint8_t>&& signature, Vector<uint8_t>&& data, BoolCallback&& callback, ExceptionCallback&& exceptionCallback, ScriptExecutionContext& context, WorkQueue& workQueue)
+{
+    context.ref();
+    workQueue.dispatch(
+        [key = WTFMove(key), signature = WTFMove(signature), data = WTFMove(data), callback = WTFMove(callback), exceptionCallback = WTFMove(exceptionCallback), &context]() mutable {
+            auto& rsaKey = downcast<CryptoKeyRSA>(key.get());
+
+            auto output = gcryptVerify(rsaKey.platformKey(), signature, data, rsaKey.hashAlgorithmIdentifier());
+            if (!output) {
+                // We should only dereference callbacks after being back to the Document/Worker threads.
+                context.postTask(
+                    [callback = WTFMove(callback), exceptionCallback = WTFMove(exceptionCallback)](ScriptExecutionContext& context) {
+                        exceptionCallback(OperationError);
+                        context.deref();
+                    });
+                return;
+            }
+
+            // We should only dereference callbacks after being back to the Document/Worker threads.
+            context.postTask(
+                [output = WTFMove(*output), callback = WTFMove(callback), exceptionCallback = WTFMove(exceptionCallback)](ScriptExecutionContext& context) {
+                    callback(output);
+                    context.deref();
+                });
+        });
 }
 
 ExceptionOr<void> CryptoAlgorithmRSASSA_PKCS1_v1_5::platformSign(const CryptoAlgorithmRsaSsaParamsDeprecated&, const CryptoKeyRSA&, const CryptoOperationData&, VectorCallback&&, VoidCallback&&)
 }
 
 ExceptionOr<void> CryptoAlgorithmRSASSA_PKCS1_v1_5::platformSign(const CryptoAlgorithmRsaSsaParamsDeprecated&, const CryptoKeyRSA&, const CryptoOperationData&, VectorCallback&&, VoidCallback&&)