NS-3 based Named Data Networking (NDN) simulator
ndnSIM 2.5: NDN, CCN, CCNx, content centric networks
API Documentation
back-end-osx.cpp
Go to the documentation of this file.
1 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2 /*
3  * Copyright (c) 2013-2017 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 "tpm.hpp"
25 #include "../transform/private-key.hpp"
26 
27 #include <CoreServices/CoreServices.h>
28 #include <Security/SecDigestTransform.h>
29 #include <Security/SecRandom.h>
30 #include <Security/Security.h>
31 
32 namespace ndn {
33 namespace security {
34 namespace tpm {
35 
36 using util::CFReleaser;
37 
39 {
40 public:
41  Impl()
42  : isTerminalMode(false)
43  {
44  }
45 
53  getKey(const Name& keyName)
54  {
55  CFReleaser<CFStringRef> keyLabel = CFStringCreateWithCString(nullptr, keyName.toUri().c_str(),
56  kCFStringEncodingUTF8);
57 
59  CFDictionaryCreateMutable(nullptr, 5, &kCFTypeDictionaryKeyCallBacks, nullptr);
60 
61  CFDictionaryAddValue(attrDict.get(), kSecClass, kSecClassKey);
62  CFDictionaryAddValue(attrDict.get(), kSecAttrLabel, keyLabel.get());
63  CFDictionaryAddValue(attrDict.get(), kSecAttrKeyClass, kSecAttrKeyClassPrivate);
64  CFDictionaryAddValue(attrDict.get(), kSecReturnRef, kCFBooleanTrue);
65 
67  // C-style cast is used as per Apple convention
68  OSStatus res = SecItemCopyMatching((CFDictionaryRef)attrDict.get(), (CFTypeRef*)&keyItem.get());
69  keyItem.retain();
70 
71  if (res != errSecSuccess) {
72  if (res == errSecAuthFailed) {
73  BOOST_THROW_EXCEPTION(Error("Fail to unlock the keychain"));
74  }
75  BOOST_THROW_EXCEPTION(std::domain_error("Key does not exist"));
76  }
77 
78  return keyItem;
79  }
80 
81 public:
82  SecKeychainRef keyChainRef;
84 };
85 
86 
87 static CFTypeRef
89 {
90  switch (keyType) {
91  case KeyType::RSA:
92  return kSecAttrKeyTypeRSA;
93  case KeyType::EC:
94  return kSecAttrKeyTypeECDSA;
95  default:
96  BOOST_THROW_EXCEPTION(Tpm::Error("Unsupported key type"));
97  }
98 }
99 
100 static CFTypeRef
102 {
103  switch (digestAlgo) {
108  return kSecDigestSHA2;
109  default:
110  return 0;
111  }
112 }
113 
114 static long
116 {
117  switch (digestAlgo) {
119  return 224;
121  return 256;
123  return 384;
125  return 512;
126  default:
127  return -1;
128  }
129 }
130 
131 BackEndOsx::BackEndOsx(const std::string&)
132  : m_impl(make_unique<Impl>())
133 {
134  SecKeychainSetUserInteractionAllowed(!m_impl->isTerminalMode);
135 
136  OSStatus res = SecKeychainCopyDefault(&m_impl->keyChainRef);
137 
138  if (res == errSecNoDefaultKeychain) { //If no default key chain, create one.
139  BOOST_THROW_EXCEPTION(Error("No default keychain, create one first"));
140  }
141 }
142 
143 BackEndOsx::~BackEndOsx() = default;
144 
145 const std::string&
147 {
148  static std::string scheme = "tpm-osxkeychain";
149  return scheme;
150 }
151 
152 bool
154 {
155  return m_impl->isTerminalMode;
156 }
157 
158 void
159 BackEndOsx::setTerminalMode(bool isTerminal) const
160 {
161  m_impl->isTerminalMode = isTerminal;
162  SecKeychainSetUserInteractionAllowed(!isTerminal);
163 }
164 
165 bool
167 {
168  SecKeychainStatus keychainStatus;
169 
170  OSStatus res = SecKeychainGetStatus(m_impl->keyChainRef, &keychainStatus);
171  if (res != errSecSuccess)
172  return true;
173  else
174  return ((kSecUnlockStateStatus & keychainStatus) == 0);
175 }
176 
177 bool
178 BackEndOsx::unlockTpm(const char* pw, size_t pwLen) const
179 {
180  // If the default key chain is already unlocked, return immediately.
181  if (!isTpmLocked())
182  return true;
183 
184  if (m_impl->isTerminalMode) {
185  // Use the supplied password.
186  SecKeychainUnlock(m_impl->keyChainRef, pwLen, pw, true);
187  }
188  else {
189  // If inTerminal is not set, get the password from GUI.
190  SecKeychainUnlock(m_impl->keyChainRef, 0, nullptr, false);
191  }
192 
193  return !isTpmLocked();
194 }
195 
197 BackEndOsx::sign(const KeyRefOsx& key, DigestAlgorithm digestAlgo, const uint8_t* buf, size_t size)
198 {
200  CFReleaser<SecTransformRef> signer = SecSignTransformCreate(key.get(), &error.get());
201  if (error != nullptr) {
202  BOOST_THROW_EXCEPTION(Error("Fail to create signer"));
203  }
204 
205  // Set input
206  CFReleaser<CFDataRef> dataRef = CFDataCreateWithBytesNoCopy(nullptr, buf, size, kCFAllocatorNull);
207  SecTransformSetAttribute(signer.get(), kSecTransformInputAttributeName, dataRef.get(), &error.get());
208  if (error != nullptr) {
209  BOOST_THROW_EXCEPTION(Error("Fail to configure input of signer"));
210  }
211 
212  // Enable use of padding
213  SecTransformSetAttribute(signer.get(), kSecPaddingKey, kSecPaddingPKCS1Key, &error.get());
214  if (error != nullptr) {
215  BOOST_THROW_EXCEPTION(Error("Fail to configure padding of signer"));
216  }
217 
218  // Set digest type
219  SecTransformSetAttribute(signer.get(), kSecDigestTypeAttribute, getDigestAlgorithm(digestAlgo), &error.get());
220  if (error != nullptr) {
221  BOOST_THROW_EXCEPTION(Error("Fail to configure digest type of signer"));
222  }
223 
224  // Set digest length
225  long digestSize = getDigestSize(digestAlgo);
226  CFReleaser<CFNumberRef> cfDigestSize = CFNumberCreate(nullptr, kCFNumberLongType, &digestSize);
227  SecTransformSetAttribute(signer.get(), kSecDigestLengthAttribute, cfDigestSize.get(), &error.get());
228  if (error != nullptr) {
229  BOOST_THROW_EXCEPTION(Error("Fail to configure digest length of signer"));
230  }
231 
232  // Actually sign
233  // C-style cast is used as per Apple convention
234  CFReleaser<CFDataRef> signature = (CFDataRef)SecTransformExecute(signer.get(), &error.get());
235  if (error != nullptr) {
236  CFShow(error.get());
237  BOOST_THROW_EXCEPTION(Error("Fail to sign data"));
238  }
239 
240  if (signature == nullptr) {
241  BOOST_THROW_EXCEPTION(Error("Signature is null"));
242  }
243 
244  return make_shared<Buffer>(CFDataGetBytePtr(signature.get()), CFDataGetLength(signature.get()));
245 }
246 
248 BackEndOsx::decrypt(const KeyRefOsx& key, const uint8_t* cipherText, size_t cipherSize)
249 {
251  CFReleaser<SecTransformRef> decryptor = SecDecryptTransformCreate(key.get(), &error.get());
252  if (error != nullptr) {
253  BOOST_THROW_EXCEPTION(Error("Fail to create decryptor"));
254  }
255 
256  CFReleaser<CFDataRef> dataRef = CFDataCreateWithBytesNoCopy(nullptr, cipherText, cipherSize, kCFAllocatorNull);
257  SecTransformSetAttribute(decryptor.get(), kSecTransformInputAttributeName, dataRef.get(), &error.get());
258  if (error != nullptr) {
259  BOOST_THROW_EXCEPTION(Error("Fail to configure decryptor input"));
260  }
261 
262  SecTransformSetAttribute(decryptor.get(), kSecPaddingKey, kSecPaddingOAEPKey, &error.get());
263  if (error != nullptr) {
264  BOOST_THROW_EXCEPTION(Error("Fail to configure decryptor padding"));
265  }
266 
267  CFReleaser<CFDataRef> output = (CFDataRef)SecTransformExecute(decryptor.get(), &error.get());
268  if (error != nullptr) {
269  // CFShow(error);
270  BOOST_THROW_EXCEPTION(Error("Fail to decrypt data"));
271  }
272 
273  if (output == nullptr) {
274  BOOST_THROW_EXCEPTION(Error("Output is null"));
275  }
276 
277  return make_shared<Buffer>(CFDataGetBytePtr(output.get()), CFDataGetLength(output.get()));
278 }
279 
282 {
283  CFReleaser<CFDataRef> exportedKey;
284  OSStatus res = SecItemExport(key.get(), // secItemOrArray
285  kSecFormatOpenSSL, // outputFormat
286  0, // flags
287  nullptr, // keyParams
288  &exportedKey.get()); // exportedData
289 
290  if (res != errSecSuccess) {
291  if (res == errSecAuthFailed) {
292  BOOST_THROW_EXCEPTION(Error("Fail to unlock the keychain"));
293  }
294  else {
295  BOOST_THROW_EXCEPTION(Error("Fail to export private key"));
296  }
297  }
298 
299  transform::PrivateKey privateKey;
300  privateKey.loadPkcs1(CFDataGetBytePtr(exportedKey.get()), CFDataGetLength(exportedKey.get()));
301  return privateKey.derivePublicKey();
302 }
303 
304 bool
305 BackEndOsx::doHasKey(const Name& keyName) const
306 {
307  CFReleaser<CFStringRef> keyLabel = CFStringCreateWithCString(nullptr, keyName.toUri().c_str(),
308  kCFStringEncodingUTF8);
309 
311  CFDictionaryCreateMutable(nullptr, 4, &kCFTypeDictionaryKeyCallBacks, nullptr);
312 
313  CFDictionaryAddValue(attrDict.get(), kSecClass, kSecClassKey);
314  CFDictionaryAddValue(attrDict.get(), kSecAttrLabel, keyLabel.get());
315  CFDictionaryAddValue(attrDict.get(), kSecReturnRef, kCFBooleanTrue);
316 
318  // C-style cast is used as per Apple convention
319  OSStatus res = SecItemCopyMatching((CFDictionaryRef)attrDict.get(), (CFTypeRef*)&itemRef.get());
320  itemRef.retain();
321 
322  return res == errSecSuccess;
323 }
324 
325 unique_ptr<KeyHandle>
326 BackEndOsx::doGetKeyHandle(const Name& keyName) const
327 {
328  CFReleaser<SecKeychainItemRef> keyItem;
329  try {
330  keyItem = m_impl->getKey(keyName);
331  }
332  catch (const std::domain_error&) {
333  return nullptr;
334  }
335 
336  return make_unique<KeyHandleOsx>((SecKeyRef)keyItem.get());
337 }
338 
339 unique_ptr<KeyHandle>
340 BackEndOsx::doCreateKey(const Name& identityName, const KeyParams& params)
341 {
342  KeyType keyType = params.getKeyType();
343  uint32_t keySize;
344  switch (keyType) {
345  case KeyType::RSA: {
346  const RsaKeyParams& rsaParams = static_cast<const RsaKeyParams&>(params);
347  keySize = rsaParams.getKeySize();
348  break;
349  }
350  case KeyType::EC: {
351  const EcKeyParams& ecParams = static_cast<const EcKeyParams&>(params);
352  keySize = ecParams.getKeySize();
353  break;
354  }
355  default: {
356  BOOST_THROW_EXCEPTION(Tpm::Error("Fail to create a key pair: Unsupported key type"));
357  }
358  }
359  CFReleaser<CFNumberRef> cfKeySize = CFNumberCreate(nullptr, kCFNumberIntType, &keySize);
360 
361  CFReleaser<CFMutableDictionaryRef> attrDict =
362  CFDictionaryCreateMutable(nullptr, 2, &kCFTypeDictionaryKeyCallBacks, nullptr);
363  CFDictionaryAddValue(attrDict.get(), kSecAttrKeyType, getAsymKeyType(keyType));
364  CFDictionaryAddValue(attrDict.get(), kSecAttrKeySizeInBits, cfKeySize.get());
365 
366  KeyRefOsx publicKey, privateKey;
367  // C-style cast is used as per Apple convention
368  OSStatus res = SecKeyGeneratePair((CFDictionaryRef)attrDict.get(), &publicKey.get(), &privateKey.get());
369 
370  BOOST_ASSERT(privateKey != nullptr);
371 
372  publicKey.retain();
373  privateKey.retain();
374 
375  BOOST_ASSERT(privateKey != nullptr);
376 
377  if (res != errSecSuccess) {
378  if (res == errSecAuthFailed) {
379  BOOST_THROW_EXCEPTION(Error("Fail to unlock the keychain"));
380  }
381  else {
382  BOOST_THROW_EXCEPTION(Error("Fail to create a key pair"));
383  }
384  }
385 
386  unique_ptr<KeyHandle> keyHandle = make_unique<KeyHandleOsx>(privateKey.get());
387  setKeyName(*keyHandle, identityName, params);
388 
389  SecKeychainAttribute attrs[1]; // maximum number of attributes
390  SecKeychainAttributeList attrList = { 0, attrs };
391  std::string keyUri = keyHandle->getKeyName().toUri();
392  {
393  attrs[attrList.count].tag = kSecKeyPrintName;
394  attrs[attrList.count].length = keyUri.size();
395  attrs[attrList.count].data = const_cast<char*>(keyUri.data());
396  attrList.count++;
397  }
398 
399  SecKeychainItemModifyAttributesAndData((SecKeychainItemRef)privateKey.get(), &attrList, 0, nullptr);
400  SecKeychainItemModifyAttributesAndData((SecKeychainItemRef)publicKey.get(), &attrList, 0, nullptr);
401 
402  return keyHandle;
403 }
404 
405 void
406 BackEndOsx::doDeleteKey(const Name& keyName)
407 {
408  CFReleaser<CFStringRef> keyLabel = CFStringCreateWithCString(nullptr, keyName.toUri().c_str(),
409  kCFStringEncodingUTF8);
410 
411  CFReleaser<CFMutableDictionaryRef> searchDict =
412  CFDictionaryCreateMutable(nullptr, 5, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
413 
414  CFDictionaryAddValue(searchDict.get(), kSecClass, kSecClassKey);
415  CFDictionaryAddValue(searchDict.get(), kSecAttrLabel, keyLabel.get());
416  CFDictionaryAddValue(searchDict.get(), kSecMatchLimit, kSecMatchLimitAll);
417  OSStatus res = SecItemDelete(searchDict.get());
418 
419  if (res != errSecSuccess) {
420  if (res == errSecAuthFailed) {
421  BOOST_THROW_EXCEPTION(Error("Fail to unlock the keychain"));
422  }
423  else if (res != errSecItemNotFound) {
424  BOOST_THROW_EXCEPTION(Error("Fail to delete a key pair"));
425  }
426  }
427 }
428 
430 BackEndOsx::doExportKey(const Name& keyName, const char* pw, size_t pwLen)
431 {
432  CFReleaser<SecKeychainItemRef> privateKey;
433 
434  try {
435  privateKey = m_impl->getKey(keyName);
436  }
437  catch (const std::domain_error&) {
438  BOOST_THROW_EXCEPTION(Tpm::Error("Private key does not exist in OSX Keychain"));
439  }
440 
441  CFReleaser<CFDataRef> exportedKey;
442  SecItemImportExportKeyParameters keyParams;
443  memset(&keyParams, 0, sizeof(keyParams));
444  CFReleaser<CFStringRef> passphrase =
445  CFStringCreateWithBytes(0, reinterpret_cast<const uint8_t*>(pw), pwLen, kCFStringEncodingUTF8, false);
446  keyParams.passphrase = passphrase.get();
447  OSStatus res = SecItemExport(privateKey.get(), // secItemOrArray
448  kSecFormatWrappedPKCS8, // outputFormat
449  0, // flags
450  &keyParams, // keyParams
451  &exportedKey.get()); // exportedData
452 
453  if (res != errSecSuccess) {
454  if (res == errSecAuthFailed) {
455  BOOST_THROW_EXCEPTION(Error("Fail to unlock the keychain"));
456  }
457  else {
458  BOOST_THROW_EXCEPTION(Error("Fail to export private key"));
459  }
460  }
461 
462  return make_shared<Buffer>(CFDataGetBytePtr(exportedKey.get()), CFDataGetLength(exportedKey.get()));
463 }
464 
465 void
466 BackEndOsx::doImportKey(const Name& keyName, const uint8_t* buf, size_t size,
467  const char* pw, size_t pwLen)
468 {
469  CFReleaser<CFDataRef> importedKey = CFDataCreateWithBytesNoCopy(nullptr, buf, size, kCFAllocatorNull);
470 
471  SecExternalFormat externalFormat = kSecFormatWrappedPKCS8;
472  SecExternalItemType externalType = kSecItemTypePrivateKey;
473 
474  CFReleaser<CFStringRef> keyLabel = CFStringCreateWithCString(nullptr, keyName.toUri().c_str(),
475  kCFStringEncodingUTF8);
476  CFReleaser<CFStringRef> passphrase =
477  CFStringCreateWithBytes(nullptr, reinterpret_cast<const uint8_t*>(pw), pwLen, kCFStringEncodingUTF8, false);
478  CFReleaser<SecAccessRef> access;
479  SecAccessCreate(keyLabel.get(), nullptr, &access.get());
480 
481  CFArrayRef attributes = nullptr;
482 
483  const SecItemImportExportKeyParameters keyParams{
484  SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION, // version
485  0, // flags
486  passphrase.get(), // passphrase
487  nullptr, // alert title
488  nullptr, // alert prompt
489  access.get(), // access ref
490  nullptr, // key usage
491  attributes // key attributes
492  };
493 
494  CFReleaser<CFArrayRef> outItems;
495  OSStatus res = SecItemImport(importedKey.get(), // importedData
496  nullptr, // fileNameOrExtension
497  &externalFormat, // inputFormat
498  &externalType, // itemType
499  0, // flags
500  &keyParams, // keyParams
501  m_impl->keyChainRef, // importKeychain
502  &outItems.get()); // outItems
503 
504  if (res != errSecSuccess) {
505  if (res == errSecAuthFailed) {
506  BOOST_THROW_EXCEPTION(Error("Fail to unlock the keychain"));
507  }
508  else {
509  BOOST_THROW_EXCEPTION(Error("Cannot import the private key"));
510  }
511  }
512 
513  // C-style cast is used as per Apple convention
514  SecKeychainItemRef privateKey = (SecKeychainItemRef)CFArrayGetValueAtIndex(outItems.get(), 0);
515  SecKeychainAttribute attrs[1]; // maximum number of attributes
516  SecKeychainAttributeList attrList = { 0, attrs };
517  std::string keyUri = keyName.toUri();
518  {
519  attrs[attrList.count].tag = kSecKeyPrintName;
520  attrs[attrList.count].length = keyUri.size();
521  attrs[attrList.count].data = const_cast<char*>(keyUri.data());
522  attrList.count++;
523  }
524 
525  res = SecKeychainItemModifyAttributesAndData(privateKey, &attrList, 0, nullptr);
526 }
527 
528 } // namespace tpm
529 } // namespace security
530 } // namespace ndn
static ConstBufferPtr decrypt(const KeyRefOsx &key, const uint8_t *cipherText, size_t cipherSize)
bool isTpmLocked() const final
Copyright (c) 2011-2015 Regents of the University of California.
std::string toUri() const
Get URI representation of the name.
Definition: name.cpp:117
const T & get() const
util::CFReleaser< SecKeyRef > KeyRefOsx
Definition: key-ref-osx.hpp:32
void loadPkcs1(const uint8_t *buf, size_t size)
Load the private key in PKCS#1 format from a buffer buf.
RSA key, supports sign/verify and encrypt/decrypt operations.
static CFTypeRef getDigestAlgorithm(DigestAlgorithm digestAlgo)
SimplePublicKeyParams< detail::RsaKeyParamsInfo > RsaKeyParams
RsaKeyParams carries parameters for RSA key.
Definition: key-params.hpp:203
KeyType
The type of a cryptographic key.
static const std::string & getScheme()
bool isTerminalMode() const final
Check if TPM is in terminal mode.
SimplePublicKeyParams< detail::EcKeyParamsInfo > EcKeyParams
EcKeyParams carries parameters for EC key.
Definition: key-params.hpp:206
void setTerminalMode(bool isTerminal) const final
Set the terminal mode of TPM.
static CFTypeRef getAsymKeyType(KeyType keyType)
static ConstBufferPtr derivePublicKey(const KeyRefOsx &key)
BackEndOsx(const std::string &location="")
Create TPM backed based on macOS KeyChain service.
bool unlockTpm(const char *pw, size_t pwLen) const final
Unlock TPM.
static long getDigestSize(DigestAlgorithm digestAlgo)
Elliptic Curve key (e.g. for ECDSA), supports sign/verify operations.
Use the SHA256 hash of the public key as the key id.
Represents an absolute name.
Definition: name.hpp:42
static void setKeyName(KeyHandle &keyHandle, const Name &identity, const KeyParams &params)
Set the key name in keyHandle according to identity and params.
Definition: back-end.cpp:110
Helper class to wrap CoreFoundation object pointers.
static ConstBufferPtr sign(const KeyRefOsx &key, DigestAlgorithm digestAlgorithm, const uint8_t *buf, size_t size)
Sign buf with key using digestAlgorithm.
void retain(const T &typeRef)
ConstBufferPtr derivePublicKey() const
unique_ptr< T > make_unique(Args &&... args)
Definition: backports.hpp:96
CFReleaser< SecKeychainItemRef > getKey(const Name &keyName)
Get private key reference with name keyName.
shared_ptr< const Buffer > ConstBufferPtr
Definition: buffer.hpp:89