[GCrypt] Gather SUBTLE_CRYPTO utility functions in a single header
[WebKit-https.git] / Source / WebCore / crypto / gcrypt / CryptoAlgorithmECDSAGCrypt.cpp
index aba95b1..276a95d 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 "CryptoAlgorithmEcdsaParams.h"
+#include "CryptoKeyEC.h"
+#include "ExceptionCode.h"
+#include "GCryptUtilities.h"
+#include "ScriptExecutionContext.h"
+#include <pal/crypto/CryptoDigest.h>
 
 namespace WebCore {
 
-void CryptoAlgorithmECDSA::platformSign(std::unique_ptr<CryptoAlgorithmParameters>&&, Ref<CryptoKey>&&, Vector<uint8_t>&&, VectorCallback&&, ExceptionCallback&&, ScriptExecutionContext&, WorkQueue&)
+static std::optional<Vector<uint8_t>> gcryptSign(gcry_sexp_t keySexp, const Vector<uint8_t>& data, CryptoAlgorithmIdentifier hashAlgorithmIdentifier, size_t keySizeInBytes)
 {
-    notImplemented();
+    // 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 raw 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 raw)(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
+    //   (dsa
+    //     (r r-mpi)
+    //     (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;
+    }
+
+    // Retrieve MPI data of the resulting r and s integers. They are concatenated into
+    // a single buffer after checking that the data length matches the key size.
+    // FIXME: But r and s integers can still be valid even if they're of shorter size.
+    // https://bugs.webkit.org/show_bug.cgi?id=171535
+    Vector<uint8_t> signature;
+    {
+        PAL::GCrypt::Handle<gcry_sexp_t> rSexp(gcry_sexp_find_token(signatureSexp, "r", 0));
+        if (!rSexp)
+            return std::nullopt;
+
+        auto rData = mpiData(rSexp);
+        if (!rData || rData->size() != keySizeInBytes)
+            return std::nullopt;
+
+        signature.appendVector(*rData);
+    }
+    {
+        PAL::GCrypt::Handle<gcry_sexp_t> sSexp(gcry_sexp_find_token(signatureSexp, "s", 0));
+        if (!sSexp)
+            return std::nullopt;
+
+        auto sData = mpiData(sSexp);
+        if (!sData || sData->size() != keySizeInBytes)
+            return std::nullopt;
+
+        signature.appendVector(*sData);
+    }
+
+    return signature;
+}
+
+static std::optional<bool> gcryptVerify(gcry_sexp_t keySexp, const Vector<uint8_t>& signature, const Vector<uint8_t>& data, CryptoAlgorithmIdentifier hashAlgorithmIdentifier, size_t keySizeInBytes)
+{
+    // Bail if the signature size isn't double the key size (i.e. concatenated r and s components).
+    if (signature.size() != keySizeInBytes * 2)
+        return false;
+
+    // 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, extracting the r and s components from the signature vector.
+    PAL::GCrypt::Handle<gcry_sexp_t> signatureSexp;
+    gcry_error_t error = gcry_sexp_build(&signatureSexp, nullptr, "(sig-val(ecdsa(r %b)(s %b)))",
+        keySizeInBytes, signature.data(), keySizeInBytes, signature.data() + keySizeInBytes);
+    if (error != GPG_ERR_NO_ERROR) {
+        PAL::GCrypt::logError(error);
+        return std::nullopt;
+    }
+
+    // Construct the data s-expression that contains raw 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 raw)(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,
+    // avoiding spilling information about the exact cause of verification failure.
+    error = gcry_pk_verify(signatureSexp, dataSexp, keySexp);
+    return { error == GPG_ERR_NO_ERROR };
 }
 
-void CryptoAlgorithmECDSA::platformVerify(std::unique_ptr<CryptoAlgorithmParameters>&&, Ref<CryptoKey>&&, Vector<uint8_t>&&, Vector<uint8_t>&&, BoolCallback&&, ExceptionCallback&&, ScriptExecutionContext&, WorkQueue&)
+void CryptoAlgorithmECDSA::platformSign(std::unique_ptr<CryptoAlgorithmParameters>&& parameters, Ref<CryptoKey>&& key, Vector<uint8_t>&& data, VectorCallback&& callback, ExceptionCallback&& exceptionCallback, ScriptExecutionContext& context, WorkQueue& workQueue)
 {
-    notImplemented();
+    context.ref();
+    workQueue.dispatch(
+        [parameters = WTFMove(parameters), key = WTFMove(key), data = WTFMove(data), callback = WTFMove(callback), exceptionCallback = WTFMove(exceptionCallback), &context]() mutable {
+            auto& ecKey = downcast<CryptoKeyEC>(key.get());
+            auto& ecParameters = downcast<CryptoAlgorithmEcdsaParams>(*parameters);
+
+            auto output = gcryptSign(ecKey.platformKey(), data, ecParameters.hashIdentifier, ecKey.keySizeInBits() / 8);
+            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 CryptoAlgorithmECDSA::platformVerify(std::unique_ptr<CryptoAlgorithmParameters>&& parameters, Ref<CryptoKey>&& key, Vector<uint8_t>&& signature, Vector<uint8_t>&& data, BoolCallback&& callback, ExceptionCallback&& exceptionCallback, ScriptExecutionContext& context, WorkQueue& workQueue)
+{
+    context.ref();
+    workQueue.dispatch(
+        [parameters = WTFMove(parameters), key = WTFMove(key), signature = WTFMove(signature), data = WTFMove(data), callback = WTFMove(callback), exceptionCallback = WTFMove(exceptionCallback), &context]() mutable {
+            auto& ecKey = downcast<CryptoKeyEC>(key.get());
+            auto& ecParameters = downcast<CryptoAlgorithmEcdsaParams>(*parameters);
+
+            auto output = gcryptVerify(ecKey.platformKey(), signature, data, ecParameters.hashIdentifier, ecKey.keySizeInBits() / 8);
+            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();
+                });
+        });
 }
 
 } // namespace WebCore