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