blob: 145ffeb4f88fb1d08d83747b30c587e3751ac387 [file] [log] [blame]
Yingdi Yu0b60e7a2015-07-16 21:05:11 -07001/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
Davide Pesaventodef60f12017-09-17 17:26:07 -04002/*
Yingdi Yufe4733a2015-10-22 14:24:12 -07003 * Copyright (c) 2013-2017 Regents of the University of California.
Yingdi Yu0b60e7a2015-07-16 21:05:11 -07004 *
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"
Yingdi Yu0b60e7a2015-07-16 21:05:11 -070024#include "../transform/private-key.hpp"
25#include "tpm.hpp"
26
27#include <CoreServices/CoreServices.h>
28#include <Security/Security.h>
29#include <Security/SecRandom.h>
30#include <Security/SecDigestTransform.h>
31
32namespace ndn {
33namespace security {
34namespace tpm {
35
Junxiao Shi0b1b4672017-07-02 22:02:31 -070036using util::CFReleaser;
37
Yingdi Yu0b60e7a2015-07-16 21:05:11 -070038class BackEndOsx::Impl
39{
40public:
41 Impl()
42 : isTerminalMode(false)
43 {
44 }
45
46 /**
47 * @brief Get private key reference with name @p keyName.
48 *
49 * @param keyName
50 * @returns reference to the key
51 */
52 CFReleaser<SecKeychainItemRef>
53 getKey(const Name& keyName)
54 {
55 CFReleaser<CFStringRef> keyLabel = CFStringCreateWithCString(nullptr, keyName.toUri().c_str(), kCFStringEncodingUTF8);
56
57 CFReleaser<CFMutableDictionaryRef> attrDict =
58 CFDictionaryCreateMutable(nullptr, 5, &kCFTypeDictionaryKeyCallBacks, nullptr);
59
60 CFDictionaryAddValue(attrDict.get(), kSecClass, kSecClassKey);
61 CFDictionaryAddValue(attrDict.get(), kSecAttrLabel, keyLabel.get());
62 CFDictionaryAddValue(attrDict.get(), kSecAttrKeyClass, kSecAttrKeyClassPrivate);
63 CFDictionaryAddValue(attrDict.get(), kSecReturnRef, kCFBooleanTrue);
64
65 CFReleaser<SecKeychainItemRef> keyItem;
66 // C-style cast is used as per Apple convention
67 OSStatus res = SecItemCopyMatching((CFDictionaryRef)attrDict.get(), (CFTypeRef*)&keyItem.get());
68 keyItem.retain();
69
70 if (res != errSecSuccess) {
71 if (res == errSecAuthFailed) {
72 BOOST_THROW_EXCEPTION(Error("Fail to unlock the keychain"));
73 }
74 BOOST_THROW_EXCEPTION(std::domain_error("Key does not exist"));
75 }
76
77 return keyItem;
78 }
79
80public:
81 SecKeychainRef keyChainRef;
82 bool isTerminalMode;
83};
84
85
86static CFTypeRef
87getAsymKeyType(KeyType keyType)
88{
89 switch (keyType) {
90 case KeyType::RSA:
91 return kSecAttrKeyTypeRSA;
92 case KeyType::EC:
93 return kSecAttrKeyTypeECDSA;
94 default:
95 BOOST_THROW_EXCEPTION(Tpm::Error("Unsupported key type"));
96 }
97}
98
99static CFTypeRef
100getDigestAlgorithm(DigestAlgorithm digestAlgo)
101{
102 switch (digestAlgo) {
Davide Pesaventodef60f12017-09-17 17:26:07 -0400103 case DigestAlgorithm::SHA224:
Yingdi Yu0b60e7a2015-07-16 21:05:11 -0700104 case DigestAlgorithm::SHA256:
Davide Pesaventodef60f12017-09-17 17:26:07 -0400105 case DigestAlgorithm::SHA384:
106 case DigestAlgorithm::SHA512:
Yingdi Yu0b60e7a2015-07-16 21:05:11 -0700107 return kSecDigestSHA2;
108 default:
109 return 0;
110 }
111}
112
113static long
114getDigestSize(DigestAlgorithm digestAlgo)
115{
116 switch (digestAlgo) {
Davide Pesaventodef60f12017-09-17 17:26:07 -0400117 case DigestAlgorithm::SHA224:
118 return 224;
Yingdi Yu0b60e7a2015-07-16 21:05:11 -0700119 case DigestAlgorithm::SHA256:
120 return 256;
Davide Pesaventodef60f12017-09-17 17:26:07 -0400121 case DigestAlgorithm::SHA384:
122 return 384;
123 case DigestAlgorithm::SHA512:
124 return 512;
Yingdi Yu0b60e7a2015-07-16 21:05:11 -0700125 default:
126 return -1;
127 }
128}
129
Yingdi Yufe4733a2015-10-22 14:24:12 -0700130BackEndOsx::BackEndOsx(const std::string&)
Davide Pesaventodef60f12017-09-17 17:26:07 -0400131 : m_impl(make_unique<Impl>())
Yingdi Yu0b60e7a2015-07-16 21:05:11 -0700132{
133 SecKeychainSetUserInteractionAllowed(!m_impl->isTerminalMode);
134
135 OSStatus res = SecKeychainCopyDefault(&m_impl->keyChainRef);
136
137 if (res == errSecNoDefaultKeychain) { //If no default key chain, create one.
138 BOOST_THROW_EXCEPTION(Error("No default keychain, create one first"));
139 }
140}
141
142BackEndOsx::~BackEndOsx() = default;
143
Yingdi Yufe4733a2015-10-22 14:24:12 -0700144const std::string&
145BackEndOsx::getScheme()
Yingdi Yu0b60e7a2015-07-16 21:05:11 -0700146{
Yingdi Yufe4733a2015-10-22 14:24:12 -0700147 static std::string scheme = "tpm-osxkeychain";
148 return scheme;
Yingdi Yu0b60e7a2015-07-16 21:05:11 -0700149}
150
151bool
152BackEndOsx::isTerminalMode() const
153{
154 return m_impl->isTerminalMode;
155}
156
Yingdi Yufe4733a2015-10-22 14:24:12 -0700157void
158BackEndOsx::setTerminalMode(bool isTerminal) const
159{
160 m_impl->isTerminalMode = isTerminal;
161 SecKeychainSetUserInteractionAllowed(!isTerminal);
162}
163
Yingdi Yu0b60e7a2015-07-16 21:05:11 -0700164bool
Yingdi Yufe4733a2015-10-22 14:24:12 -0700165BackEndOsx::isTpmLocked() const
Yingdi Yu0b60e7a2015-07-16 21:05:11 -0700166{
167 SecKeychainStatus keychainStatus;
168
169 OSStatus res = SecKeychainGetStatus(m_impl->keyChainRef, &keychainStatus);
170 if (res != errSecSuccess)
171 return true;
172 else
173 return ((kSecUnlockStateStatus & keychainStatus) == 0);
174}
175
176bool
Yingdi Yufe4733a2015-10-22 14:24:12 -0700177BackEndOsx::unlockTpm(const char* pw, size_t pwLen) const
Yingdi Yu0b60e7a2015-07-16 21:05:11 -0700178{
179 // If the default key chain is already unlocked, return immediately.
Yingdi Yufe4733a2015-10-22 14:24:12 -0700180 if (!isTpmLocked())
Yingdi Yu0b60e7a2015-07-16 21:05:11 -0700181 return true;
182
183 if (m_impl->isTerminalMode) {
184 // Use the supplied password.
Yingdi Yufe4733a2015-10-22 14:24:12 -0700185 SecKeychainUnlock(m_impl->keyChainRef, pwLen, pw, true);
Yingdi Yu0b60e7a2015-07-16 21:05:11 -0700186 }
187 else {
188 // If inTerminal is not set, get the password from GUI.
189 SecKeychainUnlock(m_impl->keyChainRef, 0, nullptr, false);
190 }
191
Yingdi Yufe4733a2015-10-22 14:24:12 -0700192 return !isTpmLocked();
Yingdi Yu0b60e7a2015-07-16 21:05:11 -0700193}
194
195ConstBufferPtr
196BackEndOsx::sign(const KeyRefOsx& key, DigestAlgorithm digestAlgorithm,
197 const uint8_t* buf, size_t size) const
198{
199 CFReleaser<CFDataRef> dataRef = CFDataCreateWithBytesNoCopy(nullptr, buf, size, kCFAllocatorNull);
200
201 CFReleaser<CFErrorRef> error;
202 // C-style cast is used as per Apple convention
203 CFReleaser<SecTransformRef> signer = SecSignTransformCreate(key.get(), &error.get());
204 if (error != nullptr) {
205 BOOST_THROW_EXCEPTION(Error("Fail to create signer"));
206 }
207
208 // Set input
209 SecTransformSetAttribute(signer.get(), kSecTransformInputAttributeName, dataRef.get(), &error.get());
210 if (error != nullptr) {
211 BOOST_THROW_EXCEPTION(Error("Fail to configure input of signer"));
212 }
213
214 // Enable use of padding
215 SecTransformSetAttribute(signer.get(), kSecPaddingKey, kSecPaddingPKCS1Key, &error.get());
216 if (error != nullptr) {
217 BOOST_THROW_EXCEPTION(Error("Fail to configure digest algorithm of signer"));
218 }
219
220 // Set padding type
221 SecTransformSetAttribute(signer.get(), kSecDigestTypeAttribute, getDigestAlgorithm(digestAlgorithm), &error.get());
222 if (error != nullptr) {
223 BOOST_THROW_EXCEPTION(Error("Fail to configure digest algorithm of signer"));
224 }
225
226 // Set digest attribute
227 long digestSize = getDigestSize(digestAlgorithm);
228 CFReleaser<CFNumberRef> cfDigestSize = CFNumberCreate(nullptr, kCFNumberLongType, &digestSize);
229 SecTransformSetAttribute(signer.get(),
230 kSecDigestLengthAttribute,
231 cfDigestSize.get(),
232 &error.get());
233 if (error != nullptr) {
234 BOOST_THROW_EXCEPTION(Error("Fail to configure digest size of signer"));
235 }
236 // Actually sign
237 // C-style cast is used as per Apple convention
238 CFReleaser<CFDataRef> signature = (CFDataRef)SecTransformExecute(signer.get(), &error.get());
239 if (error != nullptr) {
240 CFShow(error.get());
241 BOOST_THROW_EXCEPTION(Error("Fail to sign data"));
242 }
243
244 if (signature == nullptr) {
245 BOOST_THROW_EXCEPTION(Error("Signature is NULL!\n"));
246 }
247
248 return make_shared<Buffer>(CFDataGetBytePtr(signature.get()), CFDataGetLength(signature.get()));
249}
250
251ConstBufferPtr
252BackEndOsx::decrypt(const KeyRefOsx& key, const uint8_t* cipherText, size_t cipherSize) const
253{
254 CFReleaser<CFDataRef> dataRef = CFDataCreateWithBytesNoCopy(nullptr, cipherText, cipherSize, kCFAllocatorNull);
255
256 CFReleaser<CFErrorRef> error;
257 CFReleaser<SecTransformRef> decryptor = SecDecryptTransformCreate(key.get(), &error.get());
258 if (error != nullptr) {
259 BOOST_THROW_EXCEPTION(Error("Fail to create decrypt"));
260 }
261
262 SecTransformSetAttribute(decryptor.get(), kSecTransformInputAttributeName, dataRef.get(), &error.get());
263 if (error != nullptr) {
264 BOOST_THROW_EXCEPTION(Error("Fail to configure decrypt"));
265 }
266
267 SecTransformSetAttribute(decryptor.get(), kSecPaddingKey, kSecPaddingOAEPKey, &error.get());
268 if (error != nullptr) {
269 BOOST_THROW_EXCEPTION(Error("Fail to configure decrypt #2"));
270 }
271
272 CFReleaser<CFDataRef> output = (CFDataRef)SecTransformExecute(decryptor.get(), &error.get());
273 if (error != nullptr) {
274 // CFShow(error);
275 BOOST_THROW_EXCEPTION(Error("Fail to decrypt data"));
276 }
277
278 if (output == nullptr) {
279 BOOST_THROW_EXCEPTION(Error("Output is NULL!\n"));
280 }
281 return make_shared<Buffer>(CFDataGetBytePtr(output.get()), CFDataGetLength(output.get()));
282}
283
284ConstBufferPtr
285BackEndOsx::derivePublicKey(const KeyRefOsx& key) const
286{
287 CFReleaser<CFDataRef> exportedKey;
288 OSStatus res = SecItemExport(key.get(), // secItemOrArray
289 kSecFormatOpenSSL, // outputFormat
290 0, // flags
291 nullptr, // keyParams
292 &exportedKey.get()); // exportedData
293
294 if (res != errSecSuccess) {
295 if (res == errSecAuthFailed) {
296 BOOST_THROW_EXCEPTION(Error("Fail to unlock the keychain"));
297 }
298 else {
299 BOOST_THROW_EXCEPTION(Error("Fail to export private key"));
300 }
301 }
302
303 transform::PrivateKey privateKey;
304 privateKey.loadPkcs1(CFDataGetBytePtr(exportedKey.get()), CFDataGetLength(exportedKey.get()));
305 return privateKey.derivePublicKey();
306}
307
308bool
309BackEndOsx::doHasKey(const Name& keyName) const
310{
311 CFReleaser<CFStringRef> keyLabel = CFStringCreateWithCString(nullptr, keyName.toUri().c_str(), kCFStringEncodingUTF8);
312
313 CFReleaser<CFMutableDictionaryRef> attrDict =
314 CFDictionaryCreateMutable(nullptr, 4, &kCFTypeDictionaryKeyCallBacks, nullptr);
315
316 CFDictionaryAddValue(attrDict.get(), kSecClass, kSecClassKey);
317 CFDictionaryAddValue(attrDict.get(), kSecAttrLabel, keyLabel.get());
318 CFDictionaryAddValue(attrDict.get(), kSecReturnRef, kCFBooleanTrue);
319
320 CFReleaser<SecKeychainItemRef> itemRef;
321 // C-style cast is used as per Apple convention
322 OSStatus res = SecItemCopyMatching((CFDictionaryRef)attrDict.get(), (CFTypeRef*)&itemRef.get());
323 itemRef.retain();
324
325 return (res == errSecSuccess);
326}
327
328unique_ptr<KeyHandle>
329BackEndOsx::doGetKeyHandle(const Name& keyName) const
330{
331 CFReleaser<SecKeychainItemRef> keyItem;
332 try {
333 keyItem = m_impl->getKey(keyName);
334 }
335 catch (const std::domain_error&) {
336 return nullptr;
337 }
338
339 return make_unique<KeyHandleOsx>(*this, (SecKeyRef)keyItem.get());
340}
341
342unique_ptr<KeyHandle>
343BackEndOsx::doCreateKey(const Name& identityName, const KeyParams& params)
344{
345 KeyType keyType = params.getKeyType();
346 uint32_t keySize;
347 switch (keyType) {
348 case KeyType::RSA: {
349 const RsaKeyParams& rsaParams = static_cast<const RsaKeyParams&>(params);
350 keySize = rsaParams.getKeySize();
351 break;
352 }
353 case KeyType::EC: {
Spyridon Mastorakis1ece2e32015-08-27 18:52:21 -0700354 const EcKeyParams& ecParams = static_cast<const EcKeyParams&>(params);
355 keySize = ecParams.getKeySize();
Yingdi Yu0b60e7a2015-07-16 21:05:11 -0700356 break;
357 }
358 default: {
359 BOOST_THROW_EXCEPTION(Tpm::Error("Fail to create a key pair: Unsupported key type"));
360 }
361 }
362 CFReleaser<CFNumberRef> cfKeySize = CFNumberCreate(nullptr, kCFNumberIntType, &keySize);
363
364 CFReleaser<CFMutableDictionaryRef> attrDict =
365 CFDictionaryCreateMutable(nullptr, 2, &kCFTypeDictionaryKeyCallBacks, nullptr);
366 CFDictionaryAddValue(attrDict.get(), kSecAttrKeyType, getAsymKeyType(keyType));
367 CFDictionaryAddValue(attrDict.get(), kSecAttrKeySizeInBits, cfKeySize.get());
368
369 KeyRefOsx publicKey, privateKey;
370 // C-style cast is used as per Apple convention
371 OSStatus res = SecKeyGeneratePair((CFDictionaryRef)attrDict.get(), &publicKey.get(), &privateKey.get());
372
373 BOOST_ASSERT(privateKey != nullptr);
374
375 publicKey.retain();
376 privateKey.retain();
377
378 BOOST_ASSERT(privateKey != nullptr);
379
380 if (res != errSecSuccess) {
381 if (res == errSecAuthFailed) {
382 BOOST_THROW_EXCEPTION(Error("Fail to unlock the keychain"));
383 }
384 else {
385 BOOST_THROW_EXCEPTION(Error("Fail to create a key pair"));
386 }
387 }
388
389 unique_ptr<KeyHandle> keyHandle = make_unique<KeyHandleOsx>(*this, privateKey.get());
390 setKeyName(*keyHandle, identityName, params);
391
392 SecKeychainAttribute attrs[1]; // maximum number of attributes
393 SecKeychainAttributeList attrList = { 0, attrs };
394 std::string keyUri = keyHandle->getKeyName().toUri();
395 {
396 attrs[attrList.count].tag = kSecKeyPrintName;
397 attrs[attrList.count].length = keyUri.size();
398 attrs[attrList.count].data = const_cast<char*>(keyUri.data());
399 attrList.count++;
400 }
401
402 SecKeychainItemModifyAttributesAndData((SecKeychainItemRef)privateKey.get(), &attrList, 0, nullptr);
403 SecKeychainItemModifyAttributesAndData((SecKeychainItemRef)publicKey.get(), &attrList, 0, nullptr);
404
405 return keyHandle;
406}
407
408void
409BackEndOsx::doDeleteKey(const Name& keyName)
410{
411 CFReleaser<CFStringRef> keyLabel = CFStringCreateWithCString(nullptr, keyName.toUri().c_str(), kCFStringEncodingUTF8);
412
413 CFReleaser<CFMutableDictionaryRef> searchDict =
414 CFDictionaryCreateMutable(nullptr, 5, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
415
416 CFDictionaryAddValue(searchDict.get(), kSecClass, kSecClassKey);
417 CFDictionaryAddValue(searchDict.get(), kSecAttrLabel, keyLabel.get());
418 CFDictionaryAddValue(searchDict.get(), kSecMatchLimit, kSecMatchLimitAll);
419 OSStatus res = SecItemDelete(searchDict.get());
420
421 if (res != errSecSuccess) {
422 if (res == errSecAuthFailed) {
423 BOOST_THROW_EXCEPTION(Error("Fail to unlock the keychain"));
424 }
425 else if (res != errSecItemNotFound) {
426 BOOST_THROW_EXCEPTION(Error("Fail to delete a key pair"));
427 }
428 }
429}
430
431ConstBufferPtr
432BackEndOsx::doExportKey(const Name& keyName, const char* pw, size_t pwLen)
433{
434 CFReleaser<SecKeychainItemRef> privateKey;
435
436 try {
437 privateKey = m_impl->getKey(keyName);
438 }
439 catch (const std::domain_error&) {
440 BOOST_THROW_EXCEPTION(Tpm::Error("Private key does not exist in OSX Keychain"));
441 }
442
443 CFReleaser<CFDataRef> exportedKey;
444 SecItemImportExportKeyParameters keyParams;
445 memset(&keyParams, 0, sizeof(keyParams));
446 CFReleaser<CFStringRef> passphrase =
447 CFStringCreateWithBytes(0, reinterpret_cast<const uint8_t*>(pw), pwLen, kCFStringEncodingUTF8, false);
448 keyParams.passphrase = passphrase.get();
449 OSStatus res = SecItemExport(privateKey.get(), // secItemOrArray
450 kSecFormatWrappedPKCS8, // outputFormat
451 0, // flags
452 &keyParams, // keyParams
453 &exportedKey.get()); // exportedData
454
455 if (res != errSecSuccess) {
456 if (res == errSecAuthFailed) {
457 BOOST_THROW_EXCEPTION(Error("Fail to unlock the keychain"));
458 }
459 else {
460 BOOST_THROW_EXCEPTION(Error("Fail to export private key"));
461 }
462 }
463
464 return make_shared<Buffer>(CFDataGetBytePtr(exportedKey.get()), CFDataGetLength(exportedKey.get()));
465}
466
467void
468BackEndOsx::doImportKey(const Name& keyName, const uint8_t* buf, size_t size,
469 const char* pw, size_t pwLen)
470{
471 CFReleaser<CFDataRef> importedKey = CFDataCreateWithBytesNoCopy(nullptr, buf, size, kCFAllocatorNull);
472
473 SecExternalFormat externalFormat = kSecFormatWrappedPKCS8;
474 SecExternalItemType externalType = kSecItemTypePrivateKey;
475
476 CFReleaser<CFStringRef> keyLabel = CFStringCreateWithCString(nullptr, keyName.toUri().c_str(), kCFStringEncodingUTF8);
477 CFReleaser<CFStringRef> passphrase =
478 CFStringCreateWithBytes(nullptr, reinterpret_cast<const uint8_t*>(pw), pwLen, kCFStringEncodingUTF8, false);
479 CFReleaser<SecAccessRef> access;
480 SecAccessCreate(keyLabel.get(), nullptr, &access.get());
481
482 CFArrayRef attributes = nullptr;
483
484 const SecItemImportExportKeyParameters keyParams{
485 SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION, // version
486 0, // flags
487 passphrase.get(), // passphrase
488 nullptr, // alert title
489 nullptr, // alert prompt
490 access.get(), // access ref
491 nullptr, // key usage
492 attributes // key attributes
493 };
494
495 CFReleaser<CFArrayRef> outItems;
496 OSStatus res = SecItemImport(importedKey.get(), // importedData
497 nullptr, // fileNameOrExtension
498 &externalFormat, // inputFormat
499 &externalType, // itemType
500 0, // flags
501 &keyParams, // keyParams
502 m_impl->keyChainRef, // importKeychain
503 &outItems.get()); // outItems
504
505 if (res != errSecSuccess) {
506 if (res == errSecAuthFailed) {
507 BOOST_THROW_EXCEPTION(Error("Fail to unlock the keychain"));
508 }
509 else {
510 BOOST_THROW_EXCEPTION(Error("Cannot import the private key"));
511 }
512 }
513
514 // C-style cast is used as per Apple convention
515 SecKeychainItemRef privateKey = (SecKeychainItemRef)CFArrayGetValueAtIndex(outItems.get(), 0);
516 SecKeychainAttribute attrs[1]; // maximum number of attributes
517 SecKeychainAttributeList attrList = { 0, attrs };
518 std::string keyUri = keyName.toUri();
519 {
520 attrs[attrList.count].tag = kSecKeyPrintName;
521 attrs[attrList.count].length = keyUri.size();
522 attrs[attrList.count].data = const_cast<char*>(keyUri.c_str());
523 attrList.count++;
524 }
525
526 res = SecKeychainItemModifyAttributesAndData(privateKey, &attrList, 0, nullptr);
527}
528
529} // namespace tpm
530} // namespace security
531} // namespace ndn