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.
5 #include "net/base/keygen_handler.h"
7 #include <Security/SecAsn1Coder.h>
8 #include <Security/SecAsn1Templates.h>
9 #include <Security/Security.h>
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"
21 // These are in Security.framework but not declared in a public header.
22 extern const SecAsn1Template kSecAsn1AlgorithmIDTemplate
[];
23 extern const SecAsn1Template kSecAsn1SubjectPublicKeyInfoTemplate
[];
27 // Declarations of Netscape keygen cert structures for ASN.1 encoding:
29 struct PublicKeyAndChallenge
{
30 CSSM_X509_SUBJECT_PUBLIC_KEY_INFO spki
;
31 CSSM_DATA challenge_string
;
34 // This is a copy of the built-in kSecAsn1IA5StringTemplate, but without the
35 // 'streamable' flag, which was causing bogus data to be written.
36 const SecAsn1Template kIA5StringTemplate
[] = {
37 { SEC_ASN1_IA5_STRING
, 0, NULL
, sizeof(CSSM_DATA
) }
40 static const SecAsn1Template kPublicKeyAndChallengeTemplate
[] = {
45 sizeof(PublicKeyAndChallenge
)
49 offsetof(PublicKeyAndChallenge
, spki
),
50 kSecAsn1SubjectPublicKeyInfoTemplate
54 offsetof(PublicKeyAndChallenge
, challenge_string
),
62 struct SignedPublicKeyAndChallenge
{
63 PublicKeyAndChallenge pkac
;
64 CSSM_X509_ALGORITHM_IDENTIFIER signature_algorithm
;
68 static const SecAsn1Template kSignedPublicKeyAndChallengeTemplate
[] = {
73 sizeof(SignedPublicKeyAndChallenge
)
77 offsetof(SignedPublicKeyAndChallenge
, pkac
),
78 kPublicKeyAndChallengeTemplate
82 offsetof(SignedPublicKeyAndChallenge
, signature_algorithm
),
83 kSecAsn1AlgorithmIDTemplate
87 offsetof(SignedPublicKeyAndChallenge
, signature
)
95 static OSStatus
CreateRSAKeyPair(int size_in_bits
,
96 SecAccessRef initial_access
,
97 SecKeyRef
* out_pub_key
,
98 SecKeyRef
* out_priv_key
);
99 static OSStatus
SignData(CSSM_DATA data
,
100 SecKeyRef private_key
,
101 CSSM_DATA
* signature
);
103 std::string
KeygenHandler::GenKeyAndSignChallenge() {
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
};
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.
125 crypto::LogCSSMError("SecAccessCreate", err
);
128 // Create the key-pair.
129 err
= CreateRSAKeyPair(key_size_in_bits_
, initial_access
,
130 &public_key
, &private_key
);
134 // Get the public key data (DER sequence of modulus, exponent).
135 CFDataRef key_data
= NULL
;
136 err
= SecKeychainItemExport(public_key
, kSecFormatBSAFE
, 0, NULL
,
139 crypto::LogCSSMError("SecKeychainItemExpor", err
);
142 base::ScopedCFTypeRef
<CFDataRef
> scoped_key_data(key_data
);
144 // Create an ASN.1 encoder.
145 err
= SecAsn1CoderCreate(&coder
);
147 crypto::LogCSSMError("SecAsn1CoderCreate", err
);
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()));
164 err
= SecAsn1EncodeItem(coder
, &spkac
.pkac
,
165 kPublicKeyAndChallengeTemplate
, &encoded
);
167 crypto::LogCSSMError("SecAsn1EncodeItem", err
);
171 // Compute a signature of the result:
172 err
= SignData(encoded
, private_key
, &signature
);
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>
181 // DER-encode the entire SignedPublicKeyAndChallenge:
182 err
= SecAsn1EncodeItem(coder
, &spkac
,
183 kSignedPublicKeyAndChallengeTemplate
, &encoded
);
185 crypto::LogCSSMError("SecAsn1EncodeItem", err
);
189 // Base64 encode the result.
190 std::string
input(reinterpret_cast<char*>(encoded
.Data
), encoded
.Length
);
191 base::Base64Encode(input
, &result
);
196 OSSTATUS_LOG(ERROR
, err
) << "SSL Keygen failed!";
198 VLOG(1) << "SSL Keygen succeeded! Output is: " << result
;
200 // Remove keys from keychain if asked to during unit testing:
203 SecKeychainItemDelete(reinterpret_cast<SecKeychainItemRef
>(public_key
));
205 SecKeychainItemDelete(reinterpret_cast<SecKeychainItemRef
>(private_key
));
209 free(signature
.Data
);
211 SecAsn1CoderRelease(coder
);
213 CFRelease(initial_access
);
215 CFRelease(public_key
);
217 CFRelease(private_key
);
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.
226 static OSStatus
CreateRSAKeyPair(int size_in_bits
,
227 SecAccessRef initial_access
,
228 SecKeyRef
* out_pub_key
,
229 SecKeyRef
* out_priv_key
) {
231 SecKeychainRef keychain
;
232 err
= SecKeychainCopyDefault(&keychain
);
234 crypto::LogCSSMError("SecKeychainCopyDefault", err
);
237 base::ScopedCFTypeRef
<SecKeychainRef
> scoped_keychain(keychain
);
239 base::AutoLock
locked(crypto::GetMacSecurityServicesLock());
240 err
= SecKeyCreatePair(
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
,
253 out_pub_key
, out_priv_key
);
256 crypto::LogCSSMError("SecKeyCreatePair", err
);
260 static OSStatus
CreateSignatureContext(SecKeyRef key
,
261 CSSM_ALGORITHMS algorithm
,
262 CSSM_CC_HANDLE
* out_cc_handle
) {
264 const CSSM_ACCESS_CREDENTIALS
* credentials
= NULL
;
266 base::AutoLock
locked(crypto::GetMacSecurityServicesLock());
267 err
= SecKeyGetCredentials(key
,
268 CSSM_ACL_AUTHORIZATION_SIGN
,
269 kSecCredentialTypeDefault
,
273 crypto::LogCSSMError("SecKeyGetCredentials", err
);
277 CSSM_CSP_HANDLE csp_handle
= 0;
279 base::AutoLock
locked(crypto::GetMacSecurityServicesLock());
280 err
= SecKeyGetCSPHandle(key
, &csp_handle
);
283 crypto::LogCSSMError("SecKeyGetCSPHandle", err
);
287 const CSSM_KEY
* cssm_key
= NULL
;
289 base::AutoLock
locked(crypto::GetMacSecurityServicesLock());
290 err
= SecKeyGetCSSMKey(key
, &cssm_key
);
293 crypto::LogCSSMError("SecKeyGetCSSMKey", err
);
297 err
= CSSM_CSP_CreateSignatureContext(csp_handle
,
303 crypto::LogCSSMError("CSSM_CSP_CreateSignatureContext", err
);
307 static 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
,
315 crypto::LogCSSMError("CreateSignatureContext", err
);
318 err
= CSSM_SignData(cc_handle
, &data
, 1, CSSM_ALGID_NONE
, signature
);
320 crypto::LogCSSMError("CSSM_SignData", err
);
321 CSSM_DeleteContext(cc_handle
);