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