[GCrypt] Gather SUBTLE_CRYPTO utility functions in a single header
[WebKit-https.git] / Source / WebCore / crypto / gcrypt / CryptoAlgorithmHKDFGCrypt.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 "CryptoAlgorithmHKDF.h"
30
31 #if ENABLE(SUBTLE_CRYPTO)
32
33 #include "CryptoAlgorithmHkdfParams.h"
34 #include "CryptoKeyRaw.h"
35 #include "ExceptionCode.h"
36 #include "GCryptUtilities.h"
37 #include "ScriptExecutionContext.h"
38
39 namespace WebCore {
40
41 // libgcrypt doesn't provide HKDF functionality, so we have to implement it manually.
42 // We should switch to the libgcrypt-provided implementation once it's available.
43 // https://bugs.webkit.org/show_bug.cgi?id=171536
44
45 static std::optional<Vector<uint8_t>> gcryptDeriveBits(const Vector<uint8_t>& key, const Vector<uint8_t>& salt, const Vector<uint8_t>& info, size_t lengthInBytes, CryptoAlgorithmIdentifier identifier)
46 {
47     // libgcrypt doesn't provide HKDF support, so we have to implement
48     // the functionality ourselves as specified in RFC5869.
49     // https://www.ietf.org/rfc/rfc5869.txt
50
51     auto macAlgorithm = hmacAlgorithm(identifier);
52     if (!macAlgorithm)
53         return std::nullopt;
54
55     // We can immediately discard invalid output lengths, otherwise needed for the expand step.
56     size_t macLength = gcry_mac_get_algo_maclen(*macAlgorithm);
57     if (lengthInBytes > macLength * 255)
58         return std::nullopt;
59
60     PAL::GCrypt::Handle<gcry_mac_hd_t> handle;
61     gcry_error_t error = gcry_mac_open(&handle, *macAlgorithm, 0, nullptr);
62     if (error != GPG_ERR_NO_ERROR) {
63         PAL::GCrypt::logError(error);
64         return std::nullopt;
65     }
66
67     // Step 1 -- Extract. A pseudo-random key is generated with the specified algorithm
68     // for the given salt value (used as a key) and the 'input keying material'.
69     Vector<uint8_t> pseudoRandomKey(macLength);
70     {
71         // If the salt vector is empty, a zeroed-out key of macLength size should be used.
72         if (salt.isEmpty()) {
73             Vector<uint8_t> zeroedKey(macLength, 0);
74             error = gcry_mac_setkey(handle, zeroedKey.data(), zeroedKey.size());
75         } else
76             error = gcry_mac_setkey(handle, salt.data(), salt.size());
77         if (error != GPG_ERR_NO_ERROR) {
78             PAL::GCrypt::logError(error);
79             return std::nullopt;
80         }
81
82         error = gcry_mac_write(handle, key.data(), key.size());
83         if (error != GPG_ERR_NO_ERROR) {
84             PAL::GCrypt::logError(error);
85             return std::nullopt;
86         }
87
88         size_t pseudoRandomKeySize = pseudoRandomKey.size();
89         error = gcry_mac_read(handle, pseudoRandomKey.data(), &pseudoRandomKeySize);
90         if (error != GPG_ERR_NO_ERROR) {
91             PAL::GCrypt::logError(error);
92             return std::nullopt;
93         }
94
95         // Something went wrong if libgcrypt didn't write out the proper amount of data.
96         if (pseudoRandomKeySize != macLength)
97             return std::nullopt;
98     }
99
100     // Step #2 -- Expand.
101     Vector<uint8_t> output;
102     {
103         // Deduce the number of needed iterations to retrieve the necessary amount of data.
104         size_t numIterations = (lengthInBytes + macLength) / macLength;
105         // Block from the previous iteration is used in the current one, except
106         // in the first iteration when it's empty.
107         Vector<uint8_t> lastBlock(macLength);
108
109         for (size_t i = 0; i < numIterations; ++i) {
110             error = gcry_mac_reset(handle);
111             if (error != GPG_ERR_NO_ERROR) {
112                 PAL::GCrypt::logError(error);
113                 return std::nullopt;
114             }
115
116             error = gcry_mac_setkey(handle, pseudoRandomKey.data(), pseudoRandomKey.size());
117             if (error != GPG_ERR_NO_ERROR) {
118                 PAL::GCrypt::logError(error);
119                 return std::nullopt;
120             }
121
122             // T(0) = empty string (zero length) -- i.e. empty lastBlock
123             // T(i) = HMAC-Hash(PRK, T(i-1) | info | hex(i)) -- | represents concatenation
124             Vector<uint8_t> blockData;
125             if (i)
126                 blockData.appendVector(lastBlock);
127             blockData.appendVector(info);
128             blockData.append(i + 1);
129
130             error = gcry_mac_write(handle, blockData.data(), blockData.size());
131             if (error != GPG_ERR_NO_ERROR) {
132                 PAL::GCrypt::logError(error);
133                 return std::nullopt;
134             }
135
136             size_t blockSize = lastBlock.size();
137             error = gcry_mac_read(handle, lastBlock.data(), &blockSize);
138             if (error != GPG_ERR_NO_ERROR) {
139                 PAL::GCrypt::logError(error);
140                 return std::nullopt;
141             }
142
143             // Something went wrong if libgcrypt didn't write out the proper amount of data.
144             if (blockSize != lastBlock.size())
145                 return std::nullopt;
146
147             // Append the current block data to the output vector.
148             output.appendVector(lastBlock);
149         }
150     }
151
152     // Clip output vector to the requested size.
153     output.resize(lengthInBytes);
154     return output;
155 }
156
157 void CryptoAlgorithmHKDF::platformDeriveBits(std::unique_ptr<CryptoAlgorithmParameters>&& parameters, Ref<CryptoKey>&& baseKey, size_t length, VectorCallback&& callback, ExceptionCallback&& exceptionCallback, ScriptExecutionContext& context, WorkQueue& workQueue)
158 {
159     context.ref();
160     workQueue.dispatch(
161         [parameters = WTFMove(parameters), baseKey = WTFMove(baseKey), length, callback = WTFMove(callback), exceptionCallback = WTFMove(exceptionCallback), &context]() mutable {
162             auto& hkdfParameters = downcast<CryptoAlgorithmHkdfParams>(*parameters);
163             auto& rawKey = downcast<CryptoKeyRaw>(baseKey.get());
164
165             auto output = gcryptDeriveBits(rawKey.key(), hkdfParameters.saltVector(), hkdfParameters.infoVector(), length / 8, hkdfParameters.hashIdentifier);
166             if (!output) {
167                 // We should only dereference callbacks after being back to the Document/Worker threads.
168                 context.postTask(
169                     [callback = WTFMove(callback), exceptionCallback = WTFMove(exceptionCallback)](ScriptExecutionContext& context) {
170                         exceptionCallback(OperationError);
171                         context.deref();
172                     });
173                 return;
174             }
175
176             // We should only dereference callbacks after being back to the Document/Worker threads.
177             context.postTask(
178                 [output = WTFMove(*output), callback = WTFMove(callback), exceptionCallback = WTFMove(exceptionCallback)](ScriptExecutionContext& context) {
179                     callback(output);
180                     context.deref();
181                 });
182         });
183 }
184
185 } // namespace WebCore
186
187 #endif // ENABLE(SUBTLE_CRYPTO)