diff --git a/src/AppleTLSContext.cc b/src/AppleTLSContext.cc index 0c9fe999..261d1037 100644 --- a/src/AppleTLSContext.cc +++ b/src/AppleTLSContext.cc @@ -36,8 +36,14 @@ #include #include +#include + +#ifdef __MAC_10_6 +#include +#endif #include "LogFactory.h" +#include "BufferedFile.h" #include "Logger.h" #include "MessageDigest.h" #include "fmt.h" @@ -58,11 +64,30 @@ namespace { }; #endif // defined(__MAC_10_7) - class CFReleaser { - const void *ptr_; + template + class CFRef { + T ref_; public: - inline CFReleaser(const void *ptr) : ptr_(ptr) {} - inline ~CFReleaser() { if (ptr_) CFRelease(ptr_); } + CFRef() : ref_(nullptr) {} + CFRef(T ref) : ref_(ref) {} + ~CFRef() { + reset(nullptr); + } + void reset(T ref) { + if (ref_) { + CFRelease(ref_); + } + ref_ = ref; + } + T get() { + return ref_; + } + const T get() const { + return ref_; + } + operator bool() const { + return !!ref_; + } }; static inline bool isWhitespace(char c) @@ -70,6 +95,7 @@ namespace { // Fingerprints are often separated by colons return isspace(c) || c == ':'; } + static inline std::string stripWhitespace(std::string str) { str.erase(std::remove_if(str.begin(), str.end(), isWhitespace), str.end()); @@ -78,7 +104,9 @@ namespace { struct hash_validator { const std::string& hash_; + hash_validator(const std::string& hash) : hash_(hash) {} + inline bool operator()(std::string type) const { return MessageDigest::isValidHash(type, hash_); } @@ -87,9 +115,11 @@ namespace { struct hash_finder { CFDataRef data_; const std::string& hash_; + hash_finder(CFDataRef data, const std::string& hash) : data_(data), hash_(hash) {} + inline bool operator()(std::string type) const { std::string hash = MessageDigest::create(type)->update( CFDataGetBytePtr(data_), CFDataGetLength(data_)).digest(); @@ -102,15 +132,14 @@ namespace { std::string errToString(OSStatus err) { std::string rv = "Unkown error"; - CFStringRef cerr = SecCopyErrorMessageString(err, nullptr); - if (cerr) { - size_t len = CFStringGetLength(cerr) * 4; - auto buf = new char[len]; - if (CFStringGetCString(cerr, buf, len, kCFStringEncodingUTF8)) { - rv = buf; - } - delete [] buf; - CFRelease(cerr); + CFRef cerr(SecCopyErrorMessageString(err, nullptr)); + if (!cerr) { + return rv; + } + size_t len = CFStringGetLength(cerr.get()) * 4; + auto buf = make_unique(len); + if (CFStringGetCString(cerr.get(), buf.get(), len, kCFStringEncodingUTF8)) { + rv = buf.get(); } return rv; } @@ -118,26 +147,28 @@ namespace { bool checkIdentity(const SecIdentityRef id, const std::string& fingerPrint, const std::vector supported) { - SecCertificateRef ref = nullptr; - if (SecIdentityCopyCertificate(id, &ref) != errSecSuccess) { + CFRef ref; + SecCertificateRef raw_ref = nullptr; + if (SecIdentityCopyCertificate(id, &raw_ref) != errSecSuccess) { A2_LOG_ERROR("Failed to get a certref!"); return false; } - CFReleaser del_ref(ref); - CFDataRef data = SecCertificateCopyData(ref); + ref.reset(raw_ref); + + CFRef data(SecCertificateCopyData(ref.get())); if (!data) { A2_LOG_ERROR("Failed to get a data!"); return false; } - CFReleaser del_data(data); // Do try all supported hash algorithms. // Usually the fingerprint would be sha1 or md5, however this is more // future-proof. Also "usually" doesn't cut it; there is already software // using SHA-2 class algos, and SHA-3 is standardized and potential users // cannot be far. - return std::find_if(supported.begin(), supported.end(), - hash_finder(data, fingerPrint)) != supported.end(); + return std::find_if( + supported.begin(), supported.end(), hash_finder(data.get(), fingerPrint)) + != supported.end(); } #endif // defined(__MAC_10_6) @@ -162,11 +193,18 @@ AppleTLSContext::~AppleTLSContext() bool AppleTLSContext::addCredentialFile(const std::string& certfile, const std::string& keyfile) { + if (certfile.empty()) { + return false; + } + if (tryAsFingerprint(certfile)) { return true; } + if (tryAsPKCS12(certfile)) { + return true; + } - A2_LOG_WARN("TLS credential files are not supported. Use the KeyChain to manage your certificates and provide a fingerprint. See the manual."); + A2_LOG_WARN("Only PKCS12/PFX files with a blank password and fingerprints of certificates in your KeyChain are supported. See the manual."); return false; } @@ -198,27 +236,25 @@ bool AppleTLSContext::tryAsFingerprint(const std::string& fingerprint) A2_LOG_DEBUG(fmt("Looking for cert with fingerprint %s", fp.c_str())); // Build and run the KeyChain the query. - auto policy = SecPolicyCreateSSL(true, nullptr); + CFRef policy(SecPolicyCreateSSL(true, nullptr)); if (!policy) { A2_LOG_ERROR("Failed to create SecPolicy"); return false; } - CFReleaser del_policy(policy); const void *query_values[] = { kSecClassIdentity, kCFBooleanTrue, - policy, + policy.get(), kSecMatchLimitAll }; - auto query = CFDictionaryCreate(nullptr, query_keys, query_values, 4, - nullptr, nullptr); + CFRef query(CFDictionaryCreate( + nullptr, query_keys, query_values, 4, nullptr, nullptr)); if (!query) { A2_LOG_ERROR("Failed to create identity query"); return false; } - CFReleaser del_query(query); CFArrayRef identities; - OSStatus err = SecItemCopyMatching(query, (CFTypeRef*)&identities); + OSStatus err = SecItemCopyMatching(query.get(), (CFTypeRef*)&identities); if (err != errSecSuccess) { A2_LOG_ERROR("Query failed: " + errToString(err)); return false; @@ -247,14 +283,15 @@ bool AppleTLSContext::tryAsFingerprint(const std::string& fingerprint) #else // defined(__MAC_10_7) #if defined(__MAC_10_6) - SecIdentitySearchRef search; + CFRef search; + SecIdentitySearchRef raw_search; // Deprecated as of 10.7 - OSStatus err = SecIdentitySearchCreate(0, CSSM_KEYUSE_SIGN, &search); + OSStatus err = SecIdentitySearchCreate(0, CSSM_KEYUSE_SIGN, &raw_search); if (err != errSecSuccess) { A2_LOG_ERROR("Certificate search failed: " + errToString(err)); } - CFReleaser del_search(search); + search.reset(raw_search); SecIdentityRef id; while (SecIdentitySearchCopyNext(search, &id) == errSecSuccess) { @@ -278,4 +315,78 @@ bool AppleTLSContext::tryAsFingerprint(const std::string& fingerprint) #endif // defined(__MAC_10_7) } +bool AppleTLSContext::tryAsPKCS12(const std::string& certfile) +{ +#if defined(__MAC_10_6) + std::stringstream ss; + BufferedFile(certfile.c_str(), "rb").transfer(ss); + auto data = ss.str(); + if (data.empty()) { + A2_LOG_ERROR("Couldn't read certificate file."); + return false; + } + CFRef dataRef(CFDataCreate( + nullptr, (const UInt8*)data.c_str(), data.size())); + if (!dataRef) { + A2_LOG_ERROR("Couldn't allocate PKCS12 data"); + return false; + } + + return tryAsPKCS12(dataRef.get(), "") || tryAsPKCS12(dataRef.get(), nullptr); + +#else // defined(__MAC_10_6) + A2_LOG_INFO("PKCS12 files are only supported in OSX 10.6 or later."); + return false; + +#endif // defined(__MAC_10_6) +} + +bool AppleTLSContext::tryAsPKCS12(CFDataRef data, const char* password) +{ +#if defined(__MAC_10_6) + CFRef passwordRef; + if (password) { + passwordRef.reset(CFStringCreateWithBytes( + nullptr, (const UInt8*)password, strlen(password), + kCFStringEncodingUTF8, false)); + } + const void *keys[] = { kSecImportExportPassphrase }; + const void *values[] = { passwordRef.get() }; + CFRef options(CFDictionaryCreate( + nullptr, keys, values, 1, nullptr, nullptr)); + if (!options) { + A2_LOG_ERROR("Failed to create options"); + return false; + } + + CFRef items; + CFArrayRef raw_items = nullptr; + OSStatus rv = SecPKCS12Import(data, options.get(), &raw_items); + if (rv != errSecSuccess) { + A2_LOG_DEBUG(fmt("Failed to parse PKCS12 data: %s", errToString(rv).c_str())); + return false; + } + items.reset(raw_items); + + CFDictionaryRef idAndTrust = (CFDictionaryRef)CFArrayGetValueAtIndex( + items.get(), 0); + if (!idAndTrust) { + A2_LOG_ERROR("Failed to get identity and trust from PKCS12 data"); + return false; + } + credentials_ = (SecIdentityRef)CFDictionaryGetValue(idAndTrust, kSecImportItemIdentity); + if (!credentials_) { + A2_LOG_ERROR("Failed to get credentials PKCS12 data"); + return false; + } + CFRetain(credentials_); + A2_LOG_INFO("Loaded certificate from file"); + return true; + +#else // defined(__MAC_10_6) + return false; + +#endif // defined(__MAC_10_6) +} + } // namespace aria2 diff --git a/src/AppleTLSContext.h b/src/AppleTLSContext.h index d69968a2..5fb2fd41 100644 --- a/src/AppleTLSContext.h +++ b/src/AppleTLSContext.h @@ -90,6 +90,8 @@ private: SecIdentityRef credentials_; bool tryAsFingerprint(const std::string& fingerprint); + bool tryAsPKCS12(const std::string& certfile); + bool tryAsPKCS12(CFDataRef data, const char* password); }; } // namespace aria2