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