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