• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
GenKeyAndSignChallenge()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.
CreateRSAKeyPair(int size_in_bits,SecAccessRef initial_access,SecKeyRef * out_pub_key,SecKeyRef * out_priv_key)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 
CreateSignatureContext(SecKeyRef key,CSSM_ALGORITHMS algorithm,CSSM_CC_HANDLE * out_cc_handle)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 
SignData(CSSM_DATA data,SecKeyRef private_key,CSSM_DATA * signature)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
326