[GCrypt] Gather SUBTLE_CRYPTO utility functions in a single header
[WebKit-https.git] / Source / WebCore / crypto / gcrypt / CryptoAlgorithmECDSAGCrypt.cpp
1 /*
2  * Copyright (C) 2017 Apple Inc. All rights reserved.
3  * Copyright (C) 2017 Metrological Group B.V.
4  * Copyright (C) 2017 Igalia S.L.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
16  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
17  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
19  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
25  * THE POSSIBILITY OF SUCH DAMAGE.
26  */
27
28 #include "config.h"
29 #include "CryptoAlgorithmECDSA.h"
30
31 #if ENABLE(SUBTLE_CRYPTO)
32
33 #include "CryptoAlgorithmEcdsaParams.h"
34 #include "CryptoKeyEC.h"
35 #include "ExceptionCode.h"
36 #include "GCryptUtilities.h"
37 #include "ScriptExecutionContext.h"
38 #include <pal/crypto/CryptoDigest.h>
39
40 namespace WebCore {
41
42 static std::optional<Vector<uint8_t>> gcryptSign(gcry_sexp_t keySexp, const Vector<uint8_t>& data, CryptoAlgorithmIdentifier hashAlgorithmIdentifier, size_t keySizeInBytes)
43 {
44     // Perform digest operation with the specified algorithm on the given data.
45     Vector<uint8_t> dataHash;
46     {
47         auto digestAlgorithm = hashCryptoDigestAlgorithm(hashAlgorithmIdentifier);
48         if (!digestAlgorithm)
49             return std::nullopt;
50
51         auto digest = PAL::CryptoDigest::create(*digestAlgorithm);
52         if (!digest)
53             return std::nullopt;
54
55         digest->addBytes(data.data(), data.size());
56         dataHash = digest->computeHash();
57     }
58
59     // Construct the data s-expression that contains raw hashed data.
60     PAL::GCrypt::Handle<gcry_sexp_t> dataSexp;
61     {
62         auto shaAlgorithm = hashAlgorithmName(hashAlgorithmIdentifier);
63         if (!shaAlgorithm)
64             return std::nullopt;
65
66         gcry_error_t error = gcry_sexp_build(&dataSexp, nullptr, "(data(flags raw)(hash %s %b))",
67             *shaAlgorithm, dataHash.size(), dataHash.data());
68         if (error != GPG_ERR_NO_ERROR) {
69             PAL::GCrypt::logError(error);
70             return std::nullopt;
71         }
72     }
73
74     // Perform the PK signing, retrieving a sig-val s-expression of the following form:
75     // (sig-val
76     //   (dsa
77     //     (r r-mpi)
78     //     (s s-mpi)))
79     PAL::GCrypt::Handle<gcry_sexp_t> signatureSexp;
80     gcry_error_t error = gcry_pk_sign(&signatureSexp, dataSexp, keySexp);
81     if (error != GPG_ERR_NO_ERROR) {
82         PAL::GCrypt::logError(error);
83         return std::nullopt;
84     }
85
86     // Retrieve MPI data of the resulting r and s integers. They are concatenated into
87     // a single buffer after checking that the data length matches the key size.
88     // FIXME: But r and s integers can still be valid even if they're of shorter size.
89     // https://bugs.webkit.org/show_bug.cgi?id=171535
90     Vector<uint8_t> signature;
91     {
92         PAL::GCrypt::Handle<gcry_sexp_t> rSexp(gcry_sexp_find_token(signatureSexp, "r", 0));
93         if (!rSexp)
94             return std::nullopt;
95
96         auto rData = mpiData(rSexp);
97         if (!rData || rData->size() != keySizeInBytes)
98             return std::nullopt;
99
100         signature.appendVector(*rData);
101     }
102     {
103         PAL::GCrypt::Handle<gcry_sexp_t> sSexp(gcry_sexp_find_token(signatureSexp, "s", 0));
104         if (!sSexp)
105             return std::nullopt;
106
107         auto sData = mpiData(sSexp);
108         if (!sData || sData->size() != keySizeInBytes)
109             return std::nullopt;
110
111         signature.appendVector(*sData);
112     }
113
114     return signature;
115 }
116
117 static std::optional<bool> gcryptVerify(gcry_sexp_t keySexp, const Vector<uint8_t>& signature, const Vector<uint8_t>& data, CryptoAlgorithmIdentifier hashAlgorithmIdentifier, size_t keySizeInBytes)
118 {
119     // Bail if the signature size isn't double the key size (i.e. concatenated r and s components).
120     if (signature.size() != keySizeInBytes * 2)
121         return false;
122
123     // Perform digest operation with the specified algorithm on the given data.
124     Vector<uint8_t> dataHash;
125     {
126         auto digestAlgorithm = hashCryptoDigestAlgorithm(hashAlgorithmIdentifier);
127         if (!digestAlgorithm)
128             return std::nullopt;
129
130         auto digest = PAL::CryptoDigest::create(*digestAlgorithm);
131         if (!digest)
132             return std::nullopt;
133
134         digest->addBytes(data.data(), data.size());
135         dataHash = digest->computeHash();
136     }
137
138     // Construct the sig-val s-expression, extracting the r and s components from the signature vector.
139     PAL::GCrypt::Handle<gcry_sexp_t> signatureSexp;
140     gcry_error_t error = gcry_sexp_build(&signatureSexp, nullptr, "(sig-val(ecdsa(r %b)(s %b)))",
141         keySizeInBytes, signature.data(), keySizeInBytes, signature.data() + keySizeInBytes);
142     if (error != GPG_ERR_NO_ERROR) {
143         PAL::GCrypt::logError(error);
144         return std::nullopt;
145     }
146
147     // Construct the data s-expression that contains raw hashed data.
148     PAL::GCrypt::Handle<gcry_sexp_t> dataSexp;
149     {
150         auto shaAlgorithm = hashAlgorithmName(hashAlgorithmIdentifier);
151         if (!shaAlgorithm)
152             return std::nullopt;
153
154         error = gcry_sexp_build(&dataSexp, nullptr, "(data(flags raw)(hash %s %b))",
155             *shaAlgorithm, dataHash.size(), dataHash.data());
156         if (error != GPG_ERR_NO_ERROR) {
157             PAL::GCrypt::logError(error);
158             return std::nullopt;
159         }
160     }
161
162     // Perform the PK verification. We report success if there's no error returned, or
163     // a failure in any other case. OperationError should not be returned at this point,
164     // avoiding spilling information about the exact cause of verification failure.
165     error = gcry_pk_verify(signatureSexp, dataSexp, keySexp);
166     return { error == GPG_ERR_NO_ERROR };
167 }
168
169 void CryptoAlgorithmECDSA::platformSign(std::unique_ptr<CryptoAlgorithmParameters>&& parameters, Ref<CryptoKey>&& key, Vector<uint8_t>&& data, VectorCallback&& callback, ExceptionCallback&& exceptionCallback, ScriptExecutionContext& context, WorkQueue& workQueue)
170 {
171     context.ref();
172     workQueue.dispatch(
173         [parameters = WTFMove(parameters), key = WTFMove(key), data = WTFMove(data), callback = WTFMove(callback), exceptionCallback = WTFMove(exceptionCallback), &context]() mutable {
174             auto& ecKey = downcast<CryptoKeyEC>(key.get());
175             auto& ecParameters = downcast<CryptoAlgorithmEcdsaParams>(*parameters);
176
177             auto output = gcryptSign(ecKey.platformKey(), data, ecParameters.hashIdentifier, ecKey.keySizeInBits() / 8);
178             if (!output) {
179                 // We should only dereference callbacks after being back to the Document/Worker threads.
180                 context.postTask(
181                     [callback = WTFMove(callback), exceptionCallback = WTFMove(exceptionCallback)](ScriptExecutionContext& context) {
182                         exceptionCallback(OperationError);
183                         context.deref();
184                     });
185                 return;
186             }
187
188             // We should only dereference callbacks after being back to the Document/Worker threads.
189             context.postTask(
190                 [output = WTFMove(*output), callback = WTFMove(callback), exceptionCallback = WTFMove(exceptionCallback)](ScriptExecutionContext& context) {
191                     callback(output);
192                     context.deref();
193                 });
194         });
195 }
196
197 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)
198 {
199     context.ref();
200     workQueue.dispatch(
201         [parameters = WTFMove(parameters), key = WTFMove(key), signature = WTFMove(signature), data = WTFMove(data), callback = WTFMove(callback), exceptionCallback = WTFMove(exceptionCallback), &context]() mutable {
202             auto& ecKey = downcast<CryptoKeyEC>(key.get());
203             auto& ecParameters = downcast<CryptoAlgorithmEcdsaParams>(*parameters);
204
205             auto output = gcryptVerify(ecKey.platformKey(), signature, data, ecParameters.hashIdentifier, ecKey.keySizeInBits() / 8);
206             if (!output) {
207                 // We should only dereference callbacks after being back to the Document/Worker threads.
208                 context.postTask(
209                     [callback = WTFMove(callback), exceptionCallback = WTFMove(exceptionCallback)](ScriptExecutionContext& context) {
210                         exceptionCallback(OperationError);
211                         context.deref();
212                     });
213                 return;
214             }
215
216             // We should only dereference callbacks after being back to the Document/Worker threads.
217             context.postTask(
218                 [output = WTFMove(*output), callback = WTFMove(callback), exceptionCallback = WTFMove(exceptionCallback)](ScriptExecutionContext& context) {
219                     callback(output);
220                     context.deref();
221                 });
222         });
223 }
224
225 } // namespace WebCore
226
227 #endif // ENABLE(SUBTLE_CRYPTO)