OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "net/base/keygen_handler.h" | |
6 | |
7 #include <Security/SecAsn1Coder.h> | |
8 #include <Security/SecAsn1Templates.h> | |
9 #include <Security/Security.h> | |
10 | |
11 #include "base/base64.h" | |
12 #include "base/logging.h" | |
13 #include "base/mac/mac_logging.h" | |
14 #include "base/mac/scoped_cftyperef.h" | |
15 #include "base/strings/string_util.h" | |
16 #include "base/strings/sys_string_conversions.h" | |
17 #include "base/synchronization/lock.h" | |
18 #include "crypto/cssm_init.h" | |
19 #include "crypto/mac_security_services_lock.h" | |
20 | |
21 // CSSM functions are deprecated as of OSX 10.7, but have no replacement. | |
22 // https://bugs.chromium.org/p/chromium/issues/detail?id=590914#c1 | |
23 #pragma clang diagnostic push | |
24 #pragma clang diagnostic ignored "-Wdeprecated-declarations" | |
25 | |
26 // These are in Security.framework but not declared in a public header. | |
27 extern const SecAsn1Template kSecAsn1AlgorithmIDTemplate[]; | |
28 extern const SecAsn1Template kSecAsn1SubjectPublicKeyInfoTemplate[]; | |
29 | |
30 namespace net { | |
31 | |
32 // Declarations of Netscape keygen cert structures for ASN.1 encoding: | |
33 | |
34 struct PublicKeyAndChallenge { | |
35 CSSM_X509_SUBJECT_PUBLIC_KEY_INFO spki; | |
36 CSSM_DATA challenge_string; | |
37 }; | |
38 | |
39 // This is a copy of the built-in kSecAsn1IA5StringTemplate, but without the | |
40 // 'streamable' flag, which was causing bogus data to be written. | |
41 const SecAsn1Template kIA5StringTemplate[] = { | |
42 { SEC_ASN1_IA5_STRING, 0, NULL, sizeof(CSSM_DATA) } | |
43 }; | |
44 | |
45 static const SecAsn1Template kPublicKeyAndChallengeTemplate[] = { | |
46 { | |
47 SEC_ASN1_SEQUENCE, | |
48 0, | |
49 NULL, | |
50 sizeof(PublicKeyAndChallenge) | |
51 }, | |
52 { | |
53 SEC_ASN1_INLINE, | |
54 offsetof(PublicKeyAndChallenge, spki), | |
55 kSecAsn1SubjectPublicKeyInfoTemplate | |
56 }, | |
57 { | |
58 SEC_ASN1_INLINE, | |
59 offsetof(PublicKeyAndChallenge, challenge_string), | |
60 kIA5StringTemplate | |
61 }, | |
62 { | |
63 0 | |
64 } | |
65 }; | |
66 | |
67 struct SignedPublicKeyAndChallenge { | |
68 PublicKeyAndChallenge pkac; | |
69 CSSM_X509_ALGORITHM_IDENTIFIER signature_algorithm; | |
70 CSSM_DATA signature; | |
71 }; | |
72 | |
73 static const SecAsn1Template kSignedPublicKeyAndChallengeTemplate[] = { | |
74 { | |
75 SEC_ASN1_SEQUENCE, | |
76 0, | |
77 NULL, | |
78 sizeof(SignedPublicKeyAndChallenge) | |
79 }, | |
80 { | |
81 SEC_ASN1_INLINE, | |
82 offsetof(SignedPublicKeyAndChallenge, pkac), | |
83 kPublicKeyAndChallengeTemplate | |
84 }, | |
85 { | |
86 SEC_ASN1_INLINE, | |
87 offsetof(SignedPublicKeyAndChallenge, signature_algorithm), | |
88 kSecAsn1AlgorithmIDTemplate | |
89 }, | |
90 { | |
91 SEC_ASN1_BIT_STRING, | |
92 offsetof(SignedPublicKeyAndChallenge, signature) | |
93 }, | |
94 { | |
95 0 | |
96 } | |
97 }; | |
98 | |
99 | |
100 static OSStatus CreateRSAKeyPair(int size_in_bits, | |
101 SecAccessRef initial_access, | |
102 SecKeyRef* out_pub_key, | |
103 SecKeyRef* out_priv_key); | |
104 static OSStatus SignData(CSSM_DATA data, | |
105 SecKeyRef private_key, | |
106 CSSM_DATA* signature); | |
107 | |
108 std::string KeygenHandler::GenKeyAndSignChallenge() { | |
109 std::string result; | |
110 OSStatus err; | |
111 SecAccessRef initial_access = NULL; | |
112 SecKeyRef public_key = NULL; | |
113 SecKeyRef private_key = NULL; | |
114 SecAsn1CoderRef coder = NULL; | |
115 CSSM_DATA signature = {0, NULL}; | |
116 | |
117 { | |
118 if (url_.has_host()) { | |
119 // TODO(davidben): Use something like "Key generated for | |
120 // example.com", but localize it. | |
121 base::ScopedCFTypeRef<CFStringRef> label( | |
122 base::SysUTF8ToCFStringRef(url_.host())); | |
123 // Create an initial access object to set the SecAccessRef. This | |
124 // sets a label on the Keychain dialogs. Pass NULL as the second | |
125 // argument to use the default trusted list; only allow the | |
126 // current application to access without user confirmation. | |
127 err = SecAccessCreate(label, NULL, &initial_access); | |
128 // If we fail, just continue without a label. | |
129 if (err) | |
130 crypto::LogCSSMError("SecAccessCreate", err); | |
131 } | |
132 | |
133 // Create the key-pair. | |
134 err = CreateRSAKeyPair(key_size_in_bits_, initial_access, | |
135 &public_key, &private_key); | |
136 if (err) | |
137 goto failure; | |
138 | |
139 // Get the public key data (DER sequence of modulus, exponent). | |
140 CFDataRef key_data = NULL; | |
141 err = SecKeychainItemExport(public_key, kSecFormatBSAFE, 0, NULL, | |
142 &key_data); | |
143 if (err) { | |
144 crypto::LogCSSMError("SecKeychainItemExpor", err); | |
145 goto failure; | |
146 } | |
147 base::ScopedCFTypeRef<CFDataRef> scoped_key_data(key_data); | |
148 | |
149 // Create an ASN.1 encoder. | |
150 err = SecAsn1CoderCreate(&coder); | |
151 if (err) { | |
152 crypto::LogCSSMError("SecAsn1CoderCreate", err); | |
153 goto failure; | |
154 } | |
155 | |
156 // The DER encoding of a NULL. | |
157 static const uint8_t kNullDer[] = {0x05, 0x00}; | |
158 | |
159 // Fill in and DER-encode the PublicKeyAndChallenge: | |
160 SignedPublicKeyAndChallenge spkac; | |
161 memset(&spkac, 0, sizeof(spkac)); | |
162 spkac.pkac.spki.algorithm.algorithm = CSSMOID_RSA; | |
163 spkac.pkac.spki.algorithm.parameters.Data = const_cast<uint8_t*>(kNullDer); | |
164 spkac.pkac.spki.algorithm.parameters.Length = sizeof(kNullDer); | |
165 spkac.pkac.spki.subjectPublicKey.Length = | |
166 CFDataGetLength(key_data) * 8; // interpreted as a _bit_ count | |
167 spkac.pkac.spki.subjectPublicKey.Data = | |
168 const_cast<uint8_t*>(CFDataGetBytePtr(key_data)); | |
169 spkac.pkac.challenge_string.Length = challenge_.length(); | |
170 spkac.pkac.challenge_string.Data = | |
171 reinterpret_cast<uint8_t*>(const_cast<char*>(challenge_.data())); | |
172 | |
173 CSSM_DATA encoded; | |
174 err = SecAsn1EncodeItem(coder, &spkac.pkac, | |
175 kPublicKeyAndChallengeTemplate, &encoded); | |
176 if (err) { | |
177 crypto::LogCSSMError("SecAsn1EncodeItem", err); | |
178 goto failure; | |
179 } | |
180 | |
181 // Compute a signature of the result: | |
182 err = SignData(encoded, private_key, &signature); | |
183 if (err) | |
184 goto failure; | |
185 spkac.signature.Data = signature.Data; | |
186 spkac.signature.Length = signature.Length * 8; // a _bit_ count | |
187 spkac.signature_algorithm.algorithm = CSSMOID_MD5WithRSA; | |
188 spkac.signature_algorithm.parameters.Data = const_cast<uint8_t*>(kNullDer); | |
189 spkac.signature_algorithm.parameters.Length = sizeof(kNullDer); | |
190 // TODO(snej): MD5 is weak. Can we use SHA1 instead? | |
191 // See <https://bugzilla.mozilla.org/show_bug.cgi?id=549460> | |
192 | |
193 // DER-encode the entire SignedPublicKeyAndChallenge: | |
194 err = SecAsn1EncodeItem(coder, &spkac, | |
195 kSignedPublicKeyAndChallengeTemplate, &encoded); | |
196 if (err) { | |
197 crypto::LogCSSMError("SecAsn1EncodeItem", err); | |
198 goto failure; | |
199 } | |
200 | |
201 // Base64 encode the result. | |
202 std::string input(reinterpret_cast<char*>(encoded.Data), encoded.Length); | |
203 base::Base64Encode(input, &result); | |
204 } | |
205 | |
206 failure: | |
207 if (err) | |
208 OSSTATUS_LOG(ERROR, err) << "SSL Keygen failed!"; | |
209 else | |
210 VLOG(1) << "SSL Keygen succeeded! Output is: " << result; | |
211 | |
212 // Remove keys from keychain if asked to during unit testing: | |
213 if (!stores_key_) { | |
214 if (public_key) | |
215 SecKeychainItemDelete(reinterpret_cast<SecKeychainItemRef>(public_key)); | |
216 if (private_key) | |
217 SecKeychainItemDelete(reinterpret_cast<SecKeychainItemRef>(private_key)); | |
218 } | |
219 | |
220 // Clean up: | |
221 free(signature.Data); | |
222 if (coder) | |
223 SecAsn1CoderRelease(coder); | |
224 if (initial_access) | |
225 CFRelease(initial_access); | |
226 if (public_key) | |
227 CFRelease(public_key); | |
228 if (private_key) | |
229 CFRelease(private_key); | |
230 return result; | |
231 } | |
232 | |
233 | |
234 // Create an RSA key pair with size |size_in_bits|. |initial_access| | |
235 // is passed as the initial access control list in Keychain. The | |
236 // public and private keys are placed in |out_pub_key| and | |
237 // |out_priv_key|, respectively. | |
238 static OSStatus CreateRSAKeyPair(int size_in_bits, | |
239 SecAccessRef initial_access, | |
240 SecKeyRef* out_pub_key, | |
241 SecKeyRef* out_priv_key) { | |
242 OSStatus err; | |
243 SecKeychainRef keychain; | |
244 err = SecKeychainCopyDefault(&keychain); | |
245 if (err) { | |
246 crypto::LogCSSMError("SecKeychainCopyDefault", err); | |
247 return err; | |
248 } | |
249 base::ScopedCFTypeRef<SecKeychainRef> scoped_keychain(keychain); | |
250 { | |
251 base::AutoLock locked(crypto::GetMacSecurityServicesLock()); | |
252 err = SecKeyCreatePair( | |
253 keychain, | |
254 CSSM_ALGID_RSA, | |
255 size_in_bits, | |
256 0LL, | |
257 // public key usage and attributes: | |
258 CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_VERIFY | CSSM_KEYUSE_WRAP, | |
259 CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_PERMANENT, | |
260 // private key usage and attributes: | |
261 CSSM_KEYUSE_DECRYPT | CSSM_KEYUSE_SIGN | CSSM_KEYUSE_UNWRAP, | |
262 CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_PERMANENT | | |
263 CSSM_KEYATTR_SENSITIVE, | |
264 initial_access, | |
265 out_pub_key, out_priv_key); | |
266 } | |
267 if (err) | |
268 crypto::LogCSSMError("SecKeyCreatePair", err); | |
269 return err; | |
270 } | |
271 | |
272 static OSStatus CreateSignatureContext(SecKeyRef key, | |
273 CSSM_ALGORITHMS algorithm, | |
274 CSSM_CC_HANDLE* out_cc_handle) { | |
275 OSStatus err; | |
276 const CSSM_ACCESS_CREDENTIALS* credentials = NULL; | |
277 { | |
278 base::AutoLock locked(crypto::GetMacSecurityServicesLock()); | |
279 err = SecKeyGetCredentials(key, | |
280 CSSM_ACL_AUTHORIZATION_SIGN, | |
281 kSecCredentialTypeDefault, | |
282 &credentials); | |
283 } | |
284 if (err) { | |
285 crypto::LogCSSMError("SecKeyGetCredentials", err); | |
286 return err; | |
287 } | |
288 | |
289 CSSM_CSP_HANDLE csp_handle = 0; | |
290 { | |
291 base::AutoLock locked(crypto::GetMacSecurityServicesLock()); | |
292 err = SecKeyGetCSPHandle(key, &csp_handle); | |
293 } | |
294 if (err) { | |
295 crypto::LogCSSMError("SecKeyGetCSPHandle", err); | |
296 return err; | |
297 } | |
298 | |
299 const CSSM_KEY* cssm_key = NULL; | |
300 { | |
301 base::AutoLock locked(crypto::GetMacSecurityServicesLock()); | |
302 err = SecKeyGetCSSMKey(key, &cssm_key); | |
303 } | |
304 if (err) { | |
305 crypto::LogCSSMError("SecKeyGetCSSMKey", err); | |
306 return err; | |
307 } | |
308 | |
309 err = CSSM_CSP_CreateSignatureContext(csp_handle, | |
310 algorithm, | |
311 credentials, | |
312 cssm_key, | |
313 out_cc_handle); | |
314 if (err) | |
315 crypto::LogCSSMError("CSSM_CSP_CreateSignatureContext", err); | |
316 return err; | |
317 } | |
318 | |
319 static OSStatus SignData(CSSM_DATA data, | |
320 SecKeyRef private_key, | |
321 CSSM_DATA* signature) { | |
322 CSSM_CC_HANDLE cc_handle; | |
323 OSStatus err = CreateSignatureContext(private_key, | |
324 CSSM_ALGID_MD5WithRSA, | |
325 &cc_handle); | |
326 if (err) { | |
327 crypto::LogCSSMError("CreateSignatureContext", err); | |
328 return err; | |
329 } | |
330 err = CSSM_SignData(cc_handle, &data, 1, CSSM_ALGID_NONE, signature); | |
331 if (err) | |
332 crypto::LogCSSMError("CSSM_SignData", err); | |
333 CSSM_DeleteContext(cc_handle); | |
334 return err; | |
335 } | |
336 | |
337 } // namespace net | |
338 | |
339 #pragma clang diagnostic pop // "-Wdeprecated-declarations" | |
OLD | NEW |