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