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