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 
38 class BackEndOsx::Impl
39 {
40 public:
41  Impl()
42  : isTerminalMode(false)
43  {
44  }
45 
52  CFReleaser<SecKeychainItemRef>
53  getKey(const Name& keyName)
54  {
55  CFReleaser<CFStringRef> keyLabel = CFStringCreateWithCString(nullptr, keyName.toUri().c_str(),
56  kCFStringEncodingUTF8);
57 
58  CFReleaser<CFMutableDictionaryRef> attrDict =
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 
66  CFReleaser<SecKeychainItemRef> keyItem;
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;
83  bool isTerminalMode;
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 {
199  CFReleaser<CFErrorRef> error;
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 {
250  CFReleaser<CFErrorRef> error;
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 
310  CFReleaser<CFMutableDictionaryRef> attrDict =
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 
317  CFReleaser<SecKeychainItemRef> itemRef;
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) 2013-2017 Regents of the University of California.
Definition: common.hpp:66
const T & get() const
KeyType getKeyType() const
Definition: key-params.hpp:52
uint32_t getKeySize() const
Definition: key-params.hpp:180
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.
unique_ptr< T > make_unique(Args &&...args)
Definition: backports.hpp:73
static CFTypeRef getDigestAlgorithm(DigestAlgorithm digestAlgo)
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:122
bool isTerminalMode() const final
Check if TPM is in terminal mode.
void setTerminalMode(bool isTerminal) const final
Set the terminal mode of TPM.
static CFTypeRef getAsymKeyType(KeyType keyType)
static ConstBufferPtr derivePublicKey(const KeyRefOsx &key)
ConstBufferPtr derivePublicKey() const
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
static ConstBufferPtr sign(const KeyRefOsx &key, DigestAlgorithm digestAlgorithm, const uint8_t *buf, size_t size)
Sign buf with key using digestAlgorithm.
Base class of key parameters.
Definition: key-params.hpp:35
SimplePublicKeyParams is a template for public keys with only one parameter: size.
Definition: key-params.hpp:153
shared_ptr< const Buffer > ConstBufferPtr
Definition: buffer.hpp:89