security: Add new TPM framework
The TPM framework is separated into two parts:
1) The front end class Tpm provides the interface to KeyChain. The public
interface of Tpm is read-only.
2) The back end classes represent concrete implementations, such as
tpm::BackEndFile and tpm::BackEndOsx which may also provide
implementation-specific management interfaces.
New TPM supports different key id type when generating new key. The
default type is changed to 64-bit random number.
Change-Id: I41154c2ded4b65fb0bef2f4a0d2c5b77843be05d
Refs: #2948
diff --git a/src/security/tpm/back-end-osx.cpp b/src/security/tpm/back-end-osx.cpp
new file mode 100644
index 0000000..d423b10
--- /dev/null
+++ b/src/security/tpm/back-end-osx.cpp
@@ -0,0 +1,514 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2016 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "back-end-osx.hpp"
+#include "key-handle-osx.hpp"
+#include "helper-osx.hpp"
+#include "../transform/private-key.hpp"
+#include "tpm.hpp"
+
+#include <CoreServices/CoreServices.h>
+#include <Security/Security.h>
+#include <Security/SecRandom.h>
+#include <Security/SecDigestTransform.h>
+
+namespace ndn {
+namespace security {
+namespace tpm {
+
+class BackEndOsx::Impl
+{
+public:
+ Impl()
+ : isTerminalMode(false)
+ {
+ }
+
+ /**
+ * @brief Get private key reference with name @p keyName.
+ *
+ * @param keyName
+ * @returns reference to the key
+ */
+ CFReleaser<SecKeychainItemRef>
+ getKey(const Name& keyName)
+ {
+ CFReleaser<CFStringRef> keyLabel = CFStringCreateWithCString(nullptr, keyName.toUri().c_str(), kCFStringEncodingUTF8);
+
+ CFReleaser<CFMutableDictionaryRef> attrDict =
+ CFDictionaryCreateMutable(nullptr, 5, &kCFTypeDictionaryKeyCallBacks, nullptr);
+
+ CFDictionaryAddValue(attrDict.get(), kSecClass, kSecClassKey);
+ CFDictionaryAddValue(attrDict.get(), kSecAttrLabel, keyLabel.get());
+ CFDictionaryAddValue(attrDict.get(), kSecAttrKeyClass, kSecAttrKeyClassPrivate);
+ CFDictionaryAddValue(attrDict.get(), kSecReturnRef, kCFBooleanTrue);
+
+ CFReleaser<SecKeychainItemRef> keyItem;
+ // C-style cast is used as per Apple convention
+ OSStatus res = SecItemCopyMatching((CFDictionaryRef)attrDict.get(), (CFTypeRef*)&keyItem.get());
+ keyItem.retain();
+
+ if (res != errSecSuccess) {
+ if (res == errSecAuthFailed) {
+ BOOST_THROW_EXCEPTION(Error("Fail to unlock the keychain"));
+ }
+ BOOST_THROW_EXCEPTION(std::domain_error("Key does not exist"));
+ }
+
+ return keyItem;
+ }
+
+public:
+ SecKeychainRef keyChainRef;
+ bool isTerminalMode;
+};
+
+
+static CFTypeRef
+getAsymKeyType(KeyType keyType)
+{
+ switch (keyType) {
+ case KeyType::RSA:
+ return kSecAttrKeyTypeRSA;
+ case KeyType::EC:
+ return kSecAttrKeyTypeECDSA;
+ default:
+ BOOST_THROW_EXCEPTION(Tpm::Error("Unsupported key type"));
+ }
+}
+
+static CFTypeRef
+getDigestAlgorithm(DigestAlgorithm digestAlgo)
+{
+ switch (digestAlgo) {
+ case DigestAlgorithm::SHA256:
+ return kSecDigestSHA2;
+ default:
+ return 0;
+ }
+}
+
+static long
+getDigestSize(DigestAlgorithm digestAlgo)
+{
+ switch (digestAlgo) {
+ case DigestAlgorithm::SHA256:
+ return 256;
+ default:
+ return -1;
+ }
+}
+
+BackEndOsx::BackEndOsx()
+ : m_impl(new Impl)
+{
+ SecKeychainSetUserInteractionAllowed(!m_impl->isTerminalMode);
+
+ OSStatus res = SecKeychainCopyDefault(&m_impl->keyChainRef);
+
+ if (res == errSecNoDefaultKeychain) { //If no default key chain, create one.
+ BOOST_THROW_EXCEPTION(Error("No default keychain, create one first"));
+ }
+}
+
+BackEndOsx::~BackEndOsx() = default;
+
+void
+BackEndOsx::setTerminalMode(bool isTerminal)
+{
+ m_impl->isTerminalMode = isTerminal;
+ SecKeychainSetUserInteractionAllowed(!isTerminal);
+}
+
+bool
+BackEndOsx::isTerminalMode() const
+{
+ return m_impl->isTerminalMode;
+}
+
+bool
+BackEndOsx::isLocked() const
+{
+ SecKeychainStatus keychainStatus;
+
+ OSStatus res = SecKeychainGetStatus(m_impl->keyChainRef, &keychainStatus);
+ if (res != errSecSuccess)
+ return true;
+ else
+ return ((kSecUnlockStateStatus & keychainStatus) == 0);
+}
+
+bool
+BackEndOsx::unlockTpm(const char* password, size_t passwordLength)
+{
+ // If the default key chain is already unlocked, return immediately.
+ if (!isLocked())
+ return true;
+
+ if (m_impl->isTerminalMode) {
+ // Use the supplied password.
+ SecKeychainUnlock(m_impl->keyChainRef, passwordLength, password, true);
+ }
+ else {
+ // If inTerminal is not set, get the password from GUI.
+ SecKeychainUnlock(m_impl->keyChainRef, 0, nullptr, false);
+ }
+
+ return !isLocked();
+}
+
+ConstBufferPtr
+BackEndOsx::sign(const KeyRefOsx& key, DigestAlgorithm digestAlgorithm,
+ const uint8_t* buf, size_t size) const
+{
+ CFReleaser<CFDataRef> dataRef = CFDataCreateWithBytesNoCopy(nullptr, buf, size, kCFAllocatorNull);
+
+ CFReleaser<CFErrorRef> error;
+ // C-style cast is used as per Apple convention
+ CFReleaser<SecTransformRef> signer = SecSignTransformCreate(key.get(), &error.get());
+ if (error != nullptr) {
+ BOOST_THROW_EXCEPTION(Error("Fail to create signer"));
+ }
+
+ // Set input
+ SecTransformSetAttribute(signer.get(), kSecTransformInputAttributeName, dataRef.get(), &error.get());
+ if (error != nullptr) {
+ BOOST_THROW_EXCEPTION(Error("Fail to configure input of signer"));
+ }
+
+ // Enable use of padding
+ SecTransformSetAttribute(signer.get(), kSecPaddingKey, kSecPaddingPKCS1Key, &error.get());
+ if (error != nullptr) {
+ BOOST_THROW_EXCEPTION(Error("Fail to configure digest algorithm of signer"));
+ }
+
+ // Set padding type
+ SecTransformSetAttribute(signer.get(), kSecDigestTypeAttribute, getDigestAlgorithm(digestAlgorithm), &error.get());
+ if (error != nullptr) {
+ BOOST_THROW_EXCEPTION(Error("Fail to configure digest algorithm of signer"));
+ }
+
+ // Set digest attribute
+ long digestSize = getDigestSize(digestAlgorithm);
+ CFReleaser<CFNumberRef> cfDigestSize = CFNumberCreate(nullptr, kCFNumberLongType, &digestSize);
+ SecTransformSetAttribute(signer.get(),
+ kSecDigestLengthAttribute,
+ cfDigestSize.get(),
+ &error.get());
+ if (error != nullptr) {
+ BOOST_THROW_EXCEPTION(Error("Fail to configure digest size of signer"));
+ }
+ // Actually sign
+ // C-style cast is used as per Apple convention
+ CFReleaser<CFDataRef> signature = (CFDataRef)SecTransformExecute(signer.get(), &error.get());
+ if (error != nullptr) {
+ CFShow(error.get());
+ BOOST_THROW_EXCEPTION(Error("Fail to sign data"));
+ }
+
+ if (signature == nullptr) {
+ BOOST_THROW_EXCEPTION(Error("Signature is NULL!\n"));
+ }
+
+ return make_shared<Buffer>(CFDataGetBytePtr(signature.get()), CFDataGetLength(signature.get()));
+}
+
+ConstBufferPtr
+BackEndOsx::decrypt(const KeyRefOsx& key, const uint8_t* cipherText, size_t cipherSize) const
+{
+ CFReleaser<CFDataRef> dataRef = CFDataCreateWithBytesNoCopy(nullptr, cipherText, cipherSize, kCFAllocatorNull);
+
+ CFReleaser<CFErrorRef> error;
+ CFReleaser<SecTransformRef> decryptor = SecDecryptTransformCreate(key.get(), &error.get());
+ if (error != nullptr) {
+ BOOST_THROW_EXCEPTION(Error("Fail to create decrypt"));
+ }
+
+ SecTransformSetAttribute(decryptor.get(), kSecTransformInputAttributeName, dataRef.get(), &error.get());
+ if (error != nullptr) {
+ BOOST_THROW_EXCEPTION(Error("Fail to configure decrypt"));
+ }
+
+ SecTransformSetAttribute(decryptor.get(), kSecPaddingKey, kSecPaddingOAEPKey, &error.get());
+ if (error != nullptr) {
+ BOOST_THROW_EXCEPTION(Error("Fail to configure decrypt #2"));
+ }
+
+ CFReleaser<CFDataRef> output = (CFDataRef)SecTransformExecute(decryptor.get(), &error.get());
+ if (error != nullptr) {
+ // CFShow(error);
+ BOOST_THROW_EXCEPTION(Error("Fail to decrypt data"));
+ }
+
+ if (output == nullptr) {
+ BOOST_THROW_EXCEPTION(Error("Output is NULL!\n"));
+ }
+ return make_shared<Buffer>(CFDataGetBytePtr(output.get()), CFDataGetLength(output.get()));
+}
+
+ConstBufferPtr
+BackEndOsx::derivePublicKey(const KeyRefOsx& key) const
+{
+ CFReleaser<CFDataRef> exportedKey;
+ OSStatus res = SecItemExport(key.get(), // secItemOrArray
+ kSecFormatOpenSSL, // outputFormat
+ 0, // flags
+ nullptr, // keyParams
+ &exportedKey.get()); // exportedData
+
+ if (res != errSecSuccess) {
+ if (res == errSecAuthFailed) {
+ BOOST_THROW_EXCEPTION(Error("Fail to unlock the keychain"));
+ }
+ else {
+ BOOST_THROW_EXCEPTION(Error("Fail to export private key"));
+ }
+ }
+
+ transform::PrivateKey privateKey;
+ privateKey.loadPkcs1(CFDataGetBytePtr(exportedKey.get()), CFDataGetLength(exportedKey.get()));
+ return privateKey.derivePublicKey();
+}
+
+bool
+BackEndOsx::doHasKey(const Name& keyName) const
+{
+ CFReleaser<CFStringRef> keyLabel = CFStringCreateWithCString(nullptr, keyName.toUri().c_str(), kCFStringEncodingUTF8);
+
+ CFReleaser<CFMutableDictionaryRef> attrDict =
+ CFDictionaryCreateMutable(nullptr, 4, &kCFTypeDictionaryKeyCallBacks, nullptr);
+
+ CFDictionaryAddValue(attrDict.get(), kSecClass, kSecClassKey);
+ CFDictionaryAddValue(attrDict.get(), kSecAttrLabel, keyLabel.get());
+ CFDictionaryAddValue(attrDict.get(), kSecReturnRef, kCFBooleanTrue);
+
+ CFReleaser<SecKeychainItemRef> itemRef;
+ // C-style cast is used as per Apple convention
+ OSStatus res = SecItemCopyMatching((CFDictionaryRef)attrDict.get(), (CFTypeRef*)&itemRef.get());
+ itemRef.retain();
+
+ return (res == errSecSuccess);
+}
+
+unique_ptr<KeyHandle>
+BackEndOsx::doGetKeyHandle(const Name& keyName) const
+{
+ CFReleaser<SecKeychainItemRef> keyItem;
+ try {
+ keyItem = m_impl->getKey(keyName);
+ }
+ catch (const std::domain_error&) {
+ return nullptr;
+ }
+
+ return make_unique<KeyHandleOsx>(*this, (SecKeyRef)keyItem.get());
+}
+
+unique_ptr<KeyHandle>
+BackEndOsx::doCreateKey(const Name& identityName, const KeyParams& params)
+{
+ KeyType keyType = params.getKeyType();
+ uint32_t keySize;
+ switch (keyType) {
+ case KeyType::RSA: {
+ const RsaKeyParams& rsaParams = static_cast<const RsaKeyParams&>(params);
+ keySize = rsaParams.getKeySize();
+ break;
+ }
+ case KeyType::EC: {
+ const EcdsaKeyParams& ecdsaParams = static_cast<const EcdsaKeyParams&>(params);
+ keySize = ecdsaParams.getKeySize();
+ break;
+ }
+ default: {
+ BOOST_THROW_EXCEPTION(Tpm::Error("Fail to create a key pair: Unsupported key type"));
+ }
+ }
+ CFReleaser<CFNumberRef> cfKeySize = CFNumberCreate(nullptr, kCFNumberIntType, &keySize);
+
+ CFReleaser<CFMutableDictionaryRef> attrDict =
+ CFDictionaryCreateMutable(nullptr, 2, &kCFTypeDictionaryKeyCallBacks, nullptr);
+ CFDictionaryAddValue(attrDict.get(), kSecAttrKeyType, getAsymKeyType(keyType));
+ CFDictionaryAddValue(attrDict.get(), kSecAttrKeySizeInBits, cfKeySize.get());
+
+ KeyRefOsx publicKey, privateKey;
+ // C-style cast is used as per Apple convention
+ OSStatus res = SecKeyGeneratePair((CFDictionaryRef)attrDict.get(), &publicKey.get(), &privateKey.get());
+
+ BOOST_ASSERT(privateKey != nullptr);
+
+ publicKey.retain();
+ privateKey.retain();
+
+ BOOST_ASSERT(privateKey != nullptr);
+
+ if (res != errSecSuccess) {
+ if (res == errSecAuthFailed) {
+ BOOST_THROW_EXCEPTION(Error("Fail to unlock the keychain"));
+ }
+ else {
+ BOOST_THROW_EXCEPTION(Error("Fail to create a key pair"));
+ }
+ }
+
+ unique_ptr<KeyHandle> keyHandle = make_unique<KeyHandleOsx>(*this, privateKey.get());
+ setKeyName(*keyHandle, identityName, params);
+
+ SecKeychainAttribute attrs[1]; // maximum number of attributes
+ SecKeychainAttributeList attrList = { 0, attrs };
+ std::string keyUri = keyHandle->getKeyName().toUri();
+ {
+ attrs[attrList.count].tag = kSecKeyPrintName;
+ attrs[attrList.count].length = keyUri.size();
+ attrs[attrList.count].data = const_cast<char*>(keyUri.data());
+ attrList.count++;
+ }
+
+ SecKeychainItemModifyAttributesAndData((SecKeychainItemRef)privateKey.get(), &attrList, 0, nullptr);
+ SecKeychainItemModifyAttributesAndData((SecKeychainItemRef)publicKey.get(), &attrList, 0, nullptr);
+
+ return keyHandle;
+}
+
+void
+BackEndOsx::doDeleteKey(const Name& keyName)
+{
+ CFReleaser<CFStringRef> keyLabel = CFStringCreateWithCString(nullptr, keyName.toUri().c_str(), kCFStringEncodingUTF8);
+
+ CFReleaser<CFMutableDictionaryRef> searchDict =
+ CFDictionaryCreateMutable(nullptr, 5, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+
+ CFDictionaryAddValue(searchDict.get(), kSecClass, kSecClassKey);
+ CFDictionaryAddValue(searchDict.get(), kSecAttrLabel, keyLabel.get());
+ CFDictionaryAddValue(searchDict.get(), kSecMatchLimit, kSecMatchLimitAll);
+ OSStatus res = SecItemDelete(searchDict.get());
+
+ if (res != errSecSuccess) {
+ if (res == errSecAuthFailed) {
+ BOOST_THROW_EXCEPTION(Error("Fail to unlock the keychain"));
+ }
+ else if (res != errSecItemNotFound) {
+ BOOST_THROW_EXCEPTION(Error("Fail to delete a key pair"));
+ }
+ }
+}
+
+ConstBufferPtr
+BackEndOsx::doExportKey(const Name& keyName, const char* pw, size_t pwLen)
+{
+ CFReleaser<SecKeychainItemRef> privateKey;
+
+ try {
+ privateKey = m_impl->getKey(keyName);
+ }
+ catch (const std::domain_error&) {
+ BOOST_THROW_EXCEPTION(Tpm::Error("Private key does not exist in OSX Keychain"));
+ }
+
+ CFReleaser<CFDataRef> exportedKey;
+ SecItemImportExportKeyParameters keyParams;
+ memset(&keyParams, 0, sizeof(keyParams));
+ CFReleaser<CFStringRef> passphrase =
+ CFStringCreateWithBytes(0, reinterpret_cast<const uint8_t*>(pw), pwLen, kCFStringEncodingUTF8, false);
+ keyParams.passphrase = passphrase.get();
+ OSStatus res = SecItemExport(privateKey.get(), // secItemOrArray
+ kSecFormatWrappedPKCS8, // outputFormat
+ 0, // flags
+ &keyParams, // keyParams
+ &exportedKey.get()); // exportedData
+
+ if (res != errSecSuccess) {
+ if (res == errSecAuthFailed) {
+ BOOST_THROW_EXCEPTION(Error("Fail to unlock the keychain"));
+ }
+ else {
+ BOOST_THROW_EXCEPTION(Error("Fail to export private key"));
+ }
+ }
+
+ return make_shared<Buffer>(CFDataGetBytePtr(exportedKey.get()), CFDataGetLength(exportedKey.get()));
+}
+
+void
+BackEndOsx::doImportKey(const Name& keyName, const uint8_t* buf, size_t size,
+ const char* pw, size_t pwLen)
+{
+ CFReleaser<CFDataRef> importedKey = CFDataCreateWithBytesNoCopy(nullptr, buf, size, kCFAllocatorNull);
+
+ SecExternalFormat externalFormat = kSecFormatWrappedPKCS8;
+ SecExternalItemType externalType = kSecItemTypePrivateKey;
+
+ CFReleaser<CFStringRef> keyLabel = CFStringCreateWithCString(nullptr, keyName.toUri().c_str(), kCFStringEncodingUTF8);
+ CFReleaser<CFStringRef> passphrase =
+ CFStringCreateWithBytes(nullptr, reinterpret_cast<const uint8_t*>(pw), pwLen, kCFStringEncodingUTF8, false);
+ CFReleaser<SecAccessRef> access;
+ SecAccessCreate(keyLabel.get(), nullptr, &access.get());
+
+ CFArrayRef attributes = nullptr;
+
+ const SecItemImportExportKeyParameters keyParams{
+ SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION, // version
+ 0, // flags
+ passphrase.get(), // passphrase
+ nullptr, // alert title
+ nullptr, // alert prompt
+ access.get(), // access ref
+ nullptr, // key usage
+ attributes // key attributes
+ };
+
+ 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
+
+ if (res != errSecSuccess) {
+ if (res == errSecAuthFailed) {
+ BOOST_THROW_EXCEPTION(Error("Fail to unlock the keychain"));
+ }
+ else {
+ BOOST_THROW_EXCEPTION(Error("Cannot import the private key"));
+ }
+ }
+
+ // C-style cast is used as per Apple convention
+ SecKeychainItemRef privateKey = (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.c_str());
+ attrList.count++;
+ }
+
+ res = SecKeychainItemModifyAttributesAndData(privateKey, &attrList, 0, nullptr);
+}
+
+} // namespace tpm
+} // namespace security
+} // namespace ndn