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(WEB_CRYPTO)
32
33#include "CryptoAlgorithmHkdfParams.h"
34#include "CryptoKeyRaw.h"
35#include "GCryptUtilities.h"
36
37namespace 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
43static 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 WTF::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 WTF::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 WTF::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 WTF::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 WTF::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 WTF::nullopt;
91 }
92
93 // Something went wrong if libgcrypt didn't write out the proper amount of data.
94 if (pseudoRandomKeySize != macLength)
95 return WTF::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 WTF::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 WTF::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 WTF::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 WTF::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 WTF::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
155ExceptionOr<Vector<uint8_t>> CryptoAlgorithmHKDF::platformDeriveBits(const 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(WEB_CRYPTO)
166