Move URL from WebCore to WTF
[WebKit-https.git] / Source / WebCore / platform / mac / SSLKeyGeneratorMac.mm
1 /*
2  * Copyright (C) 2003-2018 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 #import "config.h"
27 #import "SSLKeyGenerator.h"
28
29 #if PLATFORM(MAC)
30
31 #import "LocalizedStrings.h"
32 #import <Security/SecAsn1Coder.h>
33 #import <Security/SecAsn1Templates.h>
34 #import <Security/SecEncodeTransform.h>
35 #import <wtf/ProcessPrivilege.h>
36 #import <wtf/RetainPtr.h>
37 #import <wtf/Scope.h>
38 #import <wtf/URL.h>
39 #import <wtf/cf/TypeCastsCF.h>
40 #import <wtf/spi/cocoa/SecuritySPI.h>
41 #import <wtf/text/Base64.h>
42
43 WTF_DECLARE_CF_TYPE_TRAIT(SecACL);
44
45 namespace WebCore {
46
47 ALLOW_DEPRECATED_DECLARATIONS_BEGIN
48
49 struct PublicKeyAndChallenge {
50     CSSM_X509_SUBJECT_PUBLIC_KEY_INFO subjectPublicKeyInfo;
51     CSSM_DATA challenge;
52 };
53
54 struct SignedPublicKeyAndChallenge {
55     PublicKeyAndChallenge publicKeyAndChallenge;
56     CSSM_X509_ALGORITHM_IDENTIFIER algorithmIdentifier;
57     CSSM_DATA signature;
58 };
59
60 static const SecAsn1Template publicKeyAndChallengeTemplate[] {
61     { SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(PublicKeyAndChallenge) },
62     { SEC_ASN1_INLINE, offsetof(PublicKeyAndChallenge, subjectPublicKeyInfo), kSecAsn1SubjectPublicKeyInfoTemplate, 0},
63     { SEC_ASN1_INLINE, offsetof(PublicKeyAndChallenge, challenge), kSecAsn1IA5StringTemplate, 0 },
64     { 0, 0, 0, 0}
65 };
66
67 static const SecAsn1Template signedPublicKeyAndChallengeTemplate[] {
68     { SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(SignedPublicKeyAndChallenge) },
69     { SEC_ASN1_INLINE, offsetof(SignedPublicKeyAndChallenge, publicKeyAndChallenge), publicKeyAndChallengeTemplate, 0 },
70     { SEC_ASN1_INLINE, offsetof(SignedPublicKeyAndChallenge, algorithmIdentifier), kSecAsn1AlgorithmIDTemplate, 0 },
71     { SEC_ASN1_BIT_STRING, offsetof(SignedPublicKeyAndChallenge, signature), 0, 0 },
72     { 0, 0, 0, 0 }
73 };
74
75 static bool getSubjectPublicKey(CSSM_CSP_HANDLE cspHandle, SecKeyRef publicKey, CSSM_KEY_PTR subjectPublicKey)
76 {
77     const CSSM_KEY* cssmPublicKey;
78     if (SecKeyGetCSSMKey(publicKey, &cssmPublicKey) != noErr)
79         return false;
80
81     CSSM_ACCESS_CREDENTIALS credentials { };
82     CSSM_CC_HANDLE ccHandle;
83     if (CSSM_CSP_CreateSymmetricContext(cspHandle, CSSM_ALGID_NONE, CSSM_ALGMODE_NONE, &credentials, nullptr, nullptr, CSSM_PADDING_NONE, 0, &ccHandle) != noErr)
84         return false;
85
86     auto deleteContext = makeScopeExit([&] {
87         CSSM_DeleteContext(ccHandle);
88     });
89
90     CSSM_CONTEXT_ATTRIBUTE publicKeyFormatAttribute;
91     publicKeyFormatAttribute.AttributeType = CSSM_ATTRIBUTE_PUBLIC_KEY_FORMAT;
92     publicKeyFormatAttribute.AttributeLength = sizeof(uint32);
93     publicKeyFormatAttribute.Attribute.Data = (CSSM_DATA_PTR)CSSM_KEYBLOB_RAW_FORMAT_X509;
94
95     if (CSSM_UpdateContextAttributes(ccHandle, 1, &publicKeyFormatAttribute) != noErr)
96         return false;
97
98     if (CSSM_WrapKey(ccHandle, &credentials, cssmPublicKey, nullptr, subjectPublicKey) != noErr)
99         return false;
100
101     return true;
102 }
103
104 static bool signPublicKeyAndChallenge(CSSM_CSP_HANDLE cspHandle, const CSSM_DATA* plainText, SecKeyRef privateKey, CSSM_ALGORITHMS algorithmID, CSSM_DATA& signature)
105 {
106     ASSERT(!signature.Data);
107     ASSERT(!signature.Length);
108
109     const CSSM_KEY* cssmPrivateKey;
110     if (SecKeyGetCSSMKey(privateKey, &cssmPrivateKey) != noErr)
111         return false;
112
113     const CSSM_ACCESS_CREDENTIALS* credentials;
114     if (SecKeyGetCredentials(privateKey, CSSM_ACL_AUTHORIZATION_SIGN, kSecCredentialTypeDefault, &credentials) != noErr)
115         return false;
116
117     CSSM_CC_HANDLE ccHandle;
118     if (CSSM_CSP_CreateSignatureContext(cspHandle, algorithmID, credentials, cssmPrivateKey, &ccHandle) != noErr)
119         return false;
120     auto deleteContext = makeScopeExit([&] {
121         CSSM_DeleteContext(ccHandle);
122     });
123
124     if (CSSM_SignData(ccHandle, plainText, 1, CSSM_ALGID_NONE, &signature) != noErr)
125         return false;
126
127     return true;
128 }
129
130 static String signedPublicKeyAndChallengeString(unsigned keySize, const CString& challenge, const String& keyDescription)
131 {
132     ASSERT(keySize >= 2048);
133
134     SignedPublicKeyAndChallenge signedPublicKeyAndChallenge { };
135
136     SecAccessRef accessRef { nullptr };
137     if (SecAccessCreate(keyDescription.createCFString().get(), nullptr, &accessRef) != noErr)
138         return String();
139     RetainPtr<SecAccessRef> access = adoptCF(accessRef);
140
141     CFArrayRef aclsRef { nullptr };
142     if (SecAccessCopySelectedACLList(access.get(), CSSM_ACL_AUTHORIZATION_DECRYPT, &aclsRef) != noErr)
143         return String();
144     RetainPtr<CFArrayRef> acls = adoptCF(aclsRef);
145
146     SecACLRef acl = checked_cf_cast<SecACLRef>(CFArrayGetValueAtIndex(acls.get(), 0));
147
148     // Passing nullptr to SecTrustedApplicationCreateFromPath tells that function to assume the application bundle.
149     SecTrustedApplicationRef trustedAppRef { nullptr };
150     if (SecTrustedApplicationCreateFromPath(nullptr, &trustedAppRef) != noErr)
151         return String();
152     RetainPtr<SecTrustedApplicationRef> trustedApp = adoptCF(trustedAppRef);
153
154     const CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR defaultSelector = {
155         CSSM_ACL_KEYCHAIN_PROMPT_CURRENT_VERSION, 0
156     };
157     if (SecACLSetSimpleContents(acl, (__bridge CFArrayRef)@[ (__bridge id)trustedApp.get() ], keyDescription.createCFString().get(), &defaultSelector) != noErr)
158         return String();
159
160     SecKeyRef publicKeyRef { nullptr };
161     SecKeyRef privateKeyRef { nullptr };
162     if (SecKeyCreatePair(nullptr, CSSM_ALGID_RSA, keySize, 0, CSSM_KEYUSE_ANY, CSSM_KEYATTR_PERMANENT | CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_RETURN_REF, CSSM_KEYUSE_ANY, CSSM_KEYATTR_SENSITIVE | CSSM_KEYATTR_RETURN_REF | CSSM_KEYATTR_PERMANENT | CSSM_KEYATTR_EXTRACTABLE, access.get(), &publicKeyRef, &privateKeyRef) != noErr)
163         return String();
164     RetainPtr<SecKeyRef> publicKey = adoptCF(publicKeyRef);
165     RetainPtr<SecKeyRef> privateKey = adoptCF(privateKeyRef);
166
167     CSSM_CSP_HANDLE cspHandle;
168     if (SecKeyGetCSPHandle(privateKey.get(), &cspHandle) != noErr)
169         return String();
170     
171     CSSM_KEY subjectPublicKey { };
172     if (!getSubjectPublicKey(cspHandle, publicKey.get(), &subjectPublicKey))
173         return String();
174     auto freeSubjectPublicKey = makeScopeExit([&] {
175         CSSM_FreeKey(cspHandle, nullptr, &subjectPublicKey, CSSM_FALSE);
176     });
177
178     SecAsn1CoderRef coder = nullptr;
179     if (SecAsn1CoderCreate(&coder) != noErr)
180         return String();
181     auto releaseCoder = makeScopeExit([&] {
182         SecAsn1CoderRelease(coder);
183     });
184
185     if (SecAsn1DecodeData(coder, &subjectPublicKey.KeyData, kSecAsn1SubjectPublicKeyInfoTemplate, &signedPublicKeyAndChallenge.publicKeyAndChallenge.subjectPublicKeyInfo) != noErr)
186         return String();
187
188     ASSERT(challenge.data());
189
190     // Length needs to account for the null terminator.
191     signedPublicKeyAndChallenge.publicKeyAndChallenge.challenge.Length = challenge.length() + 1;
192     signedPublicKeyAndChallenge.publicKeyAndChallenge.challenge.Data = reinterpret_cast<uint8_t*>(const_cast<char*>(challenge.data()));
193
194     CSSM_DATA encodedPublicKeyAndChallenge { 0, nullptr };
195     if (SecAsn1EncodeItem(coder, &signedPublicKeyAndChallenge.publicKeyAndChallenge, publicKeyAndChallengeTemplate, &encodedPublicKeyAndChallenge) != noErr)
196         return String();
197
198     CSSM_DATA signature { };
199     if (!signPublicKeyAndChallenge(cspHandle, &encodedPublicKeyAndChallenge, privateKey.get(), CSSM_ALGID_MD5WithRSA, signature))
200         return String();
201     auto freeSignatureData = makeScopeExit([&] {
202         CSSM_API_MEMORY_FUNCS memoryFunctions;
203         if (CSSM_GetAPIMemoryFunctions(cspHandle, &memoryFunctions) != noErr)
204             return;
205
206         memoryFunctions.free_func(signature.Data, memoryFunctions.AllocRef);
207     });
208
209     uint8 encodeNull[2] { SEC_ASN1_NULL, 0 };
210     signedPublicKeyAndChallenge.algorithmIdentifier.algorithm = CSSMOID_MD5WithRSA;
211     signedPublicKeyAndChallenge.algorithmIdentifier.parameters.Data = (uint8 *)encodeNull;
212     signedPublicKeyAndChallenge.algorithmIdentifier.parameters.Length = 2;
213     signedPublicKeyAndChallenge.signature = signature;
214
215     // We want the signature length to be in bits.
216     signedPublicKeyAndChallenge.signature.Length *= 8;
217
218     CSSM_DATA encodedSignedPublicKeyAndChallenge { 0, nullptr };
219     if (SecAsn1EncodeItem(coder, &signedPublicKeyAndChallenge, signedPublicKeyAndChallengeTemplate, &encodedSignedPublicKeyAndChallenge) != noErr)
220         return String();
221
222     return base64Encode(encodedSignedPublicKeyAndChallenge.Data, encodedSignedPublicKeyAndChallenge.Length);
223 }
224
225 ALLOW_DEPRECATED_DECLARATIONS_END
226
227 void getSupportedKeySizes(Vector<String>& supportedKeySizes)
228 {
229     ASSERT(supportedKeySizes.isEmpty());
230     supportedKeySizes.append(keygenMenuItem2048());
231 }
232
233 String signedPublicKeyAndChallengeString(unsigned keySizeIndex, const String& challengeString, const URL& url)
234 {
235     // This switch statement must always be synced with the UI strings returned by getSupportedKeySizes.
236     UInt32 keySize;
237     switch (keySizeIndex) {
238     case 0:
239         keySize = 2048;
240         break;
241     default:
242         ASSERT_NOT_REACHED();
243         return String();
244     }
245
246     auto challenge = challengeString.isAllASCII() ? challengeString.ascii() : "";
247
248     return signedPublicKeyAndChallengeString(keySize, challenge, keygenKeychainItemName(url.host().toString()));
249 }
250
251 }
252
253 #endif // PLATFORM(MAC)