summaryrefslogtreecommitdiffstats
path: root/net/base/keygen_handler_mac.cc
blob: 673736334c3e03fcc2c167238f47c83f93da03e7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/base/keygen_handler.h"

#include <Security/SecAsn1Coder.h>
#include <Security/SecAsn1Templates.h>
#include <Security/Security.h>

#include "base/base64.h"
#include "base/logging.h"
#include "base/mac/mac_logging.h"
#include "base/mac/scoped_cftyperef.h"
#include "base/strings/string_util.h"
#include "base/strings/sys_string_conversions.h"
#include "base/synchronization/lock.h"
#include "crypto/cssm_init.h"
#include "crypto/mac_security_services_lock.h"

// CSSM functions are deprecated as of OSX 10.7, but have no replacement.
// https://bugs.chromium.org/p/chromium/issues/detail?id=590914#c1
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"

// These are in Security.framework but not declared in a public header.
extern const SecAsn1Template kSecAsn1AlgorithmIDTemplate[];
extern const SecAsn1Template kSecAsn1SubjectPublicKeyInfoTemplate[];

namespace net {

// Declarations of Netscape keygen cert structures for ASN.1 encoding:

struct PublicKeyAndChallenge {
  CSSM_X509_SUBJECT_PUBLIC_KEY_INFO spki;
  CSSM_DATA challenge_string;
};

// This is a copy of the built-in kSecAsn1IA5StringTemplate, but without the
// 'streamable' flag, which was causing bogus data to be written.
const SecAsn1Template kIA5StringTemplate[] = {
    { SEC_ASN1_IA5_STRING, 0, NULL, sizeof(CSSM_DATA) }
};

static const SecAsn1Template kPublicKeyAndChallengeTemplate[] = {
  {
    SEC_ASN1_SEQUENCE,
    0,
    NULL,
    sizeof(PublicKeyAndChallenge)
  },
  {
    SEC_ASN1_INLINE,
    offsetof(PublicKeyAndChallenge, spki),
    kSecAsn1SubjectPublicKeyInfoTemplate
  },
  {
    SEC_ASN1_INLINE,
    offsetof(PublicKeyAndChallenge, challenge_string),
    kIA5StringTemplate
  },
  {
    0
  }
};

struct SignedPublicKeyAndChallenge {
  PublicKeyAndChallenge pkac;
  CSSM_X509_ALGORITHM_IDENTIFIER signature_algorithm;
  CSSM_DATA signature;
};

static const SecAsn1Template kSignedPublicKeyAndChallengeTemplate[] = {
  {
    SEC_ASN1_SEQUENCE,
    0,
    NULL,
    sizeof(SignedPublicKeyAndChallenge)
  },
  {
    SEC_ASN1_INLINE,
    offsetof(SignedPublicKeyAndChallenge, pkac),
    kPublicKeyAndChallengeTemplate
  },
  {
    SEC_ASN1_INLINE,
    offsetof(SignedPublicKeyAndChallenge, signature_algorithm),
    kSecAsn1AlgorithmIDTemplate
  },
  {
    SEC_ASN1_BIT_STRING,
    offsetof(SignedPublicKeyAndChallenge, signature)
  },
  {
    0
  }
};


static OSStatus CreateRSAKeyPair(int size_in_bits,
                                 SecAccessRef initial_access,
                                 SecKeyRef* out_pub_key,
                                 SecKeyRef* out_priv_key);
static OSStatus SignData(CSSM_DATA data,
                         SecKeyRef private_key,
                         CSSM_DATA* signature);

std::string KeygenHandler::GenKeyAndSignChallenge() {
  std::string result;
  OSStatus err;
  SecAccessRef initial_access = NULL;
  SecKeyRef public_key = NULL;
  SecKeyRef private_key = NULL;
  SecAsn1CoderRef coder = NULL;
  CSSM_DATA signature = {0, NULL};

  {
    if (url_.has_host()) {
      // TODO(davidben): Use something like "Key generated for
      // example.com", but localize it.
      base::ScopedCFTypeRef<CFStringRef> label(
          base::SysUTF8ToCFStringRef(url_.host()));
      // Create an initial access object to set the SecAccessRef. This
      // sets a label on the Keychain dialogs. Pass NULL as the second
      // argument to use the default trusted list; only allow the
      // current application to access without user confirmation.
      err = SecAccessCreate(label, NULL, &initial_access);
      // If we fail, just continue without a label.
      if (err)
        crypto::LogCSSMError("SecAccessCreate", err);
    }

    // Create the key-pair.
    err = CreateRSAKeyPair(key_size_in_bits_, initial_access,
                           &public_key, &private_key);
    if (err)
      goto failure;

    // Get the public key data (DER sequence of modulus, exponent).
    CFDataRef key_data = NULL;
    err = SecKeychainItemExport(public_key, kSecFormatBSAFE, 0, NULL,
                                &key_data);
    if (err) {
      crypto::LogCSSMError("SecKeychainItemExpor", err);
      goto failure;
    }
    base::ScopedCFTypeRef<CFDataRef> scoped_key_data(key_data);

    // Create an ASN.1 encoder.
    err = SecAsn1CoderCreate(&coder);
    if (err) {
      crypto::LogCSSMError("SecAsn1CoderCreate", err);
      goto failure;
    }

    // Fill in and DER-encode the PublicKeyAndChallenge:
    SignedPublicKeyAndChallenge spkac;
    memset(&spkac, 0, sizeof(spkac));
    spkac.pkac.spki.algorithm.algorithm = CSSMOID_RSA;
    spkac.pkac.spki.subjectPublicKey.Length =
        CFDataGetLength(key_data) * 8;  // interpreted as a _bit_ count
    spkac.pkac.spki.subjectPublicKey.Data =
        const_cast<uint8_t*>(CFDataGetBytePtr(key_data));
    spkac.pkac.challenge_string.Length = challenge_.length();
    spkac.pkac.challenge_string.Data =
        reinterpret_cast<uint8_t*>(const_cast<char*>(challenge_.data()));

    CSSM_DATA encoded;
    err = SecAsn1EncodeItem(coder, &spkac.pkac,
                            kPublicKeyAndChallengeTemplate, &encoded);
    if (err) {
      crypto::LogCSSMError("SecAsn1EncodeItem", err);
      goto failure;
    }

    // Compute a signature of the result:
    err = SignData(encoded, private_key, &signature);
    if (err)
      goto failure;
    spkac.signature.Data = signature.Data;
    spkac.signature.Length = signature.Length * 8;  // a _bit_ count
    spkac.signature_algorithm.algorithm = CSSMOID_MD5WithRSA;
    // TODO(snej): MD5 is weak. Can we use SHA1 instead?
    // See <https://bugzilla.mozilla.org/show_bug.cgi?id=549460>

    // DER-encode the entire SignedPublicKeyAndChallenge:
    err = SecAsn1EncodeItem(coder, &spkac,
                            kSignedPublicKeyAndChallengeTemplate, &encoded);
    if (err) {
      crypto::LogCSSMError("SecAsn1EncodeItem", err);
      goto failure;
    }

    // Base64 encode the result.
    std::string input(reinterpret_cast<char*>(encoded.Data), encoded.Length);
    base::Base64Encode(input, &result);
  }

 failure:
  if (err)
    OSSTATUS_LOG(ERROR, err) << "SSL Keygen failed!";
  else
    VLOG(1) << "SSL Keygen succeeded! Output is: " << result;

  // Remove keys from keychain if asked to during unit testing:
  if (!stores_key_) {
    if (public_key)
      SecKeychainItemDelete(reinterpret_cast<SecKeychainItemRef>(public_key));
    if (private_key)
      SecKeychainItemDelete(reinterpret_cast<SecKeychainItemRef>(private_key));
  }

  // Clean up:
  free(signature.Data);
  if (coder)
    SecAsn1CoderRelease(coder);
  if (initial_access)
    CFRelease(initial_access);
  if (public_key)
    CFRelease(public_key);
  if (private_key)
    CFRelease(private_key);
  return result;
}


// Create an RSA key pair with size |size_in_bits|. |initial_access|
// is passed as the initial access control list in Keychain. The
// public and private keys are placed in |out_pub_key| and
// |out_priv_key|, respectively.
static OSStatus CreateRSAKeyPair(int size_in_bits,
                                 SecAccessRef initial_access,
                                 SecKeyRef* out_pub_key,
                                 SecKeyRef* out_priv_key) {
  OSStatus err;
  SecKeychainRef keychain;
  err = SecKeychainCopyDefault(&keychain);
  if (err) {
    crypto::LogCSSMError("SecKeychainCopyDefault", err);
    return err;
  }
  base::ScopedCFTypeRef<SecKeychainRef> scoped_keychain(keychain);
  {
    base::AutoLock locked(crypto::GetMacSecurityServicesLock());
    err = SecKeyCreatePair(
        keychain,
        CSSM_ALGID_RSA,
        size_in_bits,
        0LL,
        // public key usage and attributes:
        CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_VERIFY | CSSM_KEYUSE_WRAP,
        CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_PERMANENT,
        // private key usage and attributes:
        CSSM_KEYUSE_DECRYPT | CSSM_KEYUSE_SIGN | CSSM_KEYUSE_UNWRAP,
        CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_PERMANENT |
            CSSM_KEYATTR_SENSITIVE,
        initial_access,
        out_pub_key, out_priv_key);
  }
  if (err)
    crypto::LogCSSMError("SecKeyCreatePair", err);
  return err;
}

static OSStatus CreateSignatureContext(SecKeyRef key,
                                       CSSM_ALGORITHMS algorithm,
                                       CSSM_CC_HANDLE* out_cc_handle) {
  OSStatus err;
  const CSSM_ACCESS_CREDENTIALS* credentials = NULL;
  {
    base::AutoLock locked(crypto::GetMacSecurityServicesLock());
    err = SecKeyGetCredentials(key,
                               CSSM_ACL_AUTHORIZATION_SIGN,
                               kSecCredentialTypeDefault,
                               &credentials);
  }
  if (err) {
    crypto::LogCSSMError("SecKeyGetCredentials", err);
    return err;
  }

  CSSM_CSP_HANDLE csp_handle = 0;
  {
    base::AutoLock locked(crypto::GetMacSecurityServicesLock());
    err = SecKeyGetCSPHandle(key, &csp_handle);
  }
  if (err) {
    crypto::LogCSSMError("SecKeyGetCSPHandle", err);
    return err;
  }

  const CSSM_KEY* cssm_key = NULL;
  {
    base::AutoLock locked(crypto::GetMacSecurityServicesLock());
    err = SecKeyGetCSSMKey(key, &cssm_key);
  }
  if (err) {
    crypto::LogCSSMError("SecKeyGetCSSMKey", err);
    return err;
  }

  err = CSSM_CSP_CreateSignatureContext(csp_handle,
                                        algorithm,
                                        credentials,
                                        cssm_key,
                                        out_cc_handle);
  if (err)
    crypto::LogCSSMError("CSSM_CSP_CreateSignatureContext", err);
  return err;
}

static OSStatus SignData(CSSM_DATA data,
                         SecKeyRef private_key,
                         CSSM_DATA* signature) {
  CSSM_CC_HANDLE cc_handle;
  OSStatus err = CreateSignatureContext(private_key,
                                        CSSM_ALGID_MD5WithRSA,
                                        &cc_handle);
  if (err) {
    crypto::LogCSSMError("CreateSignatureContext", err);
    return err;
  }
  err = CSSM_SignData(cc_handle, &data, 1, CSSM_ALGID_NONE, signature);
  if (err)
    crypto::LogCSSMError("CSSM_SignData", err);
  CSSM_DeleteContext(cc_handle);
  return err;
}

}  // namespace net

#pragma clang diagnostic pop  // "-Wdeprecated-declarations"