security: in tpm::BackEndOsx, unwrap the key before importing it

We use our own openssl-based routines for the PKCS#8 decoding/decryption,
as they support a much wider range of algorithms than macOS's Keychain.

Change-Id: Ia841e7681a47563c696d054d46fe523f427c99cb
Refs: #4450
diff --git a/src/security/tpm/back-end-osx.cpp b/src/security/tpm/back-end-osx.cpp
index 15bd41a..daf5281 100644
--- a/src/security/tpm/back-end-osx.cpp
+++ b/src/security/tpm/back-end-osx.cpp
@@ -23,6 +23,7 @@
 #include "key-handle-osx.hpp"
 #include "tpm.hpp"
 #include "../transform/private-key.hpp"
+#include "../../encoding/buffer-stream.hpp"
 #include "../../util/cf-string-osx.hpp"
 
 #include <Security/Security.h>
@@ -150,6 +151,36 @@
   }
 }
 
+/**
+ * @brief Export a private key from the Keychain to @p outKey
+ */
+static void
+exportItem(const KeyRefOsx& keyRef, transform::PrivateKey& outKey)
+{
+  // use a temporary password for PKCS8 encoding
+  const char pw[] = "correct horse battery staple";
+  auto passphrase = cfstring::fromBuffer(reinterpret_cast<const uint8_t*>(pw), std::strlen(pw));
+
+  SecItemImportExportKeyParameters keyParams;
+  std::memset(&keyParams, 0, sizeof(keyParams));
+  keyParams.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
+  keyParams.passphrase = passphrase.get();
+
+  CFReleaser<CFDataRef> exportedKey;
+  OSStatus res = SecItemExport(keyRef.get(),           // secItemOrArray
+                               kSecFormatWrappedPKCS8, // outputFormat
+                               0,                      // flags
+                               &keyParams,             // keyParams
+                               &exportedKey.get());    // exportedData
+
+  if (res != errSecSuccess) {
+    BOOST_THROW_EXCEPTION(BackEnd::Error("Failed to export private key: "s + getErrorMessage(res)));
+  }
+
+  outKey.loadPkcs8(CFDataGetBytePtr(exportedKey.get()), CFDataGetLength(exportedKey.get()),
+                   pw, std::strlen(pw));
+}
+
 BackEndOsx::BackEndOsx(const std::string&)
   : m_impl(make_unique<Impl>())
 {
@@ -297,19 +328,8 @@
 ConstBufferPtr
 BackEndOsx::derivePublicKey(const KeyRefOsx& key)
 {
-  CFReleaser<CFDataRef> exportedKey;
-  OSStatus res = SecItemExport(key.get(),           // secItemOrArray
-                               kSecFormatOpenSSL,   // outputFormat
-                               0,                   // flags
-                               nullptr,             // keyParams
-                               &exportedKey.get()); // exportedData
-
-  if (res != errSecSuccess) {
-    BOOST_THROW_EXCEPTION(Error("Failed to export private key: "s + getErrorMessage(res)));
-  }
-
   transform::PrivateKey privateKey;
-  privateKey.loadPkcs1(CFDataGetBytePtr(exportedKey.get()), CFDataGetLength(exportedKey.get()));
+  exportItem(key, privateKey);
   return privateKey.derivePublicKey();
 }
 
@@ -409,73 +429,78 @@
     BOOST_THROW_EXCEPTION(Error("Failed to export private key: " + getErrorMessage(errSecItemNotFound)));
   }
 
-  auto passphrase = cfstring::fromBuffer(reinterpret_cast<const uint8_t*>(pw), pwLen);
-  SecItemImportExportKeyParameters keyParams;
-  std::memset(&keyParams, 0, sizeof(keyParams));
-  keyParams.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
-  keyParams.passphrase = passphrase.get();
-
-  CFReleaser<CFDataRef> exportedKey;
-  OSStatus res = SecItemExport(keychainItem.get(),     // secItemOrArray
-                               kSecFormatWrappedPKCS8, // outputFormat
-                               0,                      // flags
-                               &keyParams,             // keyParams
-                               &exportedKey.get());    // exportedData
-
-  if (res != errSecSuccess) {
-    BOOST_THROW_EXCEPTION(Error("Failed to export private key: " + getErrorMessage(res)));
+  transform::PrivateKey exportedKey;
+  OBufferStream pkcs8;
+  try {
+    exportItem(keychainItem, exportedKey);
+    exportedKey.savePkcs8(pkcs8, pw, pwLen);
   }
-
-  return make_shared<Buffer>(CFDataGetBytePtr(exportedKey.get()), CFDataGetLength(exportedKey.get()));
+  catch (const transform::PrivateKey::Error& e) {
+    BOOST_THROW_EXCEPTION(Error("Failed to export private key: "s + e.what()));
+  }
+  return pkcs8.buf();
 }
 
 void
 BackEndOsx::doImportKey(const Name& keyName, const uint8_t* buf, size_t size,
                         const char* pw, size_t pwLen)
 {
-  auto importedKey = makeCFDataNoCopy(buf, size);
+  transform::PrivateKey privKey;
+  OBufferStream pkcs1;
+  try {
+    // do the PKCS8 decoding ourselves, see bug #4450
+    privKey.loadPkcs8(buf, size, pw, pwLen);
+    privKey.savePkcs1(pkcs1);
+  }
+  catch (const transform::PrivateKey::Error& e) {
+    BOOST_THROW_EXCEPTION(Error("Failed to import private key: "s + e.what()));
+  }
+  auto keyToImport = makeCFDataNoCopy(pkcs1.buf()->data(), pkcs1.buf()->size());
 
-  SecExternalFormat externalFormat = kSecFormatWrappedPKCS8;
+  SecExternalFormat externalFormat = kSecFormatOpenSSL;
   SecExternalItemType externalType = kSecItemTypePrivateKey;
 
-  auto passphrase = cfstring::fromBuffer(reinterpret_cast<const uint8_t*>(pw), pwLen);
-  auto keyLabel = cfstring::fromStdString(keyName.toUri());
+  auto keyUri = keyName.toUri();
+  auto keyLabel = cfstring::fromStdString(keyUri);
   CFReleaser<SecAccessRef> access;
-  SecAccessCreate(keyLabel.get(), nullptr, &access.get());
+  OSStatus res = SecAccessCreate(keyLabel.get(), // descriptor
+                                 nullptr,        // trustedlist (null == trust only the calling app)
+                                 &access.get()); // accessRef
+
+  if (res != errSecSuccess) {
+    BOOST_THROW_EXCEPTION(Error("Failed to import private key: " + getErrorMessage(res)));
+  }
 
   SecItemImportExportKeyParameters keyParams;
   std::memset(&keyParams, 0, sizeof(keyParams));
   keyParams.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
-  keyParams.passphrase = passphrase.get();
   keyParams.accessRef = access.get();
 
   CFReleaser<CFArrayRef> outItems;
-  OSStatus res = SecItemImport(importedKey.get(),   // importedData
-                               nullptr,             // fileNameOrExtension
-                               &externalFormat,     // inputFormat
-                               &externalType,       // itemType
-                               0,                   // flags
-                               &keyParams,          // keyParams
-                               m_impl->keyChainRef, // importKeychain
-                               &outItems.get());    // outItems
+  res = SecItemImport(keyToImport.get(),   // importedData
+                      nullptr,             // fileNameOrExtension
+                      &externalFormat,     // inputFormat
+                      &externalType,       // itemType
+                      0,                   // flags
+                      &keyParams,          // keyParams
+                      m_impl->keyChainRef, // importKeychain
+                      &outItems.get());    // outItems
 
   if (res != errSecSuccess) {
     BOOST_THROW_EXCEPTION(Error("Failed to import private key: " + getErrorMessage(res)));
   }
 
   // C-style cast is used as per Apple convention
-  SecKeychainItemRef privateKey = (SecKeychainItemRef)CFArrayGetValueAtIndex(outItems.get(), 0);
+  SecKeychainItemRef keychainItem = (SecKeychainItemRef)CFArrayGetValueAtIndex(outItems.get(), 0);
   SecKeychainAttribute attrs[1]; // maximum number of attributes
   SecKeychainAttributeList attrList = {0, attrs};
-  std::string keyUri = keyName.toUri();
   {
     attrs[attrList.count].tag = kSecKeyPrintName;
     attrs[attrList.count].length = keyUri.size();
     attrs[attrList.count].data = const_cast<char*>(keyUri.data());
     attrList.count++;
   }
-
-  SecKeychainItemModifyAttributesAndData(privateKey, &attrList, 0, nullptr);
+  SecKeychainItemModifyAttributesAndData(keychainItem, &attrList, 0, nullptr);
 }
 
 } // namespace tpm