| /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ |
| /* |
| * Copyright (c) 2013-2017 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. |
| * |
| * @author Yingdi Yu <http://irl.cs.ucla.edu/~yingdi/> |
| */ |
| |
| #include "sec-tpm-osx.hpp" |
| #include "public-key.hpp" |
| |
| #include "../../encoding/oid.hpp" |
| #include "../../encoding/buffer-stream.hpp" |
| #include "cryptopp.hpp" |
| |
| #include <pwd.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include <boost/lexical_cast.hpp> |
| |
| #include <CoreFoundation/CoreFoundation.h> |
| #include <Security/Security.h> |
| #include <Security/SecRandom.h> |
| #include <CoreServices/CoreServices.h> |
| |
| #include <Security/SecDigestTransform.h> |
| |
| namespace ndn { |
| namespace security { |
| namespace v1 { |
| |
| using std::string; |
| |
| const std::string SecTpmOsx::SCHEME("tpm-osxkeychain"); |
| |
| /** |
| * @brief Helper class to wrap CoreFoundation object pointers |
| * |
| * The class is similar in spirit to shared_ptr, but uses CoreFoundation |
| * mechanisms to retain/release object. |
| * |
| * Original implementation by Christopher Hunt and it was borrowed from |
| * http://www.cocoabuilder.com/archive/cocoa/130776-auto-cfrelease-and.html |
| */ |
| template<class T> |
| class CFReleaser |
| { |
| public: |
| ////////////////////////////// |
| // Construction/destruction // |
| |
| CFReleaser() |
| : m_typeRef(nullptr) |
| { |
| } |
| |
| CFReleaser(const T& typeRef) |
| : m_typeRef(typeRef) |
| { |
| } |
| |
| CFReleaser(const CFReleaser& inReleaser) |
| : m_typeRef(nullptr) |
| { |
| retain(inReleaser.m_typeRef); |
| } |
| |
| CFReleaser& |
| operator=(const T& typeRef) |
| { |
| if (typeRef != m_typeRef) { |
| release(); |
| m_typeRef = typeRef; |
| } |
| return *this; |
| } |
| |
| CFReleaser& |
| operator=(const CFReleaser& inReleaser) |
| { |
| retain(inReleaser.m_typeRef); |
| return *this; |
| } |
| |
| ~CFReleaser() |
| { |
| release(); |
| } |
| |
| //////////// |
| // Access // |
| |
| // operator const T&() const |
| // { |
| // return m_typeRef; |
| // } |
| |
| // operator T&() |
| // { |
| // return m_typeRef; |
| // } |
| |
| const T& |
| get() const |
| { |
| return m_typeRef; |
| } |
| |
| T& |
| get() |
| { |
| return m_typeRef; |
| } |
| |
| /////////////////// |
| // Miscellaneous // |
| |
| void |
| retain(const T& typeRef) |
| { |
| if (typeRef != nullptr) { |
| CFRetain(typeRef); |
| } |
| release(); |
| m_typeRef = typeRef; |
| } |
| |
| void |
| release() |
| { |
| if (m_typeRef != nullptr) { |
| CFRelease(m_typeRef); |
| m_typeRef = nullptr; |
| } |
| } |
| |
| bool |
| operator==(std::nullptr_t) |
| { |
| return get() == nullptr; |
| } |
| |
| bool |
| operator!=(std::nullptr_t) |
| { |
| return get() != nullptr; |
| } |
| |
| private: |
| T m_typeRef; |
| }; |
| |
| |
| class SecTpmOsx::Impl |
| { |
| public: |
| Impl() |
| : m_passwordSet(false) |
| , m_inTerminal(false) |
| { |
| } |
| |
| /** |
| * @brief Convert NDN name of a key to internal name of the key. |
| * |
| * @return the internal key name |
| */ |
| std::string |
| toInternalKeyName(const Name& keyName, KeyClass keyClass); |
| |
| /** |
| * @brief Get key. |
| * |
| * @returns pointer to the key |
| */ |
| CFReleaser<SecKeychainItemRef> |
| getKey(const Name& keyName, KeyClass keyClass); |
| |
| /** |
| * @brief Convert keyType to MAC OS symmetric key key type |
| * |
| * @returns MAC OS key type |
| */ |
| CFTypeRef |
| getSymKeyType(KeyType keyType); |
| |
| /** |
| * @brief Convert keyType to MAC OS asymmetirc key type |
| * |
| * @returns MAC OS key type |
| */ |
| CFTypeRef |
| getAsymKeyType(KeyType keyType); |
| |
| /** |
| * @brief Convert keyClass to MAC OS key class |
| * |
| * @returns MAC OS key class |
| */ |
| CFTypeRef |
| getKeyClass(KeyClass keyClass); |
| |
| /** |
| * @brief Convert digestAlgo to MAC OS algorithm id |
| * |
| * @returns MAC OS algorithm id |
| */ |
| CFStringRef |
| getDigestAlgorithm(DigestAlgorithm digestAlgo); |
| |
| /** |
| * @brief Get the digest size of the corresponding algorithm |
| * |
| * @return digest size |
| */ |
| long |
| getDigestSize(DigestAlgorithm digestAlgo); |
| |
| /////////////////////////////////////////////// |
| // everything here is public, including data // |
| /////////////////////////////////////////////// |
| public: |
| SecKeychainRef m_keyChainRef; |
| bool m_passwordSet; |
| string m_password; |
| bool m_inTerminal; |
| }; |
| |
| SecTpmOsx::SecTpmOsx(const std::string& location) |
| : SecTpm(location) |
| , m_impl(new Impl) |
| { |
| // TODO: add location support |
| if (m_impl->m_inTerminal) |
| SecKeychainSetUserInteractionAllowed(false); |
| else |
| SecKeychainSetUserInteractionAllowed(true); |
| |
| OSStatus res = SecKeychainCopyDefault(&m_impl->m_keyChainRef); |
| |
| if (res == errSecNoDefaultKeychain) //If no default key chain, create one. |
| BOOST_THROW_EXCEPTION(Error("No default keychain, please create one first")); |
| } |
| |
| SecTpmOsx::~SecTpmOsx() |
| { |
| } |
| |
| void |
| SecTpmOsx::setTpmPassword(const uint8_t* password, size_t passwordLength) |
| { |
| m_impl->m_passwordSet = true; |
| std::fill(m_impl->m_password.begin(), m_impl->m_password.end(), 0); |
| m_impl->m_password.clear(); |
| m_impl->m_password.append(reinterpret_cast<const char*>(password), passwordLength); |
| } |
| |
| void |
| SecTpmOsx::resetTpmPassword() |
| { |
| m_impl->m_passwordSet = false; |
| std::fill(m_impl->m_password.begin(), m_impl->m_password.end(), 0); |
| m_impl->m_password.clear(); |
| } |
| |
| void |
| SecTpmOsx::setInTerminal(bool inTerminal) |
| { |
| m_impl->m_inTerminal = inTerminal; |
| if (inTerminal) |
| SecKeychainSetUserInteractionAllowed(false); |
| else |
| SecKeychainSetUserInteractionAllowed(true); |
| } |
| |
| bool |
| SecTpmOsx::getInTerminal() const |
| { |
| return m_impl->m_inTerminal; |
| } |
| |
| bool |
| SecTpmOsx::isLocked() |
| { |
| SecKeychainStatus keychainStatus; |
| |
| OSStatus res = SecKeychainGetStatus(m_impl->m_keyChainRef, &keychainStatus); |
| if (res != errSecSuccess) |
| return true; |
| else |
| return ((kSecUnlockStateStatus & keychainStatus) == 0); |
| } |
| |
| bool |
| SecTpmOsx::unlockTpm(const char* password, size_t passwordLength, bool usePassword) |
| { |
| OSStatus res; |
| |
| // If the default key chain is already unlocked, return immediately. |
| if (!isLocked()) |
| return true; |
| |
| // If the default key chain is locked, unlock the key chain. |
| if (usePassword) { |
| // Use the supplied password. |
| res = SecKeychainUnlock(m_impl->m_keyChainRef, |
| passwordLength, |
| password, |
| true); |
| } |
| else if (m_impl->m_passwordSet) { |
| // If no password supplied, then use the configured password if exists. |
| SecKeychainUnlock(m_impl->m_keyChainRef, |
| m_impl->m_password.size(), |
| m_impl->m_password.c_str(), |
| true); |
| } |
| #ifdef NDN_CXX_HAVE_GETPASS |
| else if (m_impl->m_inTerminal) { |
| // If no configured password, get password from terminal if inTerminal set. |
| bool isLocked = true; |
| const char* fmt = "Password to unlock the default keychain: "; |
| int count = 0; |
| |
| while (isLocked) { |
| if (count > 2) |
| break; |
| |
| char* getPassword = nullptr; |
| getPassword = getpass(fmt); |
| count++; |
| |
| if (!getPassword) |
| continue; |
| |
| res = SecKeychainUnlock(m_impl->m_keyChainRef, |
| strlen(getPassword), |
| getPassword, |
| true); |
| |
| memset(getPassword, 0, strlen(getPassword)); |
| |
| if (res == errSecSuccess) |
| break; |
| } |
| } |
| #endif // NDN_CXX_HAVE_GETPASS |
| else { |
| // If inTerminal is not set, get the password from GUI. |
| SecKeychainUnlock(m_impl->m_keyChainRef, 0, nullptr, false); |
| } |
| |
| return !isLocked(); |
| } |
| |
| void |
| SecTpmOsx::generateKeyPairInTpmInternal(const Name& keyName, |
| const KeyParams& params, |
| bool needRetry) |
| { |
| |
| if (doesKeyExistInTpm(keyName, KeyClass::PUBLIC)) { |
| BOOST_THROW_EXCEPTION(Error("keyName already exists")); |
| } |
| |
| string keyNameUri = m_impl->toInternalKeyName(keyName, KeyClass::PUBLIC); |
| |
| CFReleaser<CFStringRef> keyLabel = |
| CFStringCreateWithCString(0, |
| keyNameUri.c_str(), |
| kCFStringEncodingUTF8); |
| |
| CFReleaser<CFMutableDictionaryRef> attrDict = |
| CFDictionaryCreateMutable(0, |
| 3, |
| &kCFTypeDictionaryKeyCallBacks, |
| 0); |
| |
| KeyType keyType = params.getKeyType(); |
| uint32_t keySize = 0; |
| switch (keyType) { |
| case KeyType::RSA: { |
| const RsaKeyParams& rsaParams = static_cast<const RsaKeyParams&>(params); |
| keySize = rsaParams.getKeySize(); |
| break; |
| } |
| |
| case KeyType::EC: { |
| const EcKeyParams& ecParams = static_cast<const EcKeyParams&>(params); |
| keySize = ecParams.getKeySize(); |
| break; |
| } |
| |
| default: |
| BOOST_THROW_EXCEPTION(Error("Fail to create a key pair: Unsupported key type")); |
| } |
| |
| CFReleaser<CFNumberRef> cfKeySize = CFNumberCreate(0, kCFNumberIntType, &keySize); |
| |
| CFDictionaryAddValue(attrDict.get(), kSecAttrKeyType, m_impl->getAsymKeyType(keyType)); |
| CFDictionaryAddValue(attrDict.get(), kSecAttrKeySizeInBits, cfKeySize.get()); |
| CFDictionaryAddValue(attrDict.get(), kSecAttrLabel, keyLabel.get()); |
| |
| CFReleaser<SecKeyRef> publicKey, privateKey; |
| // C-style cast is used as per Apple convention |
| OSStatus res = SecKeyGeneratePair((CFDictionaryRef)attrDict.get(), |
| &publicKey.get(), &privateKey.get()); |
| |
| if (res == errSecSuccess) { |
| return; |
| } |
| |
| if (res == errSecAuthFailed && !needRetry) { |
| if (unlockTpm(nullptr, 0, false)) |
| generateKeyPairInTpmInternal(keyName, params, true); |
| else |
| BOOST_THROW_EXCEPTION(Error("Fail to unlock the keychain")); |
| } |
| else { |
| BOOST_THROW_EXCEPTION(Error("Fail to create a key pair")); |
| } |
| } |
| |
| void |
| SecTpmOsx::deleteKeyPairInTpmInternal(const Name& keyName, bool needRetry) |
| { |
| CFReleaser<CFStringRef> keyLabel = |
| CFStringCreateWithCString(0, |
| keyName.toUri().c_str(), |
| kCFStringEncodingUTF8); |
| |
| CFReleaser<CFMutableDictionaryRef> searchDict = |
| CFDictionaryCreateMutable(0, 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) |
| return; |
| |
| if (res == errSecAuthFailed && !needRetry) { |
| if (unlockTpm(nullptr, 0, false)) |
| deleteKeyPairInTpmInternal(keyName, true); |
| } |
| } |
| |
| void |
| SecTpmOsx::generateSymmetricKeyInTpm(const Name& keyName, const KeyParams& params) |
| { |
| BOOST_THROW_EXCEPTION(Error("SecTpmOsx::generateSymmetricKeyInTpm is not supported")); |
| // if (doesKeyExistInTpm(keyName, KeyClass::SYMMETRIC)) |
| // throw Error("keyName has existed!"); |
| |
| // string keyNameUri = m_impl->toInternalKeyName(keyName, KeyClass::SYMMETRIC); |
| |
| // CFReleaser<CFMutableDictionaryRef> attrDict = |
| // CFDictionaryCreateMutable(kCFAllocatorDefault, |
| // 0, |
| // &kCFTypeDictionaryKeyCallBacks, |
| // &kCFTypeDictionaryValueCallBacks); |
| |
| // CFReleaser<CFStringRef> keyLabel = |
| // CFStringCreateWithCString(0, |
| // keyNameUri.c_str(), |
| // kCFStringEncodingUTF8); |
| |
| // CFReleaser<CFNumberRef> cfKeySize = CFNumberCreate(0, kCFNumberIntType, &keySize); |
| |
| // CFDictionaryAddValue(attrDict.get(), kSecAttrKeyType, m_impl->getSymKeyType(keyType)); |
| // CFDictionaryAddValue(attrDict.get(), kSecAttrKeySizeInBits, cfKeySize.get()); |
| // CFDictionaryAddValue(attrDict.get(), kSecAttrIsPermanent, kCFBooleanTrue); |
| // CFDictionaryAddValue(attrDict.get(), kSecAttrLabel, keyLabel.get()); |
| |
| // CFErrorRef error = 0; |
| |
| // SecKeyRef symmetricKey = SecKeyGenerateSymmetric(attrDict, &error); |
| |
| // if (error) |
| // throw Error("Fail to create a symmetric key"); |
| } |
| |
| shared_ptr<PublicKey> |
| SecTpmOsx::getPublicKeyFromTpm(const Name& keyName) |
| { |
| CFReleaser<SecKeychainItemRef> publicKey = m_impl->getKey(keyName, KeyClass::PUBLIC); |
| if (publicKey == nullptr) { |
| BOOST_THROW_EXCEPTION(Error("Requested public key [" + keyName.toUri() + "] does not exist " |
| "in OSX Keychain")); |
| } |
| |
| CFReleaser<CFDataRef> exportedKey; |
| OSStatus res = SecItemExport(publicKey.get(), |
| kSecFormatOpenSSL, |
| 0, |
| nullptr, |
| &exportedKey.get()); |
| if (res != errSecSuccess) { |
| BOOST_THROW_EXCEPTION(Error("Cannot export requested public key from OSX Keychain")); |
| } |
| |
| shared_ptr<PublicKey> key = make_shared<PublicKey>(CFDataGetBytePtr(exportedKey.get()), |
| CFDataGetLength(exportedKey.get())); |
| return key; |
| } |
| |
| std::string |
| SecTpmOsx::getScheme() |
| { |
| return SCHEME; |
| } |
| |
| ConstBufferPtr |
| SecTpmOsx::exportPrivateKeyPkcs8FromTpmInternal(const Name& keyName, bool needRetry) |
| { |
| using namespace CryptoPP; |
| |
| CFReleaser<SecKeychainItemRef> privateKey = m_impl->getKey(keyName, KeyClass::PRIVATE); |
| if (privateKey == nullptr) { |
| /// @todo Can this happen because of keychain is locked? |
| BOOST_THROW_EXCEPTION(Error("Private key [" + keyName.toUri() + "] does not exist " |
| "in OSX Keychain")); |
| } |
| |
| shared_ptr<PublicKey> publicKey = getPublicKeyFromTpm(keyName); |
| |
| CFReleaser<CFDataRef> exportedKey; |
| OSStatus res = SecItemExport(privateKey.get(), |
| kSecFormatOpenSSL, |
| 0, |
| nullptr, |
| &exportedKey.get()); |
| |
| if (res != errSecSuccess) { |
| if (res == errSecAuthFailed && !needRetry) { |
| if (unlockTpm(nullptr, 0, false)) |
| return exportPrivateKeyPkcs8FromTpmInternal(keyName, true); |
| else |
| return nullptr; |
| } |
| else |
| return nullptr; |
| } |
| |
| uint32_t version = 0; |
| Oid algorithm; |
| bool hasParameters = false; |
| Oid algorithmParameter; |
| switch (publicKey->getKeyType()) { |
| case KeyType::RSA: { |
| algorithm = oid::RSA; // "RSA encryption" |
| hasParameters = false; |
| break; |
| } |
| |
| case KeyType::EC: { |
| // "ECDSA encryption" |
| StringSource src(publicKey->get().buf(), publicKey->get().size(), true); |
| BERSequenceDecoder subjectPublicKeyInfo(src); |
| { |
| BERSequenceDecoder algorithmInfo(subjectPublicKeyInfo); |
| { |
| algorithm.decode(algorithmInfo); |
| algorithmParameter.decode(algorithmInfo); |
| } |
| } |
| hasParameters = true; |
| break; |
| } |
| |
| default: |
| BOOST_THROW_EXCEPTION(Error("Unsupported key type" + |
| boost::lexical_cast<std::string>(publicKey->getKeyType()))); |
| } |
| |
| OBufferStream pkcs8Os; |
| FileSink sink(pkcs8Os); |
| |
| SecByteBlock rawKeyBits; |
| // PrivateKeyInfo ::= SEQUENCE { |
| // version INTEGER, |
| // privateKeyAlgorithm SEQUENCE, |
| // privateKey OCTECT STRING} |
| DERSequenceEncoder privateKeyInfo(sink); |
| { |
| DEREncodeUnsigned<uint32_t>(privateKeyInfo, version, INTEGER); |
| DERSequenceEncoder privateKeyAlgorithm(privateKeyInfo); |
| { |
| algorithm.encode(privateKeyAlgorithm); |
| if (hasParameters) |
| algorithmParameter.encode(privateKeyAlgorithm); |
| else |
| DEREncodeNull(privateKeyAlgorithm); |
| } |
| privateKeyAlgorithm.MessageEnd(); |
| DEREncodeOctetString(privateKeyInfo, |
| CFDataGetBytePtr(exportedKey.get()), |
| CFDataGetLength(exportedKey.get())); |
| } |
| privateKeyInfo.MessageEnd(); |
| |
| return pkcs8Os.buf(); |
| } |
| |
| #ifdef __GNUC__ |
| #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) |
| #pragma GCC diagnostic push |
| #endif // __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) |
| #pragma GCC diagnostic ignored "-Wdeprecated-declarations" |
| #endif // __GNUC__ |
| |
| bool |
| SecTpmOsx::importPrivateKeyPkcs8IntoTpmInternal(const Name& keyName, |
| const uint8_t* buf, size_t size, |
| bool needRetry) |
| { |
| using namespace CryptoPP; |
| |
| StringSource privateKeySource(buf, size, true); |
| SecByteBlock rawKeyBits; |
| // PrivateKeyInfo ::= SEQUENCE { |
| // INTEGER, |
| // SEQUENCE, |
| // OCTECT STRING} |
| BERSequenceDecoder privateKeyInfo(privateKeySource); |
| { |
| uint32_t versionNum; |
| BERDecodeUnsigned<uint32_t>(privateKeyInfo, versionNum, INTEGER); |
| BERSequenceDecoder sequenceDecoder(privateKeyInfo); |
| { |
| Oid keyTypeOid; |
| keyTypeOid.decode(sequenceDecoder); |
| |
| if (keyTypeOid == oid::RSA) |
| BERDecodeNull(sequenceDecoder); |
| else if (keyTypeOid == oid::ECDSA) { |
| Oid parameterOid; |
| parameterOid.decode(sequenceDecoder); |
| } |
| else |
| return false; // Unsupported key type; |
| } |
| BERDecodeOctetString(privateKeyInfo, rawKeyBits); |
| } |
| privateKeyInfo.MessageEnd(); |
| |
| CFReleaser<CFDataRef> importedKey = CFDataCreateWithBytesNoCopy(0, |
| rawKeyBits.BytePtr(), |
| rawKeyBits.size(), |
| kCFAllocatorNull); |
| |
| SecExternalFormat externalFormat = kSecFormatOpenSSL; |
| SecExternalItemType externalType = kSecItemTypePrivateKey; |
| SecKeyImportExportParameters keyParams; |
| memset(&keyParams, 0, sizeof(keyParams)); |
| keyParams.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION; |
| keyParams.keyAttributes = CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_PERMANENT; |
| CFReleaser<SecAccessRef> access; |
| CFReleaser<CFStringRef> keyLabel = CFStringCreateWithCString(0, |
| keyName.toUri().c_str(), |
| kCFStringEncodingUTF8); |
| SecAccessCreate(keyLabel.get(), 0, &access.get()); |
| keyParams.accessRef = access.get(); |
| CFReleaser<CFArrayRef> outItems; |
| |
| #ifdef __clang__ |
| #pragma clang diagnostic push |
| #pragma clang diagnostic ignored "-Wdeprecated-declarations" |
| #endif // __clang__ |
| |
| OSStatus res = SecKeychainItemImport(importedKey.get(), |
| 0, |
| &externalFormat, |
| &externalType, |
| 0, |
| &keyParams, |
| m_impl->m_keyChainRef, |
| &outItems.get()); |
| |
| #ifdef __clang__ |
| #pragma clang diagnostic pop |
| #endif // __clang__ |
| |
| if (res != errSecSuccess) { |
| if (res == errSecAuthFailed && !needRetry) { |
| if (unlockTpm(nullptr, 0, false)) |
| return importPrivateKeyPkcs8IntoTpmInternal(keyName, buf, size, true); |
| else |
| return false; |
| } |
| else |
| return false; |
| } |
| |
| // 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}; |
| 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); |
| |
| if (res != errSecSuccess) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) |
| #pragma GCC diagnostic pop |
| #endif // __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) |
| |
| bool |
| SecTpmOsx::importPublicKeyPkcs1IntoTpm(const Name& keyName, const uint8_t* buf, size_t size) |
| { |
| CFReleaser<CFDataRef> importedKey = CFDataCreateWithBytesNoCopy(0, |
| buf, |
| size, |
| kCFAllocatorNull); |
| |
| SecExternalFormat externalFormat = kSecFormatOpenSSL; |
| SecExternalItemType externalType = kSecItemTypePublicKey; |
| CFReleaser<CFArrayRef> outItems; |
| |
| OSStatus res = SecItemImport(importedKey.get(), |
| 0, |
| &externalFormat, |
| &externalType, |
| 0, |
| 0, |
| m_impl->m_keyChainRef, |
| &outItems.get()); |
| |
| if (res != errSecSuccess) |
| return false; |
| |
| // C-style cast is used as per Apple convention |
| SecKeychainItemRef publicKey = (SecKeychainItemRef)CFArrayGetValueAtIndex(outItems.get(), 0); |
| SecKeychainAttribute attrs[1]; // maximum number of attributes |
| SecKeychainAttributeList attrList = { 0, attrs }; |
| 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(publicKey, |
| &attrList, |
| 0, |
| 0); |
| |
| if (res != errSecSuccess) |
| return false; |
| |
| return true; |
| } |
| |
| Block |
| SecTpmOsx::signInTpmInternal(const uint8_t* data, size_t dataLength, |
| const Name& keyName, DigestAlgorithm digestAlgorithm, bool needRetry) |
| { |
| CFReleaser<CFDataRef> dataRef = CFDataCreateWithBytesNoCopy(0, |
| data, |
| dataLength, |
| kCFAllocatorNull); |
| |
| CFReleaser<SecKeychainItemRef> privateKey = m_impl->getKey(keyName, KeyClass::PRIVATE); |
| if (privateKey == nullptr) { |
| BOOST_THROW_EXCEPTION(Error("Private key [" + keyName.toUri() + "] does not exist " |
| "in OSX Keychain")); |
| } |
| |
| CFReleaser<CFErrorRef> error; |
| // C-style cast is used as per Apple convention |
| CFReleaser<SecTransformRef> signer = SecSignTransformCreate((SecKeyRef)privateKey.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, |
| m_impl->getDigestAlgorithm(digestAlgorithm), |
| &error.get()); |
| if (error != nullptr) |
| BOOST_THROW_EXCEPTION(Error("Fail to configure digest algorithm of signer")); |
| |
| // Set padding attribute |
| long digestSize = m_impl->getDigestSize(digestAlgorithm); |
| CFReleaser<CFNumberRef> cfDigestSize = CFNumberCreate(0, 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) { |
| if (!needRetry) { |
| if (unlockTpm(nullptr, 0, false)) |
| return signInTpmInternal(data, dataLength, keyName, digestAlgorithm, true); |
| else |
| BOOST_THROW_EXCEPTION(Error("Fail to unlock the keychain")); |
| } |
| else { |
| CFShow(error.get()); |
| BOOST_THROW_EXCEPTION(Error("Fail to sign data")); |
| } |
| } |
| |
| if (signature == nullptr) |
| BOOST_THROW_EXCEPTION(Error("Signature is NULL!\n")); |
| |
| return Block(tlv::SignatureValue, |
| make_shared<Buffer>(CFDataGetBytePtr(signature.get()), |
| CFDataGetLength(signature.get()))); |
| } |
| |
| ConstBufferPtr |
| SecTpmOsx::decryptInTpm(const uint8_t* data, size_t dataLength, const Name& keyName, bool sym) |
| { |
| BOOST_THROW_EXCEPTION(Error("SecTpmOsx::decryptInTpm is not supported")); |
| |
| // KeyClass keyClass; |
| // if (sym) |
| // keyClass = KeyClass::SYMMETRIC; |
| // else |
| // keyClass = KeyClass::PRIVATE; |
| |
| // CFDataRef dataRef = CFDataCreate(0, |
| // reinterpret_cast<const unsigned char*>(data), |
| // dataLength |
| // ); |
| |
| // CFReleaser<SecKeyRef> decryptKey = (SecKeyRef)m_impl->getKey(keyName, keyClass); |
| // if (decryptKey == nullptr) |
| // { |
| // /// @todo Can this happen because of keychain is locked? |
| // throw Error("Decruption key [" + ??? + "] does not exist in OSX Keychain"); |
| // } |
| |
| // CFErrorRef error; |
| // SecTransformRef decrypt = SecDecryptTransformCreate(decryptKey, &error); |
| // if (error) throw Error("Fail to create decrypt"); |
| |
| // Boolean set_res = SecTransformSetAttribute(decrypt, |
| // kSecTransformInputAttributeName, |
| // dataRef, |
| // &error); |
| // if (error) throw Error("Fail to configure decrypt"); |
| |
| // CFDataRef output = (CFDataRef) SecTransformExecute(decrypt, &error); |
| // if (error) |
| // { |
| // CFShow(error); |
| // throw Error("Fail to decrypt data"); |
| // } |
| // if (!output) throw Error("Output is NULL!\n"); |
| |
| // return make_shared<Buffer>(CFDataGetBytePtr(output), CFDataGetLength(output)); |
| } |
| |
| void |
| SecTpmOsx::addAppToAcl(const Name& keyName, KeyClass keyClass, const string& appPath, AclType acl) |
| { |
| if (keyClass == KeyClass::PRIVATE && acl == AclType::PRIVATE) { |
| CFReleaser<SecKeychainItemRef> privateKey = m_impl->getKey(keyName, keyClass); |
| if (privateKey == nullptr) { |
| BOOST_THROW_EXCEPTION(Error("Private key [" + keyName.toUri() + "] does not exist " |
| "in OSX Keychain")); |
| } |
| |
| CFReleaser<SecAccessRef> accRef; |
| SecKeychainItemCopyAccess(privateKey.get(), &accRef.get()); |
| |
| CFReleaser<CFArrayRef> signACL = SecAccessCopyMatchingACLList(accRef.get(), |
| kSecACLAuthorizationSign); |
| |
| // C-style cast is used as per Apple convention |
| SecACLRef aclRef = (SecACLRef)CFArrayGetValueAtIndex(signACL.get(), 0); |
| |
| CFReleaser<CFArrayRef> appList; |
| CFReleaser<CFStringRef> description; |
| SecKeychainPromptSelector promptSelector; |
| SecACLCopyContents(aclRef, |
| &appList.get(), |
| &description.get(), |
| &promptSelector); |
| |
| CFReleaser<CFMutableArrayRef> newAppList = CFArrayCreateMutableCopy(0, |
| 0, |
| appList.get()); |
| |
| CFReleaser<SecTrustedApplicationRef> trustedApp; |
| SecTrustedApplicationCreateFromPath(appPath.c_str(), |
| &trustedApp.get()); |
| |
| CFArrayAppendValue(newAppList.get(), trustedApp.get()); |
| |
| SecACLSetContents(aclRef, |
| newAppList.get(), |
| description.get(), |
| promptSelector); |
| |
| SecKeychainItemSetAccess(privateKey.get(), accRef.get()); |
| } |
| } |
| |
| ConstBufferPtr |
| SecTpmOsx::encryptInTpm(const uint8_t* data, size_t dataLength, const Name& keyName, bool sym) |
| { |
| BOOST_THROW_EXCEPTION(Error("SecTpmOsx::encryptInTpm is not supported")); |
| |
| // KeyClass keyClass; |
| // if (sym) |
| // keyClass = KeyClass::SYMMETRIC; |
| // else |
| // keyClass = KeyClass::PUBLIC; |
| |
| // CFDataRef dataRef = CFDataCreate(0, |
| // reinterpret_cast<const unsigned char*>(data), |
| // dataLength |
| // ); |
| |
| // CFReleaser<SecKeyRef> encryptKey = (SecKeyRef)m_impl->getKey(keyName, keyClass); |
| // if (encryptKey == nullptr) |
| // { |
| // throw Error("Encryption key [" + ???? + "] does not exist in OSX Keychain"); |
| // } |
| |
| // CFErrorRef error; |
| // SecTransformRef encrypt = SecEncryptTransformCreate(encryptKey, &error); |
| // if (error) throw Error("Fail to create encrypt"); |
| |
| // Boolean set_res = SecTransformSetAttribute(encrypt, |
| // kSecTransformInputAttributeName, |
| // dataRef, |
| // &error); |
| // if (error) throw Error("Fail to configure encrypt"); |
| |
| // CFDataRef output = (CFDataRef) SecTransformExecute(encrypt, &error); |
| // if (error) throw Error("Fail to encrypt data"); |
| |
| // if (!output) throw Error("Output is NULL!\n"); |
| |
| // return make_shared<Buffer> (CFDataGetBytePtr(output), CFDataGetLength(output)); |
| } |
| |
| bool |
| SecTpmOsx::doesKeyExistInTpm(const Name& keyName, KeyClass keyClass) |
| { |
| string keyNameUri = m_impl->toInternalKeyName(keyName, keyClass); |
| |
| CFReleaser<CFStringRef> keyLabel = CFStringCreateWithCString(0, |
| keyNameUri.c_str(), |
| kCFStringEncodingUTF8); |
| |
| CFReleaser<CFMutableDictionaryRef> attrDict = |
| CFDictionaryCreateMutable(0, |
| 4, |
| &kCFTypeDictionaryKeyCallBacks, |
| 0); |
| |
| CFDictionaryAddValue(attrDict.get(), kSecClass, kSecClassKey); |
| // CFDictionaryAddValue(attrDict.get(), kSecAttrKeyClass, m_impl->getKeyClass(keyClass)); |
| 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()); |
| |
| if (res == errSecSuccess) |
| return true; |
| else |
| return false; |
| |
| } |
| |
| bool |
| SecTpmOsx::generateRandomBlock(uint8_t* res, size_t size) |
| { |
| return SecRandomCopyBytes(kSecRandomDefault, size, res) == 0; |
| } |
| |
| //////////////////////////////// |
| // OSXPrivateKeyStorage::Impl // |
| //////////////////////////////// |
| |
| CFReleaser<SecKeychainItemRef> |
| SecTpmOsx::Impl::getKey(const Name& keyName, KeyClass keyClass) |
| { |
| string keyNameUri = toInternalKeyName(keyName, keyClass); |
| |
| CFReleaser<CFStringRef> keyLabel = CFStringCreateWithCString(0, |
| keyNameUri.c_str(), |
| kCFStringEncodingUTF8); |
| |
| CFReleaser<CFMutableDictionaryRef> attrDict = |
| CFDictionaryCreateMutable(0, |
| 5, |
| &kCFTypeDictionaryKeyCallBacks, |
| 0); |
| |
| CFDictionaryAddValue(attrDict.get(), kSecClass, kSecClassKey); |
| CFDictionaryAddValue(attrDict.get(), kSecAttrLabel, keyLabel.get()); |
| CFDictionaryAddValue(attrDict.get(), kSecAttrKeyClass, getKeyClass(keyClass)); |
| 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()); |
| |
| if (res != errSecSuccess) |
| return 0; |
| else |
| return keyItem; |
| } |
| |
| string |
| SecTpmOsx::Impl::toInternalKeyName(const Name& keyName, KeyClass keyClass) |
| { |
| string keyUri = keyName.toUri(); |
| |
| if (KeyClass::SYMMETRIC == keyClass) |
| return keyUri + "/symmetric"; |
| else |
| return keyUri; |
| } |
| |
| CFTypeRef |
| SecTpmOsx::Impl::getAsymKeyType(KeyType keyType) |
| { |
| switch (keyType) { |
| case KeyType::RSA: |
| return kSecAttrKeyTypeRSA; |
| case KeyType::EC: |
| return kSecAttrKeyTypeECDSA; |
| default: |
| return 0; |
| } |
| } |
| |
| CFTypeRef |
| SecTpmOsx::Impl::getSymKeyType(KeyType keyType) |
| { |
| switch (keyType) { |
| case KeyType::AES: |
| return kSecAttrKeyTypeAES; |
| default: |
| return 0; |
| } |
| } |
| |
| CFTypeRef |
| SecTpmOsx::Impl::getKeyClass(KeyClass keyClass) |
| { |
| switch (keyClass) { |
| case KeyClass::PRIVATE: |
| return kSecAttrKeyClassPrivate; |
| case KeyClass::PUBLIC: |
| return kSecAttrKeyClassPublic; |
| case KeyClass::SYMMETRIC: |
| return kSecAttrKeyClassSymmetric; |
| default: |
| return 0; |
| } |
| } |
| |
| CFStringRef |
| SecTpmOsx::Impl::getDigestAlgorithm(DigestAlgorithm digestAlgo) |
| { |
| switch (digestAlgo) { |
| case DigestAlgorithm::SHA256: |
| return kSecDigestSHA2; |
| default: |
| return 0; |
| } |
| } |
| |
| long |
| SecTpmOsx::Impl::getDigestSize(DigestAlgorithm digestAlgo) |
| { |
| switch (digestAlgo) { |
| case DigestAlgorithm::SHA256: |
| return 256; |
| default: |
| return -1; |
| } |
| } |
| |
| } // namespace v1 |
| } // namespace security |
| } // namespace ndn |