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