[WebCrypto][Mac] Replace CCECCryptorGetKeyComponents with CCECCryptorExportKey for...
[WebKit-https.git] / Source / WebCore / crypto / mac / CryptoKeyECMac.cpp
1 /*
2  * Copyright (C) 2017 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "CryptoKeyEC.h"
28
29 #if ENABLE(SUBTLE_CRYPTO)
30
31 #include "CommonCryptoDERUtilities.h"
32 #include "CommonCryptoUtilities.h"
33 #include "JsonWebKey.h"
34 #include <wtf/text/Base64.h>
35
36 namespace WebCore {
37
38 static const unsigned char InitialOctetEC = 0x04; // Per Section 2.3.3 of http://www.secg.org/sec1-v2.pdf
39 // OID id-ecPublicKey 1.2.840.10045.2.1.
40 static const unsigned char IdEcPublicKey[] = {0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01};
41 // OID secp256r1 1.2.840.10045.3.1.7.
42 static constexpr unsigned char Secp256r1[] = {0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07};
43 // OID secp384r1 1.3.132.0.34
44 static constexpr unsigned char Secp384r1[] = {0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22};
45 // Version 1. Per https://tools.ietf.org/html/rfc5915#section-3
46 static const unsigned char PrivateKeyVersion[] = {0x02, 0x01, 0x01};
47 // Tagged type [1]
48 static const unsigned char TaggedType1 = 0xa1;
49
50 // Per Section 2.3.4 of http://www.secg.org/sec1-v2.pdf
51 // We only support uncompressed point format.
52 static bool doesUncompressedPointMatchNamedCurve(CryptoKeyEC::NamedCurve curve, size_t size)
53 {
54     switch (curve) {
55     case CryptoKeyEC::NamedCurve::P256:
56         return size == 65;
57     case CryptoKeyEC::NamedCurve::P384:
58         return size == 97;
59     }
60
61     ASSERT_NOT_REACHED();
62     return false;
63 }
64
65 // Per Section 2.3.5 of http://www.secg.org/sec1-v2.pdf
66 static bool doesFieldElementMatchNamedCurve(CryptoKeyEC::NamedCurve curve, size_t size)
67 {
68     switch (curve) {
69     case CryptoKeyEC::NamedCurve::P256:
70         return size == 32;
71     case CryptoKeyEC::NamedCurve::P384:
72         return size == 48;
73     }
74
75     ASSERT_NOT_REACHED();
76     return false;
77 }
78
79 static size_t getKeySizeFromNamedCurve(CryptoKeyEC::NamedCurve curve)
80 {
81     switch (curve) {
82     case CryptoKeyEC::NamedCurve::P256:
83         return 256;
84     case CryptoKeyEC::NamedCurve::P384:
85         return 384;
86     }
87
88     ASSERT_NOT_REACHED();
89     return 0;
90 }
91
92 CryptoKeyEC::~CryptoKeyEC()
93 {
94     CCECCryptorRelease(m_platformKey);
95 }
96
97 size_t CryptoKeyEC::keySizeInBits() const
98 {
99     int result = CCECGetKeySize(m_platformKey);
100     return result ? result : 0;
101 }
102
103 std::optional<CryptoKeyPair> CryptoKeyEC::platformGeneratePair(CryptoAlgorithmIdentifier identifier, NamedCurve curve, bool extractable, CryptoKeyUsageBitmap usages)
104 {
105     size_t size = getKeySizeFromNamedCurve(curve);
106     CCECCryptorRef ccPublicKey;
107     CCECCryptorRef ccPrivateKey;
108     if (CCECCryptorGeneratePair(size, &ccPublicKey, &ccPrivateKey))
109         return std::nullopt;
110
111     auto publicKey = CryptoKeyEC::create(identifier, curve, CryptoKeyType::Public, ccPublicKey, true, usages);
112     auto privateKey = CryptoKeyEC::create(identifier, curve, CryptoKeyType::Private, ccPrivateKey, extractable, usages);
113     return CryptoKeyPair { WTFMove(publicKey), WTFMove(privateKey) };
114 }
115
116 RefPtr<CryptoKeyEC> CryptoKeyEC::platformImportRaw(CryptoAlgorithmIdentifier identifier, NamedCurve curve, Vector<uint8_t>&& keyData, bool extractable, CryptoKeyUsageBitmap usages)
117 {
118     if (!doesUncompressedPointMatchNamedCurve(curve, keyData.size()))
119         return nullptr;
120
121     CCECCryptorRef ccPublicKey;
122     if (CCECCryptorImportKey(kCCImportKeyBinary, keyData.data(), keyData.size(), ccECKeyPublic, &ccPublicKey))
123         return nullptr;
124
125     return create(identifier, curve, CryptoKeyType::Public, ccPublicKey, extractable, usages);
126 }
127
128 Vector<uint8_t> CryptoKeyEC::platformExportRaw() const
129 {
130     size_t expectedSize = keySizeInBits() / 4 + 1; // Per Section 2.3.4 of http://www.secg.org/sec1-v2.pdf
131     Vector<uint8_t> result(expectedSize);
132     size_t size = result.size();
133     if (UNLIKELY(CCECCryptorExportKey(kCCImportKeyBinary, result.data(), &size, ccECKeyPublic, m_platformKey) || size != expectedSize))
134         return { };
135     return result;
136 }
137
138 RefPtr<CryptoKeyEC> CryptoKeyEC::platformImportJWKPublic(CryptoAlgorithmIdentifier identifier, NamedCurve curve, Vector<uint8_t>&& x, Vector<uint8_t>&& y, bool extractable, CryptoKeyUsageBitmap usages)
139 {
140     if (!doesFieldElementMatchNamedCurve(curve, x.size()) || !doesFieldElementMatchNamedCurve(curve, y.size()))
141         return nullptr;
142
143     size_t size = getKeySizeFromNamedCurve(curve);
144     CCECCryptorRef ccPublicKey;
145     if (CCECCryptorCreateFromData(size, x.data(), x.size(), y.data(), y.size(), &ccPublicKey))
146         return nullptr;
147
148     return create(identifier, curve, CryptoKeyType::Public, ccPublicKey, extractable, usages);
149 }
150
151 RefPtr<CryptoKeyEC> CryptoKeyEC::platformImportJWKPrivate(CryptoAlgorithmIdentifier identifier, NamedCurve curve, Vector<uint8_t>&& x, Vector<uint8_t>&& y, Vector<uint8_t>&& d, bool extractable, CryptoKeyUsageBitmap usages)
152 {
153     if (!doesFieldElementMatchNamedCurve(curve, x.size()) || !doesFieldElementMatchNamedCurve(curve, y.size()) || !doesFieldElementMatchNamedCurve(curve, d.size()))
154         return nullptr;
155
156     // A hack to CommonCrypto since it doesn't provide API for creating private keys directly from x, y, d.
157     // BinaryInput = InitialOctetEC + X + Y + D
158     Vector<uint8_t> binaryInput;
159     binaryInput.append(InitialOctetEC);
160     binaryInput.appendVector(x);
161     binaryInput.appendVector(y);
162     binaryInput.appendVector(d);
163
164     CCECCryptorRef ccPrivateKey;
165     if (CCECCryptorImportKey(kCCImportKeyBinary, binaryInput.data(), binaryInput.size(), ccECKeyPrivate, &ccPrivateKey))
166         return nullptr;
167
168     return create(identifier, curve, CryptoKeyType::Private, ccPrivateKey, extractable, usages);
169 }
170
171 bool CryptoKeyEC::platformAddFieldElements(JsonWebKey& jwk) const
172 {
173     size_t keySizeInBytes = keySizeInBits() / 8;
174     size_t publicKeySize = keySizeInBytes * 2 + 1; // 04 + X + Y per Section 2.3.4 of http://www.secg.org/sec1-v2.pdf
175     size_t privateKeySize = keySizeInBytes * 3 + 1; // 04 + X + Y + D
176
177     Vector<uint8_t> result(privateKeySize);
178     size_t size = result.size();
179     switch (type()) {
180     case CryptoKeyType::Public:
181         if (UNLIKELY(CCECCryptorExportKey(kCCImportKeyBinary, result.data(), &size, ccECKeyPublic, m_platformKey)))
182             return false;
183         break;
184     case CryptoKeyType::Private:
185         if (UNLIKELY(CCECCryptorExportKey(kCCImportKeyBinary, result.data(), &size, ccECKeyPrivate, m_platformKey)))
186             return false;
187         break;
188     default:
189         ASSERT_NOT_REACHED();
190         return false;
191     }
192
193     if (UNLIKELY((size != publicKeySize) && (size != privateKeySize)))
194         return false;
195     jwk.x = WTF::base64URLEncode(result.data() + 1, keySizeInBytes);
196     jwk.y = WTF::base64URLEncode(result.data() + keySizeInBytes + 1, keySizeInBytes);
197     if (size > publicKeySize)
198         jwk.d = WTF::base64URLEncode(result.data() + publicKeySize, keySizeInBytes);
199     return true;
200 }
201
202 static size_t getOID(CryptoKeyEC::NamedCurve curve, const uint8_t*& oid)
203 {
204     size_t oidSize;
205     switch (curve) {
206     case CryptoKeyEC::NamedCurve::P256:
207         oid = Secp256r1;
208         oidSize = sizeof(Secp256r1);
209         break;
210     case CryptoKeyEC::NamedCurve::P384:
211         oid = Secp384r1;
212         oidSize = sizeof(Secp384r1);
213     }
214     return oidSize;
215 }
216
217 // Per https://www.ietf.org/rfc/rfc5280.txt
218 // SubjectPublicKeyInfo ::= SEQUENCE { algorithm AlgorithmIdentifier, subjectPublicKey BIT STRING }
219 // AlgorithmIdentifier  ::= SEQUENCE { algorithm OBJECT IDENTIFIER, parameters ANY DEFINED BY algorithm OPTIONAL }
220 // Per https://www.ietf.org/rfc/rfc5480.txt
221 // id-ecPublicKey OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) ansi-X9-62(10045) keyType(2) 1 }
222 // secp256r1 OBJECT IDENTIFIER      ::= { iso(1) member-body(2) us(840) ansi-X9-62(10045) curves(3) prime(1) 7 }
223 // secp384r1 OBJECT IDENTIFIER      ::= { iso(1) identified-organization(3) certicom(132) curve(0) 34 }
224 RefPtr<CryptoKeyEC> CryptoKeyEC::platformImportSpki(CryptoAlgorithmIdentifier identifier, NamedCurve curve, Vector<uint8_t>&& keyData, bool extractable, CryptoKeyUsageBitmap usages)
225 {
226     // The following is a loose check on the provided SPKI key, it aims to extract AlgorithmIdentifier, ECParameters, and Key.
227     // Once the underlying crypto library is updated to accept SPKI EC Key, we should remove this hack.
228     // <rdar://problem/30987628>
229     size_t index = 1; // Read SEQUENCE
230     if (keyData.size() < index + 1)
231         return nullptr;
232     index += bytesUsedToEncodedLength(keyData[index]) + 1; // Read length, SEQUENCE
233     if (keyData.size() < index + 1)
234         return nullptr;
235     index += bytesUsedToEncodedLength(keyData[index]); // Read length
236     if (keyData.size() < index + sizeof(IdEcPublicKey))
237         return nullptr;
238     if (memcmp(keyData.data() + index, IdEcPublicKey, sizeof(IdEcPublicKey)))
239         return nullptr;
240     index += sizeof(IdEcPublicKey); // Read id-ecPublicKey
241     const uint8_t* oid;
242     size_t oidSize = getOID(curve, oid);
243     if (keyData.size() < index + oidSize)
244         return nullptr;
245     if (memcmp(keyData.data() + index, oid, oidSize))
246         return nullptr;
247     index += oidSize + 1; // Read named curve OID, BIT STRING
248     if (keyData.size() < index + 1)
249         return nullptr;
250     index += bytesUsedToEncodedLength(keyData[index]) + 1; // Read length, InitialOctet
251
252     if (!doesUncompressedPointMatchNamedCurve(curve, keyData.size() - index))
253         return nullptr;
254
255     CCECCryptorRef ccPublicKey;
256     if (CCECCryptorImportKey(kCCImportKeyBinary, keyData.data() + index, keyData.size() - index, ccECKeyPublic, &ccPublicKey))
257         return nullptr;
258
259     return create(identifier, curve, CryptoKeyType::Public, ccPublicKey, extractable, usages);
260 }
261
262 Vector<uint8_t> CryptoKeyEC::platformExportSpki() const
263 {
264     size_t expectedKeySize = keySizeInBits() / 4 + 1; // Per Section 2.3.4 of http://www.secg.org/sec1-v2.pdf
265     Vector<uint8_t> keyBytes(expectedKeySize);
266     size_t keySize = keyBytes.size();
267     if (UNLIKELY(CCECCryptorExportKey(kCCImportKeyBinary, keyBytes.data(), &keySize, ccECKeyPublic, m_platformKey) || keySize != expectedKeySize))
268         return { };
269
270     // The following addes SPKI header to a raw EC public key.
271     // Once the underlying crypto library is updated to output SPKI EC Key, we should remove this hack.
272     // <rdar://problem/30987628>
273     const uint8_t* oid;
274     size_t oidSize = getOID(namedCurve(), oid);
275
276     // SEQUENCE + length(1) + OID id-ecPublicKey + OID secp256r1/OID secp384r1 + BIT STRING + length(?) + InitialOctet + Key size
277     size_t totalSize = sizeof(IdEcPublicKey) + oidSize + bytesNeededForEncodedLength(keySize + 1) + keySize + 4;
278
279     Vector<uint8_t> result;
280     result.reserveCapacity(totalSize + bytesNeededForEncodedLength(totalSize) + 1);
281     result.append(SequenceMark);
282     addEncodedASN1Length(result, totalSize);
283     result.append(SequenceMark);
284     addEncodedASN1Length(result, sizeof(IdEcPublicKey) + oidSize);
285     result.append(IdEcPublicKey, sizeof(IdEcPublicKey));
286     result.append(oid, oidSize);
287     result.append(BitStringMark);
288     addEncodedASN1Length(result, keySize + 1);
289     result.append(InitialOctet);
290     result.append(keyBytes.data(), keyBytes.size());
291
292     return result;
293 }
294
295 // Per https://www.ietf.org/rfc/rfc5208.txt
296 // PrivateKeyInfo ::= SEQUENCE { version INTEGER, privateKeyAlgorithm AlgorithmIdentifier, privateKey OCTET STRING { ECPrivateKey } }
297 // Per https://www.ietf.org/rfc/rfc5915.txt
298 // ECPrivateKey ::= SEQUENCE { version INTEGER { ecPrivkeyVer1(1) }, privateKey OCTET STRING, parameters CustomECParameters, publicKey BIT STRING }
299 // OpenSSL uses custom ECParameters. We follow OpenSSL as a compatibility concern.
300 RefPtr<CryptoKeyEC> CryptoKeyEC::platformImportPkcs8(CryptoAlgorithmIdentifier identifier, NamedCurve curve, Vector<uint8_t>&& keyData, bool extractable, CryptoKeyUsageBitmap usages)
301 {
302     // The following is a loose check on the provided PKCS8 key, it aims to extract AlgorithmIdentifier, ECParameters, and Key.
303     // Once the underlying crypto library is updated to accept PKCS8 EC Key, we should remove this hack.
304     // <rdar://problem/30987628>
305     size_t index = 1; // Read SEQUENCE
306     if (keyData.size() < index + 1)
307         return nullptr;
308     index += bytesUsedToEncodedLength(keyData[index]) + 4; // Read length, version, SEQUENCE
309     if (keyData.size() < index + 1)
310         return nullptr;
311     index += bytesUsedToEncodedLength(keyData[index]); // Read length
312     if (keyData.size() < index + sizeof(IdEcPublicKey))
313         return nullptr;
314     if (memcmp(keyData.data() + index, IdEcPublicKey, sizeof(IdEcPublicKey)))
315         return nullptr;
316     index += sizeof(IdEcPublicKey); // Read id-ecPublicKey
317     const uint8_t* oid;
318     size_t oidSize = getOID(curve, oid);
319     if (keyData.size() < index + oidSize)
320         return nullptr;
321     if (memcmp(keyData.data() + index, oid, oidSize))
322         return nullptr;
323     index += oidSize + 1; // Read named curve OID, OCTET STRING
324     if (keyData.size() < index + 1)
325         return nullptr;
326     index += bytesUsedToEncodedLength(keyData[index]) + 1; // Read length, SEQUENCE
327     if (keyData.size() < index + 1)
328         return nullptr;
329     index += bytesUsedToEncodedLength(keyData[index]) + 4; // Read length, version, OCTET STRING
330     if (keyData.size() < index + 1)
331         return nullptr;
332     index += bytesUsedToEncodedLength(keyData[index]); // Read length
333
334     if (keyData.size() < index + getKeySizeFromNamedCurve(curve) / 8)
335         return nullptr;
336     size_t privateKeyPos = index;
337     index += getKeySizeFromNamedCurve(curve) / 8 + 1; // Read privateKey, TaggedType1
338     if (keyData.size() < index + 1)
339         return nullptr;
340     index += bytesUsedToEncodedLength(keyData[index]) + 1; // Read length, BIT STRING
341     if (keyData.size() < index + 1)
342         return nullptr;
343     index += bytesUsedToEncodedLength(keyData[index]) + 1; // Read length, InitialOctet
344
345     // KeyBinary = uncompressed point + private key
346     Vector<uint8_t> keyBinary;
347     keyBinary.append(keyData.data() + index, keyData.size() - index);
348     if (!doesUncompressedPointMatchNamedCurve(curve, keyBinary.size()))
349         return nullptr;
350     keyBinary.append(keyData.data() + privateKeyPos, getKeySizeFromNamedCurve(curve) / 8);
351
352     CCECCryptorRef ccPrivateKey;
353     if (CCECCryptorImportKey(kCCImportKeyBinary, keyBinary.data(), keyBinary.size(), ccECKeyPrivate, &ccPrivateKey))
354         return nullptr;
355
356     return create(identifier, curve, CryptoKeyType::Private, ccPrivateKey, extractable, usages);
357 }
358
359 Vector<uint8_t> CryptoKeyEC::platformExportPkcs8() const
360 {
361     size_t keySizeInBytes = keySizeInBits() / 8;
362     size_t expectedKeySize = keySizeInBytes * 3 + 1; // 04 + X + Y + D
363     Vector<uint8_t> keyBytes(expectedKeySize);
364     size_t keySize = keyBytes.size();
365     if (UNLIKELY(CCECCryptorExportKey(kCCImportKeyBinary, keyBytes.data(), &keySize, ccECKeyPrivate, m_platformKey) || keySize != expectedKeySize))
366         return { };
367
368     // The following addes PKCS8 header to a raw EC private key.
369     // Once the underlying crypto library is updated to output PKCS8 EC Key, we should remove this hack.
370     // <rdar://problem/30987628>
371     const uint8_t* oid;
372     size_t oidSize = getOID(namedCurve(), oid);
373
374     // InitialOctet + 04 + X + Y
375     size_t publicKeySize = keySizeInBytes * 2 + 2;
376     // BIT STRING + length(?) + publicKeySize
377     size_t taggedTypeSize = bytesNeededForEncodedLength(publicKeySize) + publicKeySize + 1;
378     // VERSION + OCTET STRING + length(1) + private key + TaggedType1(1) + length(?) + BIT STRING + length(?) + publicKeySize
379     size_t ecPrivateKeySize = sizeof(Version) + keySizeInBytes + bytesNeededForEncodedLength(taggedTypeSize) + bytesNeededForEncodedLength(publicKeySize) + publicKeySize + 4;
380     // SEQUENCE + length(?) + ecPrivateKeySize
381     size_t privateKeySize = bytesNeededForEncodedLength(ecPrivateKeySize) + ecPrivateKeySize + 1;
382     // VERSION + SEQUENCE + length(1) + OID id-ecPublicKey + OID secp256r1/OID secp384r1 + OCTET STRING + length(?) + privateKeySize
383     size_t totalSize = sizeof(Version) + sizeof(IdEcPublicKey) + oidSize + bytesNeededForEncodedLength(privateKeySize) + privateKeySize + 3;
384
385     Vector<uint8_t> result;
386     result.reserveCapacity(totalSize + bytesNeededForEncodedLength(totalSize) + 1);
387     result.append(SequenceMark);
388     addEncodedASN1Length(result, totalSize);
389     result.append(Version, sizeof(Version));
390     result.append(SequenceMark);
391     addEncodedASN1Length(result, sizeof(IdEcPublicKey) + oidSize);
392     result.append(IdEcPublicKey, sizeof(IdEcPublicKey));
393     result.append(oid, oidSize);
394     result.append(OctetStringMark);
395     addEncodedASN1Length(result, privateKeySize);
396     result.append(SequenceMark);
397     addEncodedASN1Length(result, ecPrivateKeySize);
398     result.append(PrivateKeyVersion, sizeof(PrivateKeyVersion));
399     result.append(OctetStringMark);
400     addEncodedASN1Length(result, keySizeInBytes);
401     result.append(keyBytes.data() + publicKeySize - 1, keySizeInBytes);
402     result.append(TaggedType1);
403     addEncodedASN1Length(result, taggedTypeSize);
404     result.append(BitStringMark);
405     addEncodedASN1Length(result, publicKeySize);
406     result.append(InitialOctet);
407     result.append(keyBytes.data(), publicKeySize - 1);
408
409     return result;
410 }
411
412 } // namespace WebCore
413
414 #endif // ENABLE(SUBTLE_CRYPTO)