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