Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(551)

Side by Side Diff: net/ssl/openssl_platform_key_mac.cc

Issue 396803002: Implement TLS client auth in the OS X OpenSSL port. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: DISALLOW_COPY_AND_ASSIGN Created 6 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright 2014 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/ssl/openssl_platform_key.h"
6
7 #include <openssl/err.h>
8 #include <openssl/evp.h>
9 #include <openssl/rsa.h>
10
11 #include <Security/cssm.h>
12 #include <Security/SecBase.h>
13 #include <Security/SecCertificate.h>
14 #include <Security/SecIdentity.h>
15 #include <Security/SecKey.h>
16
17 #include "base/lazy_instance.h"
18 #include "base/location.h"
19 #include "base/logging.h"
20 #include "base/mac/mac_logging.h"
21 #include "base/mac/scoped_cftyperef.h"
22 #include "base/memory/scoped_policy.h"
23 #include "base/memory/scoped_ptr.h"
24 #include "base/synchronization/lock.h"
25 #include "crypto/mac_security_services_lock.h"
26 #include "net/base/net_errors.h"
27 #include "net/cert/x509_certificate.h"
28 #include "net/ssl/openssl_ssl_util.h"
29
30 namespace net {
31
32 namespace {
33
34 class ScopedCSSM_CC_HANDLE {
35 public:
36 ScopedCSSM_CC_HANDLE() : handle_(0) {
37 }
38
39 ~ScopedCSSM_CC_HANDLE() {
40 reset();
41 }
42
43 CSSM_CC_HANDLE get() const {
44 return handle_;
45 }
46
47 void reset() {
48 if (handle_)
49 CSSM_DeleteContext(handle_);
50 handle_ = 0;
51 }
52
53 CSSM_CC_HANDLE* InitializeInto() {
54 reset();
55 return &handle_;
56 }
57 private:
58 CSSM_CC_HANDLE handle_;
59
60 DISALLOW_COPY_AND_ASSIGN(ScopedCSSM_CC_HANDLE);
61 };
62
63 // Looks up the private key for |certificate| in KeyChain and returns
64 // a SecKeyRef or NULL on failure. The caller takes ownership of the
65 // result.
66 SecKeyRef FetchSecKeyRefForCertificate(const X509Certificate* certificate) {
67 OSStatus status;
68 base::ScopedCFTypeRef<SecIdentityRef> identity;
69 {
70 base::AutoLock lock(crypto::GetMacSecurityServicesLock());
71 status = SecIdentityCreateWithCertificate(
72 NULL, certificate->os_cert_handle(), identity.InitializeInto());
73 }
74 if (status != noErr) {
75 OSSTATUS_LOG(WARNING, status);
76 return NULL;
77 }
78
79 base::ScopedCFTypeRef<SecKeyRef> private_key;
80 status = SecIdentityCopyPrivateKey(identity, private_key.InitializeInto());
81 if (status != noErr) {
82 OSSTATUS_LOG(WARNING, status);
83 return NULL;
84 }
85
86 return private_key.release();
87 }
88
89 extern const RSA_METHOD mac_rsa_method;
Ryan Sleevi 2014/07/24 19:13:02 Does this really need to be extern linkage? It doe
davidben 2014/07/24 21:02:25 It's just to forward-declare it so that BoringSSLE
90 extern const ECDSA_METHOD mac_ecdsa_method;
91
92 // KeyExData contains the data that is contained in the EX_DATA of the
93 // RSA and ECDSA objects that are created to wrap Mac system keys.
94 struct KeyExData {
95 KeyExData(SecKeyRef key, const CSSM_KEY* cssm_key)
96 : key(key, base::scoped_policy::RETAIN), cssm_key(cssm_key) {}
97
98 base::ScopedCFTypeRef<SecKeyRef> key;
99 const CSSM_KEY* cssm_key;
100 };
101
102 // ExDataDup is called when one of the RSA or EC_KEY objects is
103 // duplicated. This is not supported and should never happen.
104 int ExDataDup(CRYPTO_EX_DATA* to,
105 const CRYPTO_EX_DATA* from,
106 void** from_d,
107 int idx,
108 long argl,
109 void* argp) {
110 CHECK(false);
111 return 0;
112 }
113
114 // ExDataFree is called when one of the RSA or EC_KEY objects is freed.
115 void ExDataFree(void* parent,
116 void* ptr,
117 CRYPTO_EX_DATA* ex_data,
118 int idx,
119 long argl, void* argp) {
120 KeyExData* data = reinterpret_cast<KeyExData*>(ptr);
121 delete data;
122 }
123
124 // BoringSSLEngine is a BoringSSL ENGINE that implements RSA and ECDSA
125 // by forwarding the requested operations to Apple's CSSM
126 // implementation.
127 class BoringSSLEngine {
128 public:
129 BoringSSLEngine()
130 : rsa_index_(RSA_get_ex_new_index(0 /* argl */,
131 NULL /* argp */,
132 NULL /* new_func */,
133 ExDataDup,
134 ExDataFree)),
135 ec_key_index_(EC_KEY_get_ex_new_index(0 /* argl */,
136 NULL /* argp */,
137 NULL /* new_func */,
138 ExDataDup,
139 ExDataFree)),
140 engine_(ENGINE_new()) {
141 ENGINE_set_RSA_method(
142 engine_, &mac_rsa_method, sizeof(mac_rsa_method));
143 ENGINE_set_ECDSA_method(
144 engine_, &mac_ecdsa_method, sizeof(mac_ecdsa_method));
145 }
146
147 int rsa_ex_index() const { return rsa_index_; }
148 int ec_key_ex_index() const { return ec_key_index_; }
149
150 const ENGINE* engine() const { return engine_; }
151
152 private:
153 const int rsa_index_;
154 const int ec_key_index_;
155 ENGINE* const engine_;
156 };
157
158 base::LazyInstance<BoringSSLEngine>::Leaky global_boringssl_engine =
159 LAZY_INSTANCE_INITIALIZER;
160
161 // Helper function for making a signature.
162
163 // MakeCSSMSignature uses the key information in |ex_data| to sign the
164 // |in_len| bytes pointed by |in|. It writes up to |max_out| bytes
165 // into the buffer pointed to by |out|, setting |*out_len| to the
166 // number of bytes written. It returns 1 on success and 0 on failure.
167 int MakeCSSMSignature(const KeyExData* ex_data,
168 size_t* out_len,
169 uint8_t* out,
170 size_t max_out,
171 const uint8_t* in,
172 size_t in_len) {
173 CSSM_CSP_HANDLE csp_handle;
174 OSStatus status = SecKeyGetCSPHandle(ex_data->key.get(), &csp_handle);
175 if (status != noErr) {
176 OSSTATUS_LOG(WARNING, status);
177 OPENSSL_PUT_ERROR(RSA, sign_raw, ERR_R_INTERNAL_ERROR);
178 return 0;
179 }
180
181 // TODO(davidben): (Taken from TODO(rsleevi) in sslplatf.c) Should
182 // it be kSecCredentialTypeNoUI? In Win32, at least, you can prevent
183 // the UI by setting the provider handle on the certificate to be
184 // opened with CRYPT_SILENT, but is there an equivalent?
Ryan Sleevi 2014/07/24 19:13:02 Yeah, we can nuke this TODO.
davidben 2014/07/24 21:02:25 Done.
185 const CSSM_ACCESS_CREDENTIALS* cssm_creds = NULL;
186 status = SecKeyGetCredentials(ex_data->key.get(), CSSM_ACL_AUTHORIZATION_SIGN,
187 kSecCredentialTypeDefault, &cssm_creds);
188 if (status != noErr) {
189 OSSTATUS_LOG(WARNING, status);
190 OpenSSLPutNetError(FROM_HERE, ERR_SSL_CLIENT_AUTH_SIGNATURE_FAILED);
191 return 0;
192 }
193
194 ScopedCSSM_CC_HANDLE cssm_signature;
195 if (CSSM_CSP_CreateSignatureContext(
196 csp_handle, ex_data->cssm_key->KeyHeader.AlgorithmId, cssm_creds,
197 ex_data->cssm_key, cssm_signature.InitializeInto()) != CSSM_OK) {
198 OpenSSLPutNetError(FROM_HERE, ERR_SSL_CLIENT_AUTH_SIGNATURE_FAILED);
199 return 0;
200 }
201
202 if (ex_data->cssm_key->KeyHeader.AlgorithmId == CSSM_ALGID_RSA) {
203 // Set RSA blinding.
204 CSSM_CONTEXT_ATTRIBUTE blinding_attr;
205 blinding_attr.AttributeType = CSSM_ATTRIBUTE_RSA_BLINDING;
206 blinding_attr.AttributeLength = sizeof(uint32);
207 blinding_attr.Attribute.Uint32 = 1;
208 if (CSSM_UpdateContextAttributes(
209 cssm_signature.get(), 1, &blinding_attr) != CSSM_OK) {
210 OpenSSLPutNetError(FROM_HERE, ERR_SSL_CLIENT_AUTH_SIGNATURE_FAILED);
211 return 0;
212 }
213 }
214
215 CSSM_DATA hash_data;
216 hash_data.Length = in_len;
217 hash_data.Data = const_cast<uint8*>(in);
218
219 CSSM_DATA signature_data;
220 signature_data.Length = max_out;
221 signature_data.Data = out;
222
223 if (CSSM_SignData(cssm_signature.get(), &hash_data, 1,
224 CSSM_ALGID_NONE, &signature_data) != CSSM_OK) {
Ryan Sleevi 2014/07/24 19:13:02 Can you explain more the CSSM_ALGID_NONE, and cont
davidben 2014/07/24 21:02:25 Yes, |in| at this point is a DigestInfo or SHA-1 +
225 OpenSSLPutNetError(FROM_HERE, ERR_SSL_CLIENT_AUTH_SIGNATURE_FAILED);
226 return 0;
227 }
228
229 *out_len = signature_data.Length;
230 return 1;
231 }
232
233 // Custom RSA_METHOD that uses the platform APIs for signing.
234
235 const KeyExData* RsaGetExData(const RSA* rsa) {
236 return reinterpret_cast<const KeyExData*>(
237 RSA_get_ex_data(rsa, global_boringssl_engine.Get().rsa_ex_index()));
238 }
239
240 size_t RsaMethodSize(const RSA *rsa) {
241 const KeyExData *ex_data = RsaGetExData(rsa);
242 return (ex_data->cssm_key->KeyHeader.LogicalKeySizeInBits + 7) / 8;
243 }
244
245 int RsaMethodEncrypt(RSA* rsa,
246 size_t* out_len,
247 uint8_t* out,
248 size_t max_out,
249 const uint8_t* in,
250 size_t in_len,
251 int padding) {
252 NOTIMPLEMENTED();
253 OPENSSL_PUT_ERROR(RSA, encrypt, RSA_R_UNKNOWN_ALGORITHM_TYPE);
254 return 0;
255 }
256
257 int RsaMethodSignRaw(RSA* rsa,
258 size_t* out_len,
259 uint8_t* out,
260 size_t max_out,
261 const uint8_t* in,
262 size_t in_len,
263 int padding) {
264 // Only support PKCS#1 padding.
265 DCHECK_EQ(RSA_PKCS1_PADDING, padding);
266 if (padding != RSA_PKCS1_PADDING) {
267 OPENSSL_PUT_ERROR(RSA, sign_raw, RSA_R_UNKNOWN_PADDING_TYPE);
268 return 0;
269 }
270
271 const KeyExData *ex_data = RsaGetExData(rsa);
272 if (!ex_data) {
273 OPENSSL_PUT_ERROR(RSA, sign_raw, ERR_R_INTERNAL_ERROR);
274 return 0;
275 }
276 DCHECK_EQ(CSSM_ALGID_RSA, ex_data->cssm_key->KeyHeader.AlgorithmId);
277
278 return MakeCSSMSignature(ex_data, out_len, out, max_out, in, in_len);
279 }
280
281 int RsaMethodDecrypt(RSA* rsa,
282 size_t* out_len,
283 uint8_t* out,
284 size_t max_out,
285 const uint8_t* in,
286 size_t in_len,
287 int padding) {
288 NOTIMPLEMENTED();
289 OPENSSL_PUT_ERROR(RSA, decrypt, RSA_R_UNKNOWN_ALGORITHM_TYPE);
290 return 0;
291 }
292
293 int RsaMethodVerifyRaw(RSA* rsa,
294 size_t* out_len,
295 uint8_t* out,
296 size_t max_out,
297 const uint8_t* in,
298 size_t in_len,
299 int padding) {
300 NOTIMPLEMENTED();
301 OPENSSL_PUT_ERROR(RSA, verify_raw, RSA_R_UNKNOWN_ALGORITHM_TYPE);
302 return 0;
303 }
304
305 const RSA_METHOD mac_rsa_method = {
306 {
307 0 /* references */,
308 1 /* is_static */
309 } /* common */,
310 NULL /* app_data */,
311
312 NULL /* init */,
313 NULL /* finish */,
314 RsaMethodSize,
315 NULL /* sign */,
316 NULL /* verify */,
317 RsaMethodEncrypt,
318 RsaMethodSignRaw,
319 RsaMethodDecrypt,
320 RsaMethodVerifyRaw,
321 NULL /* mod_exp */,
322 NULL /* bn_mod_exp */,
323 RSA_FLAG_OPAQUE,
324 NULL /* keygen */,
325 };
326
327 crypto::ScopedEVP_PKEY CreateRSAWrapper(SecKeyRef key,
328 const CSSM_KEY* cssm_key) {
329 crypto::ScopedRSA rsa(
330 RSA_new_method(global_boringssl_engine.Get().engine()));
331 if (!rsa)
332 return crypto::ScopedEVP_PKEY();
333
334 RSA_set_ex_data(
335 rsa.get(), global_boringssl_engine.Get().rsa_ex_index(),
336 new KeyExData(key, cssm_key));
337
338 crypto::ScopedEVP_PKEY pkey(EVP_PKEY_new());
339 if (!pkey)
340 return crypto::ScopedEVP_PKEY();
341
342 if (!EVP_PKEY_set1_RSA(pkey.get(), rsa.get()))
343 return crypto::ScopedEVP_PKEY();
344
345 return pkey.Pass();
346 }
347
348 // Custom ECDSA_METHOD that uses the platform APIs.
349 // Note that for now, only signing through ECDSA_sign() is really supported.
350 // all other method pointers are either stubs returning errors, or no-ops.
351
352 const KeyExData* EcKeyGetExData(const EC_KEY* ec_key) {
353 return reinterpret_cast<const KeyExData*>(EC_KEY_get_ex_data(
354 ec_key, global_boringssl_engine.Get().ec_key_ex_index()));
355 }
356
357 size_t EcdsaMethodGroupOrderSize(const EC_KEY* ec_key) {
358 const KeyExData* ex_data = EcKeyGetExData(ec_key);
359 // LogicalKeySizeInBits is the size of an EC public key. But an
360 // ECDSA signature length depends on the size of the base point's
361 // order. For P-256, P-384, and P-521, these two sizes are the same.
362 return (ex_data->cssm_key->KeyHeader.LogicalKeySizeInBits + 7) / 8;
363 }
364
365 int EcdsaMethodSign(const uint8_t* digest,
366 size_t digest_len,
367 uint8_t* sig,
368 unsigned int* sig_len,
369 EC_KEY* ec_key) {
370 const KeyExData *ex_data = EcKeyGetExData(ec_key);
371 if (!ex_data) {
372 OPENSSL_PUT_ERROR(RSA, sign_raw, ERR_R_INTERNAL_ERROR);
373 return 0;
374 }
375 DCHECK_EQ(CSSM_ALGID_ECDSA, ex_data->cssm_key->KeyHeader.AlgorithmId);
376
377 // TODO(davidben): Fix BoringSSL to make sig_len a size_t*.
378 size_t out_len;
379 int ret = MakeCSSMSignature(
380 ex_data, &out_len, sig, ECDSA_size(ec_key), digest, digest_len);
381 if (!ret)
382 return 0;
383 *sig_len = out_len;
384 return 1;
385 }
386
387 int EcdsaMethodVerify(const uint8_t* digest,
388 size_t digest_len,
389 const uint8_t* sig,
390 size_t sig_len,
391 EC_KEY* eckey) {
392 NOTIMPLEMENTED();
393 OPENSSL_PUT_ERROR(ECDSA, ECDSA_do_verify, ECDSA_R_NOT_IMPLEMENTED);
394 return 0;
395 }
396
397 const ECDSA_METHOD mac_ecdsa_method = {
398 {
399 0 /* references */,
400 1 /* is_static */
401 } /* common */,
402 NULL /* app_data */,
403
404 NULL /* init */,
405 NULL /* finish */,
406 EcdsaMethodGroupOrderSize,
407 EcdsaMethodSign,
408 EcdsaMethodVerify,
409 ECDSA_FLAG_OPAQUE,
410 };
411
412 crypto::ScopedEVP_PKEY CreateECDSAWrapper(SecKeyRef key,
413 const CSSM_KEY* cssm_key) {
414 crypto::ScopedEC_KEY ec_key(
415 EC_KEY_new_method(global_boringssl_engine.Get().engine()));
416 if (!ec_key)
417 return crypto::ScopedEVP_PKEY();
418
419 EC_KEY_set_ex_data(
420 ec_key.get(), global_boringssl_engine.Get().ec_key_ex_index(),
421 new KeyExData(key, cssm_key));
422
423 crypto::ScopedEVP_PKEY pkey(EVP_PKEY_new());
424 if (!pkey)
425 return crypto::ScopedEVP_PKEY();
426
427 if (!EVP_PKEY_set1_EC_KEY(pkey.get(), ec_key.get()))
428 return crypto::ScopedEVP_PKEY();
429
430 return pkey.Pass();
431 }
432
433 crypto::ScopedEVP_PKEY CreatePkeyWrapper(SecKeyRef key) {
434 const CSSM_KEY* cssm_key;
435 OSStatus status = SecKeyGetCSSMKey(key, &cssm_key);
436 if (status != noErr)
437 return crypto::ScopedEVP_PKEY();
438
439 switch (cssm_key->KeyHeader.AlgorithmId) {
440 case CSSM_ALGID_RSA:
441 return CreateRSAWrapper(key, cssm_key);
442 case CSSM_ALGID_ECDSA:
443 return CreateECDSAWrapper(key, cssm_key);
444 default:
445 // TODO(davidben): Filter out anything other than ECDSA and RSA
446 // elsewhere. We don't support other key types.
447 NOTREACHED();
448 LOG(ERROR) << "Unknown key type";
449 return crypto::ScopedEVP_PKEY();
450 }
451 }
452
453 } // namespace
454
455 crypto::ScopedEVP_PKEY FetchClientCertPrivateKey(
456 const X509Certificate* certificate) {
457 // Look up the private key.
458 base::ScopedCFTypeRef<SecKeyRef> private_key(
459 FetchSecKeyRefForCertificate(certificate));
460 if (!private_key)
461 return crypto::ScopedEVP_PKEY();
462
463 // Create an EVP_PKEY wrapper.
464 return CreatePkeyWrapper(private_key.get());
465 }
466
467 } // namespace net
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698