b7ad4b1d81df3041af6cf2bd56b55119b52a8b63
[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 Vector<uint8_t> CryptoKeyEC::platformExportRaw() const
104 {
105     Vector<uint8_t> result(keySizeInBits() / 4 + 1); // Per Section 2.3.4 of http://www.secg.org/sec1-v2.pdf
106     size_t size = result.size();
107     CCECCryptorExportKey(kCCImportKeyBinary, result.data(), &size, ccECKeyPublic, m_platformKey);
108     return result;
109 }
110
111 std::optional<CryptoKeyPair> CryptoKeyEC::platformGeneratePair(CryptoAlgorithmIdentifier identifier, NamedCurve curve, bool extractable, CryptoKeyUsageBitmap usages)
112 {
113     size_t size = getKeySizeFromNamedCurve(curve);
114     CCECCryptorRef ccPublicKey;
115     CCECCryptorRef ccPrivateKey;
116     if (CCECCryptorGeneratePair(size, &ccPublicKey, &ccPrivateKey))
117         return std::nullopt;
118
119     auto publicKey = CryptoKeyEC::create(identifier, curve, CryptoKeyType::Public, ccPublicKey, true, usages);
120     auto privateKey = CryptoKeyEC::create(identifier, curve, CryptoKeyType::Private, ccPrivateKey, extractable, usages);
121     return CryptoKeyPair { WTFMove(publicKey), WTFMove(privateKey) };
122 }
123
124 RefPtr<CryptoKeyEC> CryptoKeyEC::platformImportRaw(CryptoAlgorithmIdentifier identifier, NamedCurve curve, Vector<uint8_t>&& keyData, bool extractable, CryptoKeyUsageBitmap usages)
125 {
126     if (!doesUncompressedPointMatchNamedCurve(curve, keyData.size()))
127         return nullptr;
128
129     CCECCryptorRef ccPublicKey;
130     if (CCECCryptorImportKey(kCCImportKeyBinary, keyData.data(), keyData.size(), ccECKeyPublic, &ccPublicKey))
131         return nullptr;
132
133     return create(identifier, curve, CryptoKeyType::Public, ccPublicKey, extractable, usages);
134 }
135
136 RefPtr<CryptoKeyEC> CryptoKeyEC::platformImportJWKPublic(CryptoAlgorithmIdentifier identifier, NamedCurve curve, Vector<uint8_t>&& x, Vector<uint8_t>&& y, bool extractable, CryptoKeyUsageBitmap usages)
137 {
138     if (!doesFieldElementMatchNamedCurve(curve, x.size()) || !doesFieldElementMatchNamedCurve(curve, y.size()))
139         return nullptr;
140
141     size_t size = getKeySizeFromNamedCurve(curve);
142     CCECCryptorRef ccPublicKey;
143     if (CCECCryptorCreateFromData(size, x.data(), x.size(), y.data(), y.size(), &ccPublicKey))
144         return nullptr;
145
146     return create(identifier, curve, CryptoKeyType::Public, ccPublicKey, extractable, usages);
147 }
148
149 RefPtr<CryptoKeyEC> CryptoKeyEC::platformImportJWKPrivate(CryptoAlgorithmIdentifier identifier, NamedCurve curve, Vector<uint8_t>&& x, Vector<uint8_t>&& y, Vector<uint8_t>&& d, bool extractable, CryptoKeyUsageBitmap usages)
150 {
151     if (!doesFieldElementMatchNamedCurve(curve, x.size()) || !doesFieldElementMatchNamedCurve(curve, y.size()) || !doesFieldElementMatchNamedCurve(curve, d.size()))
152         return nullptr;
153
154     // A hack to CommonCrypto since it doesn't provide API for creating private keys directly from x, y, d.
155     // BinaryInput = InitialOctetEC + X + Y + D
156     Vector<uint8_t> binaryInput;
157     binaryInput.append(InitialOctetEC);
158     binaryInput.appendVector(x);
159     binaryInput.appendVector(y);
160     binaryInput.appendVector(d);
161
162     CCECCryptorRef ccPrivateKey;
163     if (CCECCryptorImportKey(kCCImportKeyBinary, binaryInput.data(), binaryInput.size(), ccECKeyPrivate, &ccPrivateKey))
164         return nullptr;
165
166     return create(identifier, curve, CryptoKeyType::Private, ccPrivateKey, extractable, usages);
167 }
168
169 void CryptoKeyEC::platformAddFieldElements(JsonWebKey& jwk) const
170 {
171     size_t size = getKeySizeFromNamedCurve(m_curve);
172     size_t sizeInBytes = size / 8;
173     Vector<uint8_t> x(sizeInBytes);
174     size_t xSize = x.size();
175     Vector<uint8_t> y(sizeInBytes);
176     size_t ySize = y.size();
177     Vector<uint8_t> d(sizeInBytes);
178     size_t dSize = d.size();
179
180     CCECCryptorGetKeyComponents(m_platformKey, &size, x.data(), &xSize, y.data(), &ySize, d.data(), &dSize);
181     jwk.x = base64URLEncode(x);
182     jwk.y = base64URLEncode(y);
183     if (type() == Type::Private)
184         jwk.d = base64URLEncode(d);
185 }
186
187 static size_t getOID(CryptoKeyEC::NamedCurve curve, const uint8_t*& oid)
188 {
189     size_t oidSize;
190     switch (curve) {
191     case CryptoKeyEC::NamedCurve::P256:
192         oid = Secp256r1;
193         oidSize = sizeof(Secp256r1);
194         break;
195     case CryptoKeyEC::NamedCurve::P384:
196         oid = Secp384r1;
197         oidSize = sizeof(Secp384r1);
198     }
199     return oidSize;
200 }
201
202 // Per https://www.ietf.org/rfc/rfc5280.txt
203 // SubjectPublicKeyInfo ::= SEQUENCE { algorithm AlgorithmIdentifier, subjectPublicKey BIT STRING }
204 // AlgorithmIdentifier  ::= SEQUENCE { algorithm OBJECT IDENTIFIER, parameters ANY DEFINED BY algorithm OPTIONAL }
205 // Per https://www.ietf.org/rfc/rfc5480.txt
206 // id-ecPublicKey OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) ansi-X9-62(10045) keyType(2) 1 }
207 // secp256r1 OBJECT IDENTIFIER      ::= { iso(1) member-body(2) us(840) ansi-X9-62(10045) curves(3) prime(1) 7 }
208 // secp384r1 OBJECT IDENTIFIER      ::= { iso(1) identified-organization(3) certicom(132) curve(0) 34 }
209 RefPtr<CryptoKeyEC> CryptoKeyEC::platformImportSpki(CryptoAlgorithmIdentifier identifier, NamedCurve curve, Vector<uint8_t>&& keyData, bool extractable, CryptoKeyUsageBitmap usages)
210 {
211     // The following is a loose check on the provided SPKI key, it aims to extract AlgorithmIdentifier, ECParameters, and Key.
212     // Once the underlying crypto library is updated to accept SPKI EC Key, we should remove this hack.
213     // <rdar://problem/30987628>
214     size_t index = 1; // Read SEQUENCE
215     if (keyData.size() < index + 1)
216         return nullptr;
217     index += bytesUsedToEncodedLength(keyData[index]) + 1; // Read length, SEQUENCE
218     if (keyData.size() < index + 1)
219         return nullptr;
220     index += bytesUsedToEncodedLength(keyData[index]); // Read length
221     if (keyData.size() < index + sizeof(IdEcPublicKey))
222         return nullptr;
223     if (memcmp(keyData.data() + index, IdEcPublicKey, sizeof(IdEcPublicKey)))
224         return nullptr;
225     index += sizeof(IdEcPublicKey); // Read id-ecPublicKey
226     const uint8_t* oid;
227     size_t oidSize = getOID(curve, oid);
228     if (keyData.size() < index + oidSize)
229         return nullptr;
230     if (memcmp(keyData.data() + index, oid, oidSize))
231         return nullptr;
232     index += oidSize + 1; // Read named curve OID, BIT STRING
233     if (keyData.size() < index + 1)
234         return nullptr;
235     index += bytesUsedToEncodedLength(keyData[index]) + 1; // Read length, InitialOctet
236
237     if (!doesUncompressedPointMatchNamedCurve(curve, keyData.size() - index))
238         return nullptr;
239
240     CCECCryptorRef ccPublicKey;
241     if (CCECCryptorImportKey(kCCImportKeyBinary, keyData.data() + index, keyData.size() - index, ccECKeyPublic, &ccPublicKey))
242         return nullptr;
243
244     return create(identifier, curve, CryptoKeyType::Public, ccPublicKey, extractable, usages);
245 }
246
247 Vector<uint8_t> CryptoKeyEC::platformExportSpki() const
248 {
249     Vector<uint8_t> keyBytes(keySizeInBits() / 4 + 1); // Per Section 2.3.4 of http://www.secg.org/sec1-v2.pdf
250     size_t keySize = keyBytes.size();
251     CCECCryptorExportKey(kCCImportKeyBinary, keyBytes.data(), &keySize, ccECKeyPublic, m_platformKey);
252
253     // The following addes SPKI header to a raw EC public key.
254     // Once the underlying crypto library is updated to output SPKI EC Key, we should remove this hack.
255     // <rdar://problem/30987628>
256     const uint8_t* oid;
257     size_t oidSize = getOID(namedCurve(), oid);
258
259     // SEQUENCE + length(1) + OID id-ecPublicKey + OID secp256r1/OID secp384r1 + BIT STRING + length(?) + InitialOctet + Key size
260     size_t totalSize = sizeof(IdEcPublicKey) + oidSize + bytesNeededForEncodedLength(keySize + 1) + keySize + 4;
261
262     Vector<uint8_t> result;
263     result.reserveCapacity(totalSize + bytesNeededForEncodedLength(totalSize) + 1);
264     result.append(SequenceMark);
265     addEncodedASN1Length(result, totalSize);
266     result.append(SequenceMark);
267     addEncodedASN1Length(result, sizeof(IdEcPublicKey) + oidSize);
268     result.append(IdEcPublicKey, sizeof(IdEcPublicKey));
269     result.append(oid, oidSize);
270     result.append(BitStringMark);
271     addEncodedASN1Length(result, keySize + 1);
272     result.append(InitialOctet);
273     result.append(keyBytes.data(), keyBytes.size());
274
275     return result;
276 }
277
278 // Per https://www.ietf.org/rfc/rfc5208.txt
279 // PrivateKeyInfo ::= SEQUENCE { version INTEGER, privateKeyAlgorithm AlgorithmIdentifier, privateKey OCTET STRING { ECPrivateKey } }
280 // Per https://www.ietf.org/rfc/rfc5915.txt
281 // ECPrivateKey ::= SEQUENCE { version INTEGER { ecPrivkeyVer1(1) }, privateKey OCTET STRING, parameters CustomECParameters, publicKey BIT STRING }
282 // OpenSSL uses custom ECParameters. We follow OpenSSL as a compatibility concern.
283 RefPtr<CryptoKeyEC> CryptoKeyEC::platformImportPkcs8(CryptoAlgorithmIdentifier identifier, NamedCurve curve, Vector<uint8_t>&& keyData, bool extractable, CryptoKeyUsageBitmap usages)
284 {
285     // The following is a loose check on the provided PKCS8 key, it aims to extract AlgorithmIdentifier, ECParameters, and Key.
286     // Once the underlying crypto library is updated to accept PKCS8 EC Key, we should remove this hack.
287     // <rdar://problem/30987628>
288     size_t index = 1; // Read SEQUENCE
289     if (keyData.size() < index + 1)
290         return nullptr;
291     index += bytesUsedToEncodedLength(keyData[index]) + 4; // Read length, version, SEQUENCE
292     if (keyData.size() < index + 1)
293         return nullptr;
294     index += bytesUsedToEncodedLength(keyData[index]); // Read length
295     if (keyData.size() < index + sizeof(IdEcPublicKey))
296         return nullptr;
297     if (memcmp(keyData.data() + index, IdEcPublicKey, sizeof(IdEcPublicKey)))
298         return nullptr;
299     index += sizeof(IdEcPublicKey); // Read id-ecPublicKey
300     const uint8_t* oid;
301     size_t oidSize = getOID(curve, oid);
302     if (keyData.size() < index + oidSize)
303         return nullptr;
304     if (memcmp(keyData.data() + index, oid, oidSize))
305         return nullptr;
306     index += oidSize + 1; // Read named curve OID, OCTET STRING
307     if (keyData.size() < index + 1)
308         return nullptr;
309     index += bytesUsedToEncodedLength(keyData[index]) + 1; // Read length, SEQUENCE
310     if (keyData.size() < index + 1)
311         return nullptr;
312     index += bytesUsedToEncodedLength(keyData[index]) + 4; // Read length, version, OCTET STRING
313     if (keyData.size() < index + 1)
314         return nullptr;
315     index += bytesUsedToEncodedLength(keyData[index]); // Read length
316
317     if (keyData.size() < index + getKeySizeFromNamedCurve(curve) / 8)
318         return nullptr;
319     size_t privateKeyPos = index;
320     index += getKeySizeFromNamedCurve(curve) / 8 + 1; // Read privateKey, TaggedType1
321     if (keyData.size() < index + 1)
322         return nullptr;
323     index += bytesUsedToEncodedLength(keyData[index]) + 1; // Read length, BIT STRING
324     if (keyData.size() < index + 1)
325         return nullptr;
326     index += bytesUsedToEncodedLength(keyData[index]) + 1; // Read length, InitialOctet
327
328     // KeyBinary = uncompressed point + private key
329     Vector<uint8_t> keyBinary;
330     keyBinary.append(keyData.data() + index, keyData.size() - index);
331     if (!doesUncompressedPointMatchNamedCurve(curve, keyBinary.size()))
332         return nullptr;
333     keyBinary.append(keyData.data() + privateKeyPos, getKeySizeFromNamedCurve(curve) / 8);
334
335     CCECCryptorRef ccPrivateKey;
336     if (CCECCryptorImportKey(kCCImportKeyBinary, keyBinary.data(), keyBinary.size(), ccECKeyPrivate, &ccPrivateKey))
337         return nullptr;
338
339     return create(identifier, curve, CryptoKeyType::Private, ccPrivateKey, extractable, usages);
340 }
341
342 Vector<uint8_t> CryptoKeyEC::platformExportPkcs8() const
343 {
344     size_t keySizeInBytes = keySizeInBits() / 8;
345     Vector<uint8_t> keyBytes(keySizeInBytes * 3 + 1); // 04 + X + Y + private key
346     size_t keySize = keyBytes.size();
347     CCECCryptorExportKey(kCCImportKeyBinary, keyBytes.data(), &keySize, ccECKeyPrivate, m_platformKey);
348
349     // The following addes PKCS8 header to a raw EC private key.
350     // Once the underlying crypto library is updated to output PKCS8 EC Key, we should remove this hack.
351     // <rdar://problem/30987628>
352     const uint8_t* oid;
353     size_t oidSize = getOID(namedCurve(), oid);
354
355     // InitialOctet + 04 + X + Y
356     size_t publicKeySize = keySizeInBytes * 2 + 2;
357     // BIT STRING + length(?) + publicKeySize
358     size_t taggedTypeSize = bytesNeededForEncodedLength(publicKeySize) + publicKeySize + 1;
359     // VERSION + OCTET STRING + length(1) + private key + TaggedType1(1) + length(?) + BIT STRING + length(?) + publicKeySize
360     size_t ecPrivateKeySize = sizeof(Version) + keySizeInBytes + bytesNeededForEncodedLength(taggedTypeSize) + bytesNeededForEncodedLength(publicKeySize) + publicKeySize + 4;
361     // SEQUENCE + length(?) + ecPrivateKeySize
362     size_t privateKeySize = bytesNeededForEncodedLength(ecPrivateKeySize) + ecPrivateKeySize + 1;
363     // VERSION + SEQUENCE + length(1) + OID id-ecPublicKey + OID secp256r1/OID secp384r1 + OCTET STRING + length(?) + privateKeySize
364     size_t totalSize = sizeof(Version) + sizeof(IdEcPublicKey) + oidSize + bytesNeededForEncodedLength(privateKeySize) + privateKeySize + 3;
365
366     Vector<uint8_t> result;
367     result.reserveCapacity(totalSize + bytesNeededForEncodedLength(totalSize) + 1);
368     result.append(SequenceMark);
369     addEncodedASN1Length(result, totalSize);
370     result.append(Version, sizeof(Version));
371     result.append(SequenceMark);
372     addEncodedASN1Length(result, sizeof(IdEcPublicKey) + oidSize);
373     result.append(IdEcPublicKey, sizeof(IdEcPublicKey));
374     result.append(oid, oidSize);
375     result.append(OctetStringMark);
376     addEncodedASN1Length(result, privateKeySize);
377     result.append(SequenceMark);
378     addEncodedASN1Length(result, ecPrivateKeySize);
379     result.append(PrivateKeyVersion, sizeof(PrivateKeyVersion));
380     result.append(OctetStringMark);
381     addEncodedASN1Length(result, keySizeInBytes);
382     result.append(keyBytes.data() + publicKeySize - 1, keySizeInBytes);
383     result.append(TaggedType1);
384     addEncodedASN1Length(result, taggedTypeSize);
385     result.append(BitStringMark);
386     addEncodedASN1Length(result, publicKeySize);
387     result.append(InitialOctet);
388     result.append(keyBytes.data(), publicKeySize - 1);
389
390     return result;
391 }
392
393 } // namespace WebCore
394
395 #endif // ENABLE(SUBTLE_CRYPTO)