• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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