blob: 178ae55470de78b83ef960209668dc37ffcf3035 [file] [log] [blame]
Yingdi Yu0b60e7a2015-07-16 21:05:11 -07001/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2/**
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"
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
Yingdi Yufe4733a2015-10-22 14:24:12 -0700120BackEndOsx::BackEndOsx(const std::string&)
Yingdi Yu0b60e7a2015-07-16 21:05:11 -0700121 : 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
Yingdi Yufe4733a2015-10-22 14:24:12 -0700134const std::string&
135BackEndOsx::getScheme()
Yingdi Yu0b60e7a2015-07-16 21:05:11 -0700136{
Yingdi Yufe4733a2015-10-22 14:24:12 -0700137 static std::string scheme = "tpm-osxkeychain";
138 return scheme;
Yingdi Yu0b60e7a2015-07-16 21:05:11 -0700139}
140
141bool
142BackEndOsx::isTerminalMode() const
143{
144 return m_impl->isTerminalMode;
145}
146
Yingdi Yufe4733a2015-10-22 14:24:12 -0700147void
148BackEndOsx::setTerminalMode(bool isTerminal) const
149{
150 m_impl->isTerminalMode = isTerminal;
151 SecKeychainSetUserInteractionAllowed(!isTerminal);
152}
153
Yingdi Yu0b60e7a2015-07-16 21:05:11 -0700154bool
Yingdi Yufe4733a2015-10-22 14:24:12 -0700155BackEndOsx::isTpmLocked() const
Yingdi Yu0b60e7a2015-07-16 21:05:11 -0700156{
157 SecKeychainStatus keychainStatus;
158
159 OSStatus res = SecKeychainGetStatus(m_impl->keyChainRef, &keychainStatus);
160 if (res != errSecSuccess)
161 return true;
162 else
163 return ((kSecUnlockStateStatus & keychainStatus) == 0);
164}
165
166bool
Yingdi Yufe4733a2015-10-22 14:24:12 -0700167BackEndOsx::unlockTpm(const char* pw, size_t pwLen) const
Yingdi Yu0b60e7a2015-07-16 21:05:11 -0700168{
169 // If the default key chain is already unlocked, return immediately.
Yingdi Yufe4733a2015-10-22 14:24:12 -0700170 if (!isTpmLocked())
Yingdi Yu0b60e7a2015-07-16 21:05:11 -0700171 return true;
172
173 if (m_impl->isTerminalMode) {
174 // Use the supplied password.
Yingdi Yufe4733a2015-10-22 14:24:12 -0700175 SecKeychainUnlock(m_impl->keyChainRef, pwLen, pw, true);
Yingdi Yu0b60e7a2015-07-16 21:05:11 -0700176 }
177 else {
178 // If inTerminal is not set, get the password from GUI.
179 SecKeychainUnlock(m_impl->keyChainRef, 0, nullptr, false);
180 }
181
Yingdi Yufe4733a2015-10-22 14:24:12 -0700182 return !isTpmLocked();
Yingdi Yu0b60e7a2015-07-16 21:05:11 -0700183}
184
185ConstBufferPtr
186BackEndOsx::sign(const KeyRefOsx& key, DigestAlgorithm digestAlgorithm,
187 const uint8_t* buf, size_t size) const
188{
189 CFReleaser<CFDataRef> dataRef = CFDataCreateWithBytesNoCopy(nullptr, buf, size, kCFAllocatorNull);
190
191 CFReleaser<CFErrorRef> error;
192 // C-style cast is used as per Apple convention
193 CFReleaser<SecTransformRef> signer = SecSignTransformCreate(key.get(), &error.get());
194 if (error != nullptr) {
195 BOOST_THROW_EXCEPTION(Error("Fail to create signer"));
196 }
197
198 // Set input
199 SecTransformSetAttribute(signer.get(), kSecTransformInputAttributeName, dataRef.get(), &error.get());
200 if (error != nullptr) {
201 BOOST_THROW_EXCEPTION(Error("Fail to configure input of signer"));
202 }
203
204 // Enable use of padding
205 SecTransformSetAttribute(signer.get(), kSecPaddingKey, kSecPaddingPKCS1Key, &error.get());
206 if (error != nullptr) {
207 BOOST_THROW_EXCEPTION(Error("Fail to configure digest algorithm of signer"));
208 }
209
210 // Set padding type
211 SecTransformSetAttribute(signer.get(), kSecDigestTypeAttribute, getDigestAlgorithm(digestAlgorithm), &error.get());
212 if (error != nullptr) {
213 BOOST_THROW_EXCEPTION(Error("Fail to configure digest algorithm of signer"));
214 }
215
216 // Set digest attribute
217 long digestSize = getDigestSize(digestAlgorithm);
218 CFReleaser<CFNumberRef> cfDigestSize = CFNumberCreate(nullptr, kCFNumberLongType, &digestSize);
219 SecTransformSetAttribute(signer.get(),
220 kSecDigestLengthAttribute,
221 cfDigestSize.get(),
222 &error.get());
223 if (error != nullptr) {
224 BOOST_THROW_EXCEPTION(Error("Fail to configure digest size of signer"));
225 }
226 // Actually sign
227 // C-style cast is used as per Apple convention
228 CFReleaser<CFDataRef> signature = (CFDataRef)SecTransformExecute(signer.get(), &error.get());
229 if (error != nullptr) {
230 CFShow(error.get());
231 BOOST_THROW_EXCEPTION(Error("Fail to sign data"));
232 }
233
234 if (signature == nullptr) {
235 BOOST_THROW_EXCEPTION(Error("Signature is NULL!\n"));
236 }
237
238 return make_shared<Buffer>(CFDataGetBytePtr(signature.get()), CFDataGetLength(signature.get()));
239}
240
241ConstBufferPtr
242BackEndOsx::decrypt(const KeyRefOsx& key, const uint8_t* cipherText, size_t cipherSize) const
243{
244 CFReleaser<CFDataRef> dataRef = CFDataCreateWithBytesNoCopy(nullptr, cipherText, cipherSize, kCFAllocatorNull);
245
246 CFReleaser<CFErrorRef> error;
247 CFReleaser<SecTransformRef> decryptor = SecDecryptTransformCreate(key.get(), &error.get());
248 if (error != nullptr) {
249 BOOST_THROW_EXCEPTION(Error("Fail to create decrypt"));
250 }
251
252 SecTransformSetAttribute(decryptor.get(), kSecTransformInputAttributeName, dataRef.get(), &error.get());
253 if (error != nullptr) {
254 BOOST_THROW_EXCEPTION(Error("Fail to configure decrypt"));
255 }
256
257 SecTransformSetAttribute(decryptor.get(), kSecPaddingKey, kSecPaddingOAEPKey, &error.get());
258 if (error != nullptr) {
259 BOOST_THROW_EXCEPTION(Error("Fail to configure decrypt #2"));
260 }
261
262 CFReleaser<CFDataRef> output = (CFDataRef)SecTransformExecute(decryptor.get(), &error.get());
263 if (error != nullptr) {
264 // CFShow(error);
265 BOOST_THROW_EXCEPTION(Error("Fail to decrypt data"));
266 }
267
268 if (output == nullptr) {
269 BOOST_THROW_EXCEPTION(Error("Output is NULL!\n"));
270 }
271 return make_shared<Buffer>(CFDataGetBytePtr(output.get()), CFDataGetLength(output.get()));
272}
273
274ConstBufferPtr
275BackEndOsx::derivePublicKey(const KeyRefOsx& key) const
276{
277 CFReleaser<CFDataRef> exportedKey;
278 OSStatus res = SecItemExport(key.get(), // secItemOrArray
279 kSecFormatOpenSSL, // outputFormat
280 0, // flags
281 nullptr, // keyParams
282 &exportedKey.get()); // exportedData
283
284 if (res != errSecSuccess) {
285 if (res == errSecAuthFailed) {
286 BOOST_THROW_EXCEPTION(Error("Fail to unlock the keychain"));
287 }
288 else {
289 BOOST_THROW_EXCEPTION(Error("Fail to export private key"));
290 }
291 }
292
293 transform::PrivateKey privateKey;
294 privateKey.loadPkcs1(CFDataGetBytePtr(exportedKey.get()), CFDataGetLength(exportedKey.get()));
295 return privateKey.derivePublicKey();
296}
297
298bool
299BackEndOsx::doHasKey(const Name& keyName) const
300{
301 CFReleaser<CFStringRef> keyLabel = CFStringCreateWithCString(nullptr, keyName.toUri().c_str(), kCFStringEncodingUTF8);
302
303 CFReleaser<CFMutableDictionaryRef> attrDict =
304 CFDictionaryCreateMutable(nullptr, 4, &kCFTypeDictionaryKeyCallBacks, nullptr);
305
306 CFDictionaryAddValue(attrDict.get(), kSecClass, kSecClassKey);
307 CFDictionaryAddValue(attrDict.get(), kSecAttrLabel, keyLabel.get());
308 CFDictionaryAddValue(attrDict.get(), kSecReturnRef, kCFBooleanTrue);
309
310 CFReleaser<SecKeychainItemRef> itemRef;
311 // C-style cast is used as per Apple convention
312 OSStatus res = SecItemCopyMatching((CFDictionaryRef)attrDict.get(), (CFTypeRef*)&itemRef.get());
313 itemRef.retain();
314
315 return (res == errSecSuccess);
316}
317
318unique_ptr<KeyHandle>
319BackEndOsx::doGetKeyHandle(const Name& keyName) const
320{
321 CFReleaser<SecKeychainItemRef> keyItem;
322 try {
323 keyItem = m_impl->getKey(keyName);
324 }
325 catch (const std::domain_error&) {
326 return nullptr;
327 }
328
329 return make_unique<KeyHandleOsx>(*this, (SecKeyRef)keyItem.get());
330}
331
332unique_ptr<KeyHandle>
333BackEndOsx::doCreateKey(const Name& identityName, const KeyParams& params)
334{
335 KeyType keyType = params.getKeyType();
336 uint32_t keySize;
337 switch (keyType) {
338 case KeyType::RSA: {
339 const RsaKeyParams& rsaParams = static_cast<const RsaKeyParams&>(params);
340 keySize = rsaParams.getKeySize();
341 break;
342 }
343 case KeyType::EC: {
344 const EcdsaKeyParams& ecdsaParams = static_cast<const EcdsaKeyParams&>(params);
345 keySize = ecdsaParams.getKeySize();
346 break;
347 }
348 default: {
349 BOOST_THROW_EXCEPTION(Tpm::Error("Fail to create a key pair: Unsupported key type"));
350 }
351 }
352 CFReleaser<CFNumberRef> cfKeySize = CFNumberCreate(nullptr, kCFNumberIntType, &keySize);
353
354 CFReleaser<CFMutableDictionaryRef> attrDict =
355 CFDictionaryCreateMutable(nullptr, 2, &kCFTypeDictionaryKeyCallBacks, nullptr);
356 CFDictionaryAddValue(attrDict.get(), kSecAttrKeyType, getAsymKeyType(keyType));
357 CFDictionaryAddValue(attrDict.get(), kSecAttrKeySizeInBits, cfKeySize.get());
358
359 KeyRefOsx publicKey, privateKey;
360 // C-style cast is used as per Apple convention
361 OSStatus res = SecKeyGeneratePair((CFDictionaryRef)attrDict.get(), &publicKey.get(), &privateKey.get());
362
363 BOOST_ASSERT(privateKey != nullptr);
364
365 publicKey.retain();
366 privateKey.retain();
367
368 BOOST_ASSERT(privateKey != nullptr);
369
370 if (res != errSecSuccess) {
371 if (res == errSecAuthFailed) {
372 BOOST_THROW_EXCEPTION(Error("Fail to unlock the keychain"));
373 }
374 else {
375 BOOST_THROW_EXCEPTION(Error("Fail to create a key pair"));
376 }
377 }
378
379 unique_ptr<KeyHandle> keyHandle = make_unique<KeyHandleOsx>(*this, privateKey.get());
380 setKeyName(*keyHandle, identityName, params);
381
382 SecKeychainAttribute attrs[1]; // maximum number of attributes
383 SecKeychainAttributeList attrList = { 0, attrs };
384 std::string keyUri = keyHandle->getKeyName().toUri();
385 {
386 attrs[attrList.count].tag = kSecKeyPrintName;
387 attrs[attrList.count].length = keyUri.size();
388 attrs[attrList.count].data = const_cast<char*>(keyUri.data());
389 attrList.count++;
390 }
391
392 SecKeychainItemModifyAttributesAndData((SecKeychainItemRef)privateKey.get(), &attrList, 0, nullptr);
393 SecKeychainItemModifyAttributesAndData((SecKeychainItemRef)publicKey.get(), &attrList, 0, nullptr);
394
395 return keyHandle;
396}
397
398void
399BackEndOsx::doDeleteKey(const Name& keyName)
400{
401 CFReleaser<CFStringRef> keyLabel = CFStringCreateWithCString(nullptr, keyName.toUri().c_str(), kCFStringEncodingUTF8);
402
403 CFReleaser<CFMutableDictionaryRef> searchDict =
404 CFDictionaryCreateMutable(nullptr, 5, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
405
406 CFDictionaryAddValue(searchDict.get(), kSecClass, kSecClassKey);
407 CFDictionaryAddValue(searchDict.get(), kSecAttrLabel, keyLabel.get());
408 CFDictionaryAddValue(searchDict.get(), kSecMatchLimit, kSecMatchLimitAll);
409 OSStatus res = SecItemDelete(searchDict.get());
410
411 if (res != errSecSuccess) {
412 if (res == errSecAuthFailed) {
413 BOOST_THROW_EXCEPTION(Error("Fail to unlock the keychain"));
414 }
415 else if (res != errSecItemNotFound) {
416 BOOST_THROW_EXCEPTION(Error("Fail to delete a key pair"));
417 }
418 }
419}
420
421ConstBufferPtr
422BackEndOsx::doExportKey(const Name& keyName, const char* pw, size_t pwLen)
423{
424 CFReleaser<SecKeychainItemRef> privateKey;
425
426 try {
427 privateKey = m_impl->getKey(keyName);
428 }
429 catch (const std::domain_error&) {
430 BOOST_THROW_EXCEPTION(Tpm::Error("Private key does not exist in OSX Keychain"));
431 }
432
433 CFReleaser<CFDataRef> exportedKey;
434 SecItemImportExportKeyParameters keyParams;
435 memset(&keyParams, 0, sizeof(keyParams));
436 CFReleaser<CFStringRef> passphrase =
437 CFStringCreateWithBytes(0, reinterpret_cast<const uint8_t*>(pw), pwLen, kCFStringEncodingUTF8, false);
438 keyParams.passphrase = passphrase.get();
439 OSStatus res = SecItemExport(privateKey.get(), // secItemOrArray
440 kSecFormatWrappedPKCS8, // outputFormat
441 0, // flags
442 &keyParams, // keyParams
443 &exportedKey.get()); // exportedData
444
445 if (res != errSecSuccess) {
446 if (res == errSecAuthFailed) {
447 BOOST_THROW_EXCEPTION(Error("Fail to unlock the keychain"));
448 }
449 else {
450 BOOST_THROW_EXCEPTION(Error("Fail to export private key"));
451 }
452 }
453
454 return make_shared<Buffer>(CFDataGetBytePtr(exportedKey.get()), CFDataGetLength(exportedKey.get()));
455}
456
457void
458BackEndOsx::doImportKey(const Name& keyName, const uint8_t* buf, size_t size,
459 const char* pw, size_t pwLen)
460{
461 CFReleaser<CFDataRef> importedKey = CFDataCreateWithBytesNoCopy(nullptr, buf, size, kCFAllocatorNull);
462
463 SecExternalFormat externalFormat = kSecFormatWrappedPKCS8;
464 SecExternalItemType externalType = kSecItemTypePrivateKey;
465
466 CFReleaser<CFStringRef> keyLabel = CFStringCreateWithCString(nullptr, keyName.toUri().c_str(), kCFStringEncodingUTF8);
467 CFReleaser<CFStringRef> passphrase =
468 CFStringCreateWithBytes(nullptr, reinterpret_cast<const uint8_t*>(pw), pwLen, kCFStringEncodingUTF8, false);
469 CFReleaser<SecAccessRef> access;
470 SecAccessCreate(keyLabel.get(), nullptr, &access.get());
471
472 CFArrayRef attributes = nullptr;
473
474 const SecItemImportExportKeyParameters keyParams{
475 SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION, // version
476 0, // flags
477 passphrase.get(), // passphrase
478 nullptr, // alert title
479 nullptr, // alert prompt
480 access.get(), // access ref
481 nullptr, // key usage
482 attributes // key attributes
483 };
484
485 CFReleaser<CFArrayRef> outItems;
486 OSStatus res = SecItemImport(importedKey.get(), // importedData
487 nullptr, // fileNameOrExtension
488 &externalFormat, // inputFormat
489 &externalType, // itemType
490 0, // flags
491 &keyParams, // keyParams
492 m_impl->keyChainRef, // importKeychain
493 &outItems.get()); // outItems
494
495 if (res != errSecSuccess) {
496 if (res == errSecAuthFailed) {
497 BOOST_THROW_EXCEPTION(Error("Fail to unlock the keychain"));
498 }
499 else {
500 BOOST_THROW_EXCEPTION(Error("Cannot import the private key"));
501 }
502 }
503
504 // C-style cast is used as per Apple convention
505 SecKeychainItemRef privateKey = (SecKeychainItemRef)CFArrayGetValueAtIndex(outItems.get(), 0);
506 SecKeychainAttribute attrs[1]; // maximum number of attributes
507 SecKeychainAttributeList attrList = { 0, attrs };
508 std::string keyUri = keyName.toUri();
509 {
510 attrs[attrList.count].tag = kSecKeyPrintName;
511 attrs[attrList.count].length = keyUri.size();
512 attrs[attrList.count].data = const_cast<char*>(keyUri.c_str());
513 attrList.count++;
514 }
515
516 res = SecKeychainItemModifyAttributesAndData(privateKey, &attrList, 0, nullptr);
517}
518
519} // namespace tpm
520} // namespace security
521} // namespace ndn