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-2021 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 
30 
31 #include <Security/Security.h>
32 #include <cstring>
33 
34 #include <boost/lexical_cast.hpp>
35 
36 namespace ndn {
37 namespace security {
38 namespace tpm {
39 
40 namespace cfstring = detail::cfstring;
41 using detail::CFReleaser;
42 
44 {
45 public:
46  SecKeychainRef keyChainRef;
47  bool isTerminalMode = false;
48 };
49 
51 makeCFDataNoCopy(span<const uint8_t> buf)
52 {
53  return CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, buf.data(), buf.size(), kCFAllocatorNull);
54 }
55 
58 {
59  return CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
60  &kCFTypeDictionaryKeyCallBacks,
61  &kCFTypeDictionaryValueCallBacks);
62 }
63 
64 static std::string
65 getErrorMessage(OSStatus status)
66 {
67  CFReleaser<CFStringRef> msg = SecCopyErrorMessageString(status, nullptr);
68  if (msg != nullptr)
69  return cfstring::toStdString(msg.get());
70  else
71  return "<no error message>";
72 }
73 
74 static std::string
75 getFailureReason(CFErrorRef err)
76 {
77  CFReleaser<CFStringRef> reason = CFErrorCopyFailureReason(err);
78  if (reason != nullptr)
79  return cfstring::toStdString(reason.get());
80  else
81  return "<unknown reason>";
82 }
83 
84 static CFTypeRef
86 {
87  switch (keyType) {
88  case KeyType::RSA:
89  return kSecAttrKeyTypeRSA;
90  case KeyType::EC:
91  return kSecAttrKeyTypeECDSA;
92  default:
94  }
95 }
96 
97 static CFTypeRef
99 {
100  switch (digestAlgo) {
105  return kSecDigestSHA2;
106  default:
107  return nullptr;
108  }
109 }
110 
111 static int
113 {
114  switch (digestAlgo) {
116  return 224;
118  return 256;
120  return 384;
122  return 512;
123  default:
124  return -1;
125  }
126 }
127 
131 static KeyRefOsx
132 getKeyRef(const Name& keyName)
133 {
134  auto keyLabel = cfstring::fromStdString(keyName.toUri());
135 
136  auto query = makeCFMutableDictionary();
137  CFDictionaryAddValue(query.get(), kSecClass, kSecClassKey);
138  CFDictionaryAddValue(query.get(), kSecAttrKeyClass, kSecAttrKeyClassPrivate);
139  CFDictionaryAddValue(query.get(), kSecAttrLabel, keyLabel.get());
140  CFDictionaryAddValue(query.get(), kSecReturnRef, kCFBooleanTrue);
141 
142  KeyRefOsx keyRef;
143  // C-style cast is used as per Apple convention
144  OSStatus res = SecItemCopyMatching(query.get(), (CFTypeRef*)&keyRef.get());
145  keyRef.retain();
146 
147  if (res == errSecSuccess) {
148  return keyRef;
149  }
150  else if (res == errSecItemNotFound) {
151  return nullptr;
152  }
153  else {
154  NDN_THROW(Tpm::Error("Key lookup in keychain failed: " + getErrorMessage(res)));
155  }
156 }
157 
161 static void
163 {
164  // use a temporary password for PKCS8 encoding
165  const char pw[] = "correct horse battery staple";
166  auto passphrase = cfstring::fromBuffer(reinterpret_cast<const uint8_t*>(pw), std::strlen(pw));
167 
168  SecItemImportExportKeyParameters keyParams;
169  std::memset(&keyParams, 0, sizeof(keyParams));
170  keyParams.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
171  keyParams.passphrase = passphrase.get();
172 
173  CFReleaser<CFDataRef> exportedKey;
174  OSStatus res = SecItemExport(keyRef.get(), // secItemOrArray
175  kSecFormatWrappedPKCS8, // outputFormat
176  0, // flags
177  &keyParams, // keyParams
178  &exportedKey.get()); // exportedData
179 
180  if (res != errSecSuccess) {
181  NDN_THROW(Tpm::Error("Failed to export private key: "s + getErrorMessage(res)));
182  }
183 
184  auto keyPtr = CFDataGetBytePtr(exportedKey.get());
185  auto keyLen = static_cast<size_t>(CFDataGetLength(exportedKey.get()));
186  outKey.loadPkcs8({keyPtr, keyLen}, pw, std::strlen(pw));
187 }
188 
189 BackEndOsx::BackEndOsx(const std::string&)
190  : m_impl(make_unique<Impl>())
191 {
192  SecKeychainSetUserInteractionAllowed(!m_impl->isTerminalMode);
193 
194  OSStatus res = SecKeychainCopyDefault(&m_impl->keyChainRef);
195  if (res == errSecNoDefaultKeychain) {
196  NDN_THROW(Error("No default keychain, create one first"));
197  }
198 }
199 
200 BackEndOsx::~BackEndOsx() = default;
201 
202 const std::string&
204 {
205  static std::string scheme = "tpm-osxkeychain";
206  return scheme;
207 }
208 
209 bool
211 {
212  return m_impl->isTerminalMode;
213 }
214 
215 void
216 BackEndOsx::setTerminalMode(bool isTerminal) const
217 {
218  m_impl->isTerminalMode = isTerminal;
219  SecKeychainSetUserInteractionAllowed(!isTerminal);
220 }
221 
222 bool
224 {
225  SecKeychainStatus keychainStatus;
226  OSStatus res = SecKeychainGetStatus(m_impl->keyChainRef, &keychainStatus);
227  if (res != errSecSuccess)
228  return true;
229  else
230  return (kSecUnlockStateStatus & keychainStatus) == 0;
231 }
232 
233 bool
234 BackEndOsx::unlockTpm(const char* pw, size_t pwLen) const
235 {
236  // If the default key chain is already unlocked, return immediately.
237  if (!isTpmLocked())
238  return true;
239 
240  if (m_impl->isTerminalMode) {
241  // Use the supplied password.
242  SecKeychainUnlock(m_impl->keyChainRef, pwLen, pw, true);
243  }
244  else {
245  // If inTerminal is not set, get the password from GUI.
246  SecKeychainUnlock(m_impl->keyChainRef, 0, nullptr, false);
247  }
248 
249  return !isTpmLocked();
250 }
251 
253 BackEndOsx::sign(const KeyRefOsx& key, DigestAlgorithm digestAlgo, const InputBuffers& bufs)
254 {
256  CFReleaser<SecTransformRef> signer = SecSignTransformCreate(key.get(), &error.get());
257  if (signer == nullptr) {
258  NDN_THROW(Error("Failed to create sign transform: " + getFailureReason(error.get())));
259  }
260 
261  // Generate digest
262  OBufferStream digestSink;
263  using namespace transform;
264  bufferSource(bufs) >> digestFilter(digestAlgo) >> streamSink(digestSink);
265 
266  // Set input
267  auto buffer = digestSink.buf();
268  BOOST_ASSERT(buffer->size() * 8 == static_cast<size_t>(getDigestSize(digestAlgo)));
269  auto data = makeCFDataNoCopy(*buffer);
270  SecTransformSetAttribute(signer.get(), kSecTransformInputAttributeName, data.get(), &error.get());
271  if (error != nullptr) {
272  NDN_THROW(Error("Failed to configure input of sign transform: " + getFailureReason(error.get())));
273  }
274 
275  // Configure input as digest
276  SecTransformSetAttribute(signer.get(), kSecInputIsAttributeName, kSecInputIsDigest, &error.get());
277  if (error != nullptr) {
278  NDN_THROW(Error("Failed to configure sign transform input as digest: " + getFailureReason(error.get())));
279  }
280 
281  // Enable use of padding
282  SecTransformSetAttribute(signer.get(), kSecPaddingKey, kSecPaddingPKCS1Key, &error.get());
283  if (error != nullptr) {
284  NDN_THROW(Error("Failed to configure padding of sign transform: " + getFailureReason(error.get())));
285  }
286 
287  // Set digest type
288  SecTransformSetAttribute(signer.get(), kSecDigestTypeAttribute, getDigestAlgorithm(digestAlgo), &error.get());
289  if (error != nullptr) {
290  NDN_THROW(Error("Failed to configure digest type of sign transform: " + getFailureReason(error.get())));
291  }
292 
293  // Set digest length
294  int digestSize = getDigestSize(digestAlgo);
295  CFReleaser<CFNumberRef> cfDigestSize = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &digestSize);
296  SecTransformSetAttribute(signer.get(), kSecDigestLengthAttribute, cfDigestSize.get(), &error.get());
297  if (error != nullptr) {
298  NDN_THROW(Error("Failed to configure digest length of sign transform: " + getFailureReason(error.get())));
299  }
300 
301  // Actually sign
302  // C-style cast is used as per Apple convention
303  CFReleaser<CFDataRef> signature = (CFDataRef)SecTransformExecute(signer.get(), &error.get());
304  if (signature == nullptr) {
305  NDN_THROW(Error("Failed to sign data: " + getFailureReason(error.get())));
306  }
307 
308  return make_shared<Buffer>(CFDataGetBytePtr(signature.get()), CFDataGetLength(signature.get()));
309 }
310 
312 BackEndOsx::decrypt(const KeyRefOsx& key, span<const uint8_t> cipherText)
313 {
315  CFReleaser<SecTransformRef> decryptor = SecDecryptTransformCreate(key.get(), &error.get());
316  if (decryptor == nullptr) {
317  NDN_THROW(Error("Failed to create decrypt transform: " + getFailureReason(error.get())));
318  }
319 
320  auto data = makeCFDataNoCopy(cipherText);
321  SecTransformSetAttribute(decryptor.get(), kSecTransformInputAttributeName, data.get(), &error.get());
322  if (error != nullptr) {
323  NDN_THROW(Error("Failed to configure input of decrypt transform: " + getFailureReason(error.get())));
324  }
325 
326  SecTransformSetAttribute(decryptor.get(), kSecPaddingKey, kSecPaddingOAEPKey, &error.get());
327  if (error != nullptr) {
328  NDN_THROW(Error("Failed to configure padding of decrypt transform: " + getFailureReason(error.get())));
329  }
330 
331  // C-style cast is used as per Apple convention
332  CFReleaser<CFDataRef> plainText = (CFDataRef)SecTransformExecute(decryptor.get(), &error.get());
333  if (plainText == nullptr) {
334  NDN_THROW(Error("Failed to decrypt data: " + getFailureReason(error.get())));
335  }
336 
337  return make_shared<Buffer>(CFDataGetBytePtr(plainText.get()), CFDataGetLength(plainText.get()));
338 }
339 
342 {
343  transform::PrivateKey privateKey;
344  exportItem(key, privateKey);
345  return privateKey.derivePublicKey();
346 }
347 
348 bool
349 BackEndOsx::doHasKey(const Name& keyName) const
350 {
351  return getKeyRef(keyName) != nullptr;
352 }
353 
354 unique_ptr<KeyHandle>
355 BackEndOsx::doGetKeyHandle(const Name& keyName) const
356 {
357  KeyRefOsx keyRef = getKeyRef(keyName);
358  if (keyRef == nullptr) {
359  return nullptr;
360  }
361 
362  return make_unique<KeyHandleOsx>(keyRef.get());
363 }
364 
365 unique_ptr<KeyHandle>
366 BackEndOsx::doCreateKey(const Name& identityName, const KeyParams& params)
367 {
368  KeyType keyType = params.getKeyType();
369  uint32_t keySize;
370  switch (keyType) {
371  case KeyType::RSA: {
372  const RsaKeyParams& rsaParams = static_cast<const RsaKeyParams&>(params);
373  keySize = rsaParams.getKeySize();
374  break;
375  }
376  case KeyType::EC: {
377  const EcKeyParams& ecParams = static_cast<const EcKeyParams&>(params);
378  keySize = ecParams.getKeySize();
379  break;
380  }
381  default: {
382  NDN_THROW(std::invalid_argument("macOS-based TPM does not support creating a key of type " +
383  boost::lexical_cast<std::string>(keyType)));
384  }
385  }
386  CFReleaser<CFNumberRef> cfKeySize = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &keySize);
387 
388  auto attrDict = makeCFMutableDictionary();
389  CFDictionaryAddValue(attrDict.get(), kSecAttrKeyType, getAsymKeyType(keyType));
390  CFDictionaryAddValue(attrDict.get(), kSecAttrKeySizeInBits, cfKeySize.get());
391 
392  KeyRefOsx publicKey, privateKey;
393  OSStatus res = SecKeyGeneratePair(attrDict.get(), &publicKey.get(), &privateKey.get());
394  publicKey.retain();
395  privateKey.retain();
396 
397  if (res != errSecSuccess) {
398  NDN_THROW(Error("Failed to generate key pair: " + getErrorMessage(res)));
399  }
400 
401  unique_ptr<KeyHandle> keyHandle = make_unique<KeyHandleOsx>(privateKey.get());
402  Name keyName = constructAsymmetricKeyName(*keyHandle, identityName, params);
403  keyHandle->setKeyName(keyName);
404 
405  SecKeychainAttribute attrs[1]; // maximum number of attributes
406  SecKeychainAttributeList attrList = {0, attrs};
407  std::string keyUri = keyName.toUri();
408  {
409  attrs[attrList.count].tag = kSecKeyPrintName;
410  attrs[attrList.count].length = keyUri.size();
411  attrs[attrList.count].data = const_cast<char*>(keyUri.data());
412  attrList.count++;
413  }
414 
415  SecKeychainItemModifyAttributesAndData((SecKeychainItemRef)privateKey.get(), &attrList, 0, nullptr);
416  SecKeychainItemModifyAttributesAndData((SecKeychainItemRef)publicKey.get(), &attrList, 0, nullptr);
417 
418  return keyHandle;
419 }
420 
421 void
422 BackEndOsx::doDeleteKey(const Name& keyName)
423 {
424  auto keyLabel = cfstring::fromStdString(keyName.toUri());
425 
426  auto query = makeCFMutableDictionary();
427  CFDictionaryAddValue(query.get(), kSecClass, kSecClassKey);
428  CFDictionaryAddValue(query.get(), kSecAttrLabel, keyLabel.get());
429  CFDictionaryAddValue(query.get(), kSecMatchLimit, kSecMatchLimitAll);
430 
431  OSStatus res = SecItemDelete(query.get());
432 
433  if (res != errSecSuccess && res != errSecItemNotFound) {
434  NDN_THROW(Error("Failed to delete key pair: " + getErrorMessage(res)));
435  }
436 }
437 
439 BackEndOsx::doExportKey(const Name& keyName, const char* pw, size_t pwLen)
440 {
441  KeyRefOsx keychainItem = getKeyRef(keyName);
442  if (keychainItem == nullptr) {
443  NDN_THROW(Error("Failed to export private key: " + getErrorMessage(errSecItemNotFound)));
444  }
445 
446  transform::PrivateKey exportedKey;
447  OBufferStream pkcs8;
448  try {
449  exportItem(keychainItem, exportedKey);
450  exportedKey.savePkcs8(pkcs8, pw, pwLen);
451  }
452  catch (const transform::PrivateKey::Error&) {
453  NDN_THROW_NESTED(Error("Failed to export private key"));
454  }
455  return pkcs8.buf();
456 }
457 
458 void
459 BackEndOsx::doImportKey(const Name& keyName, span<const uint8_t> pkcs8, const char* pw, size_t pwLen)
460 {
461  transform::PrivateKey privKey;
462  OBufferStream pkcs1;
463  try {
464  // do the PKCS8 decoding ourselves, see bug #4450
465  privKey.loadPkcs8(pkcs8, pw, pwLen);
466  privKey.savePkcs1(pkcs1);
467  }
468  catch (const transform::PrivateKey::Error&) {
469  NDN_THROW_NESTED(Error("Failed to import private key"));
470  }
471  auto keyToImport = makeCFDataNoCopy(*pkcs1.buf());
472 
473  SecExternalFormat externalFormat = kSecFormatOpenSSL;
474  SecExternalItemType externalType = kSecItemTypePrivateKey;
475 
476  auto keyUri = keyName.toUri();
477  auto keyLabel = cfstring::fromStdString(keyUri);
479  OSStatus res = SecAccessCreate(keyLabel.get(), // descriptor
480  nullptr, // trustedlist (null == trust only the calling app)
481  &access.get()); // accessRef
482 
483  if (res != errSecSuccess) {
484  NDN_THROW(Error("Failed to import private key: " + getErrorMessage(res)));
485  }
486 
487  SecItemImportExportKeyParameters keyParams;
488  std::memset(&keyParams, 0, sizeof(keyParams));
489  keyParams.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
490  keyParams.accessRef = access.get();
491 
492  CFReleaser<CFArrayRef> outItems;
493  res = SecItemImport(keyToImport.get(), // importedData
494  nullptr, // fileNameOrExtension
495  &externalFormat, // inputFormat
496  &externalType, // itemType
497  0, // flags
498  &keyParams, // keyParams
499  m_impl->keyChainRef, // importKeychain
500  &outItems.get()); // outItems
501 
502  if (res != errSecSuccess) {
503  NDN_THROW(Error("Failed to import private key: " + getErrorMessage(res)));
504  }
505 
506  // C-style cast is used as per Apple convention
507  SecKeychainItemRef keychainItem = (SecKeychainItemRef)CFArrayGetValueAtIndex(outItems.get(), 0);
508  SecKeychainAttribute attrs[1]; // maximum number of attributes
509  SecKeychainAttributeList attrList = {0, attrs};
510  {
511  attrs[attrList.count].tag = kSecKeyPrintName;
512  attrs[attrList.count].length = keyUri.size();
513  attrs[attrList.count].data = const_cast<char*>(keyUri.data());
514  attrList.count++;
515  }
516  SecKeychainItemModifyAttributesAndData(keychainItem, &attrList, 0, nullptr);
517 }
518 
519 void
520 BackEndOsx::doImportKey(const Name& keyName, shared_ptr<transform::PrivateKey> key)
521 {
522  NDN_THROW(Error("macOS-based TPM does not support importing a transform::PrivateKey"));
523 }
524 
525 } // namespace tpm
526 } // namespace security
527 } // namespace ndn
#define NDN_THROW_NESTED(e)
Definition: exception.hpp:71
CFReleaser< CFStringRef > fromStdString(const std::string &str)
Create a CFString by copying characters from a std::string.
This file contains utilities to deal with Apple Core Foundation&#39;s CFString and related types...
bool isTpmLocked() const final
Check if the TPM is locked.
Copyright (c) 2011-2015 Regents of the University of California.
std::string toStdString(CFStringRef cfStr)
Convert a CFString to a std::string.
static CFReleaser< CFMutableDictionaryRef > makeCFMutableDictionary()
RSA key, supports sign/verify and encrypt/decrypt operations.
static CFTypeRef getDigestAlgorithm(DigestAlgorithm digestAlgo)
static KeyRefOsx getKeyRef(const Name &keyName)
Get reference to private key with name keyName.
void savePkcs8(std::ostream &os, const char *pw, size_t pwLen) const
Save the private key in encrypted PKCS#8 format into a stream os.
static std::string getErrorMessage(OSStatus status)
#define NDN_THROW(e)
Definition: exception.hpp:61
KeyType
The type of a cryptographic key.
static const std::string & getScheme()
Name constructAsymmetricKeyName(const KeyHandle &key, const Name &identity, const KeyParams &params) const
Construct and return the name of a RSA or EC key, based on identity and params.
Definition: back-end.cpp:114
bool isTerminalMode() const final
Check if the TPM is in terminal mode.
void setTerminalMode(bool isTerminal) const final
Set the terminal mode of the TPM.
void retain(const T &typeRef)
static CFTypeRef getAsymKeyType(KeyType keyType)
static int getDigestSize(DigestAlgorithm digestAlgo)
static ConstBufferPtr derivePublicKey(const KeyRefOsx &key)
void loadPkcs8(span< const uint8_t > buf, const char *pw, size_t pwLen)
Load the private key in encrypted PKCS#8 format from a buffer buf with passphrase pw...
unique_ptr< Sink > streamSink(std::ostream &os)
Definition: stream-sink.cpp:53
BackEndOsx(const std::string &location="")
Create TPM backed based on macOS Keychain Services.
bool unlockTpm(const char *pw, size_t pwLen) const final
Unlock the TPM.
Elliptic Curve key (e.g. for ECDSA), supports sign/verify operations.
unique_ptr< Transform > digestFilter(DigestAlgorithm algo)
Use the SHA-256 hash of the public key as key id.
Represents an absolute name.
Definition: name.hpp:41
KeyType getKeyType() const
Definition: key-params.hpp:48
static CFReleaser< CFDataRef > makeCFDataNoCopy(span< const uint8_t > buf)
Helper class to wrap CoreFoundation object pointers.
CFReleaser< CFStringRef > fromBuffer(const uint8_t *buf, size_t buflen)
Create a CFString by copying bytes from a raw buffer.
shared_ptr< Buffer > buf()
Flush written data to the stream and return shared pointer to the underlying buffer.
#define NDN_CXX_UNREACHABLE
Definition: backports.hpp:138
Base class for key parameters.
Definition: key-params.hpp:35
void toUri(std::ostream &os, name::UriFormat format=name::UriFormat::DEFAULT) const
Write URI representation of the name to the output stream.
Definition: name.cpp:349
uint32_t getKeySize() const
Definition: key-params.hpp:176
ConstBufferPtr derivePublicKey() const
static std::string getFailureReason(CFErrorRef err)
implements an output stream that constructs ndn::Buffer
static void exportItem(const KeyRefOsx &keyRef, transform::PrivateKey &outKey)
Export a private key from the Keychain to outKey.
InputBuffers bufs
SimplePublicKeyParams is a template for public keys with only one parameter: size.
Definition: key-params.hpp:149
static ConstBufferPtr decrypt(const KeyRefOsx &key, span< const uint8_t > cipherText)
Decrypt cipherText with key.
static ConstBufferPtr sign(const KeyRefOsx &key, DigestAlgorithm digestAlgorithm, const InputBuffers &bufs)
Sign bufs with key using digestAlgorithm.
void savePkcs1(std::ostream &os) const
Save the private key in PKCS#1 format into a stream os.
shared_ptr< const Buffer > ConstBufferPtr
Definition: buffer.hpp:139