blob: d423b10d177367ebc05bb70bcd537da5213ee9e0 [file] [log] [blame]
Yingdi Yu0b60e7a2015-07-16 21:05:11 -07001/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2/**
3 * Copyright (c) 2013-2016 Regents of the University of California.
4 *
5 * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
6 *
7 * ndn-cxx library is free software: you can redistribute it and/or modify it under the
8 * terms of the GNU Lesser General Public License as published by the Free Software
9 * Foundation, either version 3 of the License, or (at your option) any later version.
10 *
11 * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
12 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
13 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
14 *
15 * You should have received copies of the GNU General Public License and GNU Lesser
16 * General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see
17 * <http://www.gnu.org/licenses/>.
18 *
19 * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
20 */
21
22#include "back-end-osx.hpp"
23#include "key-handle-osx.hpp"
24#include "helper-osx.hpp"
25#include "../transform/private-key.hpp"
26#include "tpm.hpp"
27
28#include <CoreServices/CoreServices.h>
29#include <Security/Security.h>
30#include <Security/SecRandom.h>
31#include <Security/SecDigestTransform.h>
32
33namespace ndn {
34namespace security {
35namespace tpm {
36
37class BackEndOsx::Impl
38{
39public:
40 Impl()
41 : isTerminalMode(false)
42 {
43 }
44
45 /**
46 * @brief Get private key reference with name @p keyName.
47 *
48 * @param keyName
49 * @returns reference to the key
50 */
51 CFReleaser<SecKeychainItemRef>
52 getKey(const Name& keyName)
53 {
54 CFReleaser<CFStringRef> keyLabel = CFStringCreateWithCString(nullptr, keyName.toUri().c_str(), kCFStringEncodingUTF8);
55
56 CFReleaser<CFMutableDictionaryRef> attrDict =
57 CFDictionaryCreateMutable(nullptr, 5, &kCFTypeDictionaryKeyCallBacks, nullptr);
58
59 CFDictionaryAddValue(attrDict.get(), kSecClass, kSecClassKey);
60 CFDictionaryAddValue(attrDict.get(), kSecAttrLabel, keyLabel.get());
61 CFDictionaryAddValue(attrDict.get(), kSecAttrKeyClass, kSecAttrKeyClassPrivate);
62 CFDictionaryAddValue(attrDict.get(), kSecReturnRef, kCFBooleanTrue);
63
64 CFReleaser<SecKeychainItemRef> keyItem;
65 // C-style cast is used as per Apple convention
66 OSStatus res = SecItemCopyMatching((CFDictionaryRef)attrDict.get(), (CFTypeRef*)&keyItem.get());
67 keyItem.retain();
68
69 if (res != errSecSuccess) {
70 if (res == errSecAuthFailed) {
71 BOOST_THROW_EXCEPTION(Error("Fail to unlock the keychain"));
72 }
73 BOOST_THROW_EXCEPTION(std::domain_error("Key does not exist"));
74 }
75
76 return keyItem;
77 }
78
79public:
80 SecKeychainRef keyChainRef;
81 bool isTerminalMode;
82};
83
84
85static CFTypeRef
86getAsymKeyType(KeyType keyType)
87{
88 switch (keyType) {
89 case KeyType::RSA:
90 return kSecAttrKeyTypeRSA;
91 case KeyType::EC:
92 return kSecAttrKeyTypeECDSA;
93 default:
94 BOOST_THROW_EXCEPTION(Tpm::Error("Unsupported key type"));
95 }
96}
97
98static CFTypeRef
99getDigestAlgorithm(DigestAlgorithm digestAlgo)
100{
101 switch (digestAlgo) {
102 case DigestAlgorithm::SHA256:
103 return kSecDigestSHA2;
104 default:
105 return 0;
106 }
107}
108
109static long
110getDigestSize(DigestAlgorithm digestAlgo)
111{
112 switch (digestAlgo) {
113 case DigestAlgorithm::SHA256:
114 return 256;
115 default:
116 return -1;
117 }
118}
119
120BackEndOsx::BackEndOsx()
121 : m_impl(new Impl)
122{
123 SecKeychainSetUserInteractionAllowed(!m_impl->isTerminalMode);
124
125 OSStatus res = SecKeychainCopyDefault(&m_impl->keyChainRef);
126
127 if (res == errSecNoDefaultKeychain) { //If no default key chain, create one.
128 BOOST_THROW_EXCEPTION(Error("No default keychain, create one first"));
129 }
130}
131
132BackEndOsx::~BackEndOsx() = default;
133
134void
135BackEndOsx::setTerminalMode(bool isTerminal)
136{
137 m_impl->isTerminalMode = isTerminal;
138 SecKeychainSetUserInteractionAllowed(!isTerminal);
139}
140
141bool
142BackEndOsx::isTerminalMode() const
143{
144 return m_impl->isTerminalMode;
145}
146
147bool
148BackEndOsx::isLocked() const
149{
150 SecKeychainStatus keychainStatus;
151
152 OSStatus res = SecKeychainGetStatus(m_impl->keyChainRef, &keychainStatus);
153 if (res != errSecSuccess)
154 return true;
155 else
156 return ((kSecUnlockStateStatus & keychainStatus) == 0);
157}
158
159bool
160BackEndOsx::unlockTpm(const char* password, size_t passwordLength)
161{
162 // If the default key chain is already unlocked, return immediately.
163 if (!isLocked())
164 return true;
165
166 if (m_impl->isTerminalMode) {
167 // Use the supplied password.
168 SecKeychainUnlock(m_impl->keyChainRef, passwordLength, password, true);
169 }
170 else {
171 // If inTerminal is not set, get the password from GUI.
172 SecKeychainUnlock(m_impl->keyChainRef, 0, nullptr, false);
173 }
174
175 return !isLocked();
176}
177
178ConstBufferPtr
179BackEndOsx::sign(const KeyRefOsx& key, DigestAlgorithm digestAlgorithm,
180 const uint8_t* buf, size_t size) const
181{
182 CFReleaser<CFDataRef> dataRef = CFDataCreateWithBytesNoCopy(nullptr, buf, size, kCFAllocatorNull);
183
184 CFReleaser<CFErrorRef> error;
185 // C-style cast is used as per Apple convention
186 CFReleaser<SecTransformRef> signer = SecSignTransformCreate(key.get(), &error.get());
187 if (error != nullptr) {
188 BOOST_THROW_EXCEPTION(Error("Fail to create signer"));
189 }
190
191 // Set input
192 SecTransformSetAttribute(signer.get(), kSecTransformInputAttributeName, dataRef.get(), &error.get());
193 if (error != nullptr) {
194 BOOST_THROW_EXCEPTION(Error("Fail to configure input of signer"));
195 }
196
197 // Enable use of padding
198 SecTransformSetAttribute(signer.get(), kSecPaddingKey, kSecPaddingPKCS1Key, &error.get());
199 if (error != nullptr) {
200 BOOST_THROW_EXCEPTION(Error("Fail to configure digest algorithm of signer"));
201 }
202
203 // Set padding type
204 SecTransformSetAttribute(signer.get(), kSecDigestTypeAttribute, getDigestAlgorithm(digestAlgorithm), &error.get());
205 if (error != nullptr) {
206 BOOST_THROW_EXCEPTION(Error("Fail to configure digest algorithm of signer"));
207 }
208
209 // Set digest attribute
210 long digestSize = getDigestSize(digestAlgorithm);
211 CFReleaser<CFNumberRef> cfDigestSize = CFNumberCreate(nullptr, kCFNumberLongType, &digestSize);
212 SecTransformSetAttribute(signer.get(),
213 kSecDigestLengthAttribute,
214 cfDigestSize.get(),
215 &error.get());
216 if (error != nullptr) {
217 BOOST_THROW_EXCEPTION(Error("Fail to configure digest size of signer"));
218 }
219 // Actually sign
220 // C-style cast is used as per Apple convention
221 CFReleaser<CFDataRef> signature = (CFDataRef)SecTransformExecute(signer.get(), &error.get());
222 if (error != nullptr) {
223 CFShow(error.get());
224 BOOST_THROW_EXCEPTION(Error("Fail to sign data"));
225 }
226
227 if (signature == nullptr) {
228 BOOST_THROW_EXCEPTION(Error("Signature is NULL!\n"));
229 }
230
231 return make_shared<Buffer>(CFDataGetBytePtr(signature.get()), CFDataGetLength(signature.get()));
232}
233
234ConstBufferPtr
235BackEndOsx::decrypt(const KeyRefOsx& key, const uint8_t* cipherText, size_t cipherSize) const
236{
237 CFReleaser<CFDataRef> dataRef = CFDataCreateWithBytesNoCopy(nullptr, cipherText, cipherSize, kCFAllocatorNull);
238
239 CFReleaser<CFErrorRef> error;
240 CFReleaser<SecTransformRef> decryptor = SecDecryptTransformCreate(key.get(), &error.get());
241 if (error != nullptr) {
242 BOOST_THROW_EXCEPTION(Error("Fail to create decrypt"));
243 }
244
245 SecTransformSetAttribute(decryptor.get(), kSecTransformInputAttributeName, dataRef.get(), &error.get());
246 if (error != nullptr) {
247 BOOST_THROW_EXCEPTION(Error("Fail to configure decrypt"));
248 }
249
250 SecTransformSetAttribute(decryptor.get(), kSecPaddingKey, kSecPaddingOAEPKey, &error.get());
251 if (error != nullptr) {
252 BOOST_THROW_EXCEPTION(Error("Fail to configure decrypt #2"));
253 }
254
255 CFReleaser<CFDataRef> output = (CFDataRef)SecTransformExecute(decryptor.get(), &error.get());
256 if (error != nullptr) {
257 // CFShow(error);
258 BOOST_THROW_EXCEPTION(Error("Fail to decrypt data"));
259 }
260
261 if (output == nullptr) {
262 BOOST_THROW_EXCEPTION(Error("Output is NULL!\n"));
263 }
264 return make_shared<Buffer>(CFDataGetBytePtr(output.get()), CFDataGetLength(output.get()));
265}
266
267ConstBufferPtr
268BackEndOsx::derivePublicKey(const KeyRefOsx& key) const
269{
270 CFReleaser<CFDataRef> exportedKey;
271 OSStatus res = SecItemExport(key.get(), // secItemOrArray
272 kSecFormatOpenSSL, // outputFormat
273 0, // flags
274 nullptr, // keyParams
275 &exportedKey.get()); // exportedData
276
277 if (res != errSecSuccess) {
278 if (res == errSecAuthFailed) {
279 BOOST_THROW_EXCEPTION(Error("Fail to unlock the keychain"));
280 }
281 else {
282 BOOST_THROW_EXCEPTION(Error("Fail to export private key"));
283 }
284 }
285
286 transform::PrivateKey privateKey;
287 privateKey.loadPkcs1(CFDataGetBytePtr(exportedKey.get()), CFDataGetLength(exportedKey.get()));
288 return privateKey.derivePublicKey();
289}
290
291bool
292BackEndOsx::doHasKey(const Name& keyName) const
293{
294 CFReleaser<CFStringRef> keyLabel = CFStringCreateWithCString(nullptr, keyName.toUri().c_str(), kCFStringEncodingUTF8);
295
296 CFReleaser<CFMutableDictionaryRef> attrDict =
297 CFDictionaryCreateMutable(nullptr, 4, &kCFTypeDictionaryKeyCallBacks, nullptr);
298
299 CFDictionaryAddValue(attrDict.get(), kSecClass, kSecClassKey);
300 CFDictionaryAddValue(attrDict.get(), kSecAttrLabel, keyLabel.get());
301 CFDictionaryAddValue(attrDict.get(), kSecReturnRef, kCFBooleanTrue);
302
303 CFReleaser<SecKeychainItemRef> itemRef;
304 // C-style cast is used as per Apple convention
305 OSStatus res = SecItemCopyMatching((CFDictionaryRef)attrDict.get(), (CFTypeRef*)&itemRef.get());
306 itemRef.retain();
307
308 return (res == errSecSuccess);
309}
310
311unique_ptr<KeyHandle>
312BackEndOsx::doGetKeyHandle(const Name& keyName) const
313{
314 CFReleaser<SecKeychainItemRef> keyItem;
315 try {
316 keyItem = m_impl->getKey(keyName);
317 }
318 catch (const std::domain_error&) {
319 return nullptr;
320 }
321
322 return make_unique<KeyHandleOsx>(*this, (SecKeyRef)keyItem.get());
323}
324
325unique_ptr<KeyHandle>
326BackEndOsx::doCreateKey(const Name& identityName, const KeyParams& params)
327{
328 KeyType keyType = params.getKeyType();
329 uint32_t keySize;
330 switch (keyType) {
331 case KeyType::RSA: {
332 const RsaKeyParams& rsaParams = static_cast<const RsaKeyParams&>(params);
333 keySize = rsaParams.getKeySize();
334 break;
335 }
336 case KeyType::EC: {
337 const EcdsaKeyParams& ecdsaParams = static_cast<const EcdsaKeyParams&>(params);
338 keySize = ecdsaParams.getKeySize();
339 break;
340 }
341 default: {
342 BOOST_THROW_EXCEPTION(Tpm::Error("Fail to create a key pair: Unsupported key type"));
343 }
344 }
345 CFReleaser<CFNumberRef> cfKeySize = CFNumberCreate(nullptr, kCFNumberIntType, &keySize);
346
347 CFReleaser<CFMutableDictionaryRef> attrDict =
348 CFDictionaryCreateMutable(nullptr, 2, &kCFTypeDictionaryKeyCallBacks, nullptr);
349 CFDictionaryAddValue(attrDict.get(), kSecAttrKeyType, getAsymKeyType(keyType));
350 CFDictionaryAddValue(attrDict.get(), kSecAttrKeySizeInBits, cfKeySize.get());
351
352 KeyRefOsx publicKey, privateKey;
353 // C-style cast is used as per Apple convention
354 OSStatus res = SecKeyGeneratePair((CFDictionaryRef)attrDict.get(), &publicKey.get(), &privateKey.get());
355
356 BOOST_ASSERT(privateKey != nullptr);
357
358 publicKey.retain();
359 privateKey.retain();
360
361 BOOST_ASSERT(privateKey != nullptr);
362
363 if (res != errSecSuccess) {
364 if (res == errSecAuthFailed) {
365 BOOST_THROW_EXCEPTION(Error("Fail to unlock the keychain"));
366 }
367 else {
368 BOOST_THROW_EXCEPTION(Error("Fail to create a key pair"));
369 }
370 }
371
372 unique_ptr<KeyHandle> keyHandle = make_unique<KeyHandleOsx>(*this, privateKey.get());
373 setKeyName(*keyHandle, identityName, params);
374
375 SecKeychainAttribute attrs[1]; // maximum number of attributes
376 SecKeychainAttributeList attrList = { 0, attrs };
377 std::string keyUri = keyHandle->getKeyName().toUri();
378 {
379 attrs[attrList.count].tag = kSecKeyPrintName;
380 attrs[attrList.count].length = keyUri.size();
381 attrs[attrList.count].data = const_cast<char*>(keyUri.data());
382 attrList.count++;
383 }
384
385 SecKeychainItemModifyAttributesAndData((SecKeychainItemRef)privateKey.get(), &attrList, 0, nullptr);
386 SecKeychainItemModifyAttributesAndData((SecKeychainItemRef)publicKey.get(), &attrList, 0, nullptr);
387
388 return keyHandle;
389}
390
391void
392BackEndOsx::doDeleteKey(const Name& keyName)
393{
394 CFReleaser<CFStringRef> keyLabel = CFStringCreateWithCString(nullptr, keyName.toUri().c_str(), kCFStringEncodingUTF8);
395
396 CFReleaser<CFMutableDictionaryRef> searchDict =
397 CFDictionaryCreateMutable(nullptr, 5, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
398
399 CFDictionaryAddValue(searchDict.get(), kSecClass, kSecClassKey);
400 CFDictionaryAddValue(searchDict.get(), kSecAttrLabel, keyLabel.get());
401 CFDictionaryAddValue(searchDict.get(), kSecMatchLimit, kSecMatchLimitAll);
402 OSStatus res = SecItemDelete(searchDict.get());
403
404 if (res != errSecSuccess) {
405 if (res == errSecAuthFailed) {
406 BOOST_THROW_EXCEPTION(Error("Fail to unlock the keychain"));
407 }
408 else if (res != errSecItemNotFound) {
409 BOOST_THROW_EXCEPTION(Error("Fail to delete a key pair"));
410 }
411 }
412}
413
414ConstBufferPtr
415BackEndOsx::doExportKey(const Name& keyName, const char* pw, size_t pwLen)
416{
417 CFReleaser<SecKeychainItemRef> privateKey;
418
419 try {
420 privateKey = m_impl->getKey(keyName);
421 }
422 catch (const std::domain_error&) {
423 BOOST_THROW_EXCEPTION(Tpm::Error("Private key does not exist in OSX Keychain"));
424 }
425
426 CFReleaser<CFDataRef> exportedKey;
427 SecItemImportExportKeyParameters keyParams;
428 memset(&keyParams, 0, sizeof(keyParams));
429 CFReleaser<CFStringRef> passphrase =
430 CFStringCreateWithBytes(0, reinterpret_cast<const uint8_t*>(pw), pwLen, kCFStringEncodingUTF8, false);
431 keyParams.passphrase = passphrase.get();
432 OSStatus res = SecItemExport(privateKey.get(), // secItemOrArray
433 kSecFormatWrappedPKCS8, // outputFormat
434 0, // flags
435 &keyParams, // keyParams
436 &exportedKey.get()); // exportedData
437
438 if (res != errSecSuccess) {
439 if (res == errSecAuthFailed) {
440 BOOST_THROW_EXCEPTION(Error("Fail to unlock the keychain"));
441 }
442 else {
443 BOOST_THROW_EXCEPTION(Error("Fail to export private key"));
444 }
445 }
446
447 return make_shared<Buffer>(CFDataGetBytePtr(exportedKey.get()), CFDataGetLength(exportedKey.get()));
448}
449
450void
451BackEndOsx::doImportKey(const Name& keyName, const uint8_t* buf, size_t size,
452 const char* pw, size_t pwLen)
453{
454 CFReleaser<CFDataRef> importedKey = CFDataCreateWithBytesNoCopy(nullptr, buf, size, kCFAllocatorNull);
455
456 SecExternalFormat externalFormat = kSecFormatWrappedPKCS8;
457 SecExternalItemType externalType = kSecItemTypePrivateKey;
458
459 CFReleaser<CFStringRef> keyLabel = CFStringCreateWithCString(nullptr, keyName.toUri().c_str(), kCFStringEncodingUTF8);
460 CFReleaser<CFStringRef> passphrase =
461 CFStringCreateWithBytes(nullptr, reinterpret_cast<const uint8_t*>(pw), pwLen, kCFStringEncodingUTF8, false);
462 CFReleaser<SecAccessRef> access;
463 SecAccessCreate(keyLabel.get(), nullptr, &access.get());
464
465 CFArrayRef attributes = nullptr;
466
467 const SecItemImportExportKeyParameters keyParams{
468 SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION, // version
469 0, // flags
470 passphrase.get(), // passphrase
471 nullptr, // alert title
472 nullptr, // alert prompt
473 access.get(), // access ref
474 nullptr, // key usage
475 attributes // key attributes
476 };
477
478 CFReleaser<CFArrayRef> outItems;
479 OSStatus res = SecItemImport(importedKey.get(), // importedData
480 nullptr, // fileNameOrExtension
481 &externalFormat, // inputFormat
482 &externalType, // itemType
483 0, // flags
484 &keyParams, // keyParams
485 m_impl->keyChainRef, // importKeychain
486 &outItems.get()); // outItems
487
488 if (res != errSecSuccess) {
489 if (res == errSecAuthFailed) {
490 BOOST_THROW_EXCEPTION(Error("Fail to unlock the keychain"));
491 }
492 else {
493 BOOST_THROW_EXCEPTION(Error("Cannot import the private key"));
494 }
495 }
496
497 // C-style cast is used as per Apple convention
498 SecKeychainItemRef privateKey = (SecKeychainItemRef)CFArrayGetValueAtIndex(outItems.get(), 0);
499 SecKeychainAttribute attrs[1]; // maximum number of attributes
500 SecKeychainAttributeList attrList = { 0, attrs };
501 std::string keyUri = keyName.toUri();
502 {
503 attrs[attrList.count].tag = kSecKeyPrintName;
504 attrs[attrList.count].length = keyUri.size();
505 attrs[attrList.count].data = const_cast<char*>(keyUri.c_str());
506 attrList.count++;
507 }
508
509 res = SecKeychainItemModifyAttributesAndData(privateKey, &attrList, 0, nullptr);
510}
511
512} // namespace tpm
513} // namespace security
514} // namespace ndn