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.
21extern const SecAsn1Template kSecAsn1AlgorithmIDTemplate[];
22extern const SecAsn1Template kSecAsn1SubjectPublicKeyInfoTemplate[];
23
24namespace net {
25
26// Declarations of Netscape keygen cert structures for ASN.1 encoding:
27
28struct 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.
35const SecAsn1Template kIA5StringTemplate[] = {
36    { SEC_ASN1_IA5_STRING, 0, NULL, sizeof(CSSM_DATA) }
37};
38
39static 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
61struct SignedPublicKeyAndChallenge {
62  PublicKeyAndChallenge pkac;
63  CSSM_X509_ALGORITHM_IDENTIFIER signature_algorithm;
64  CSSM_DATA signature;
65};
66
67static 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
94static OSStatus CreateRSAKeyPair(int size_in_bits,
95                                 SecAccessRef initial_access,
96                                 SecKeyRef* out_pub_key,
97                                 SecKeyRef* out_priv_key);
98static OSStatus SignData(CSSM_DATA data,
99                         SecKeyRef private_key,
100                         CSSM_DATA* signature);
101
102std::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.
225static 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
259static 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
306static 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