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