back-end-osx.cpp
Go to the documentation of this file.
1 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
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 
33 namespace ndn {
34 namespace security {
35 namespace tpm {
36 
38 {
39 public:
40  Impl()
41  : isTerminalMode(false)
42  {
43  }
44 
52  getKey(const Name& keyName)
53  {
54  CFReleaser<CFStringRef> keyLabel = CFStringCreateWithCString(nullptr, keyName.toUri().c_str(), kCFStringEncodingUTF8);
55 
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 
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 
79 public:
80  SecKeychainRef keyChainRef;
82 };
83 
84 
85 static CFTypeRef
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 
98 static CFTypeRef
100 {
101  switch (digestAlgo) {
103  return kSecDigestSHA2;
104  default:
105  return 0;
106  }
107 }
108 
109 static long
111 {
112  switch (digestAlgo) {
114  return 256;
115  default:
116  return -1;
117  }
118 }
119 
120 BackEndOsx::BackEndOsx(const std::string&)
121  : 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 
132 BackEndOsx::~BackEndOsx() = default;
133 
134 const std::string&
136 {
137  static std::string scheme = "tpm-osxkeychain";
138  return scheme;
139 }
140 
141 bool
143 {
144  return m_impl->isTerminalMode;
145 }
146 
147 void
148 BackEndOsx::setTerminalMode(bool isTerminal) const
149 {
150  m_impl->isTerminalMode = isTerminal;
151  SecKeychainSetUserInteractionAllowed(!isTerminal);
152 }
153 
154 bool
156 {
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 
166 bool
167 BackEndOsx::unlockTpm(const char* pw, size_t pwLen) const
168 {
169  // If the default key chain is already unlocked, return immediately.
170  if (!isTpmLocked())
171  return true;
172 
173  if (m_impl->isTerminalMode) {
174  // Use the supplied password.
175  SecKeychainUnlock(m_impl->keyChainRef, pwLen, pw, true);
176  }
177  else {
178  // If inTerminal is not set, get the password from GUI.
179  SecKeychainUnlock(m_impl->keyChainRef, 0, nullptr, false);
180  }
181 
182  return !isTpmLocked();
183 }
184 
186 BackEndOsx::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 
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 
242 BackEndOsx::decrypt(const KeyRefOsx& key, const uint8_t* cipherText, size_t cipherSize) const
243 {
244  CFReleaser<CFDataRef> dataRef = CFDataCreateWithBytesNoCopy(nullptr, cipherText, cipherSize, kCFAllocatorNull);
245 
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 
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 
298 bool
299 BackEndOsx::doHasKey(const Name& keyName) const
300 {
301  CFReleaser<CFStringRef> keyLabel = CFStringCreateWithCString(nullptr, keyName.toUri().c_str(), kCFStringEncodingUTF8);
302 
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 
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 
318 unique_ptr<KeyHandle>
319 BackEndOsx::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 
332 unique_ptr<KeyHandle>
333 BackEndOsx::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 EcKeyParams& ecParams = static_cast<const EcKeyParams&>(params);
345  keySize = ecParams.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 
398 void
399 BackEndOsx::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 
422 BackEndOsx::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 
457 void
458 BackEndOsx::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
bool isTpmLocked() const final
Copyright (c) 2013-2016 Regents of the University of California.
Definition: common.hpp:74
SimplePublicKeyParams< RsaKeyParamsInfo > RsaKeyParams
RsaKeyParams carries parameters for RSA key.
Definition: key-params.hpp:200
ConstBufferPtr sign(const KeyRefOsx &key, DigestAlgorithm digestAlgorithm, const uint8_t *buf, size_t size) const
Sign buf with key using digestAlgorithm.
void loadPkcs1(const uint8_t *buf, size_t size)
Load the private key in PKCS#1 format from a buffer buf.
Definition: private-key.cpp:69
bool isTerminalMode() const final
Check if TPM is in terminal mode.
static CFTypeRef getDigestAlgorithm(DigestAlgorithm digestAlgo)
void retain(const T &typeRef)
Definition: helper-osx.hpp:118
Helper class to wrap CoreFoundation object pointers.
Definition: helper-osx.hpp:48
static const std::string & getScheme()
std::string toUri() const
Encode this name as a URI.
Definition: name.cpp:171
ConstBufferPtr derivePublicKey(const KeyRefOsx &key) const
static CFTypeRef getAsymKeyType(KeyType keyType)
CFReleaser< SecKeyRef > KeyRefOsx
Definition: helper-osx.hpp:148
ConstBufferPtr decrypt(const KeyRefOsx &key, const uint8_t *cipherText, size_t cipherSize) const
ConstBufferPtr derivePublicKey() const
void setTerminalMode(bool isTerminal) const final
Set the terminal mode of TPM.
BackEndOsx(const std::string &location="")
Create TPM backed based on macOS KeyChain service.
static long getDigestSize(DigestAlgorithm digestAlgo)
Use the SHA256 hash of the public key as the key id.
Name abstraction to represent an absolute name.
Definition: name.hpp:46
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:108
SimplePublicKeyParams< EcKeyParamsInfo > EcKeyParams
EcKeyParams carries parameters for EC key.
Definition: key-params.hpp:203
bool unlockTpm(const char *pw, size_t pwLen) const final
Unlock TPM.
shared_ptr< const Buffer > ConstBufferPtr
Definition: buffer.hpp:33
CFReleaser< SecKeychainItemRef > getKey(const Name &keyName)
Get private key reference with name keyName.