Просмотр исходного кода

AppleTLS: Implement PKCS12 loading.

Nils Maier 12 лет назад
Родитель
Сommit
a476fb352e
2 измененных файлов с 144 добавлено и 31 удалено
  1. 142 31
      src/AppleTLSContext.cc
  2. 2 0
      src/AppleTLSContext.h

+ 142 - 31
src/AppleTLSContext.cc

@@ -36,8 +36,14 @@
 
 #include <algorithm>
 #include <functional>
+#include <sstream>
+
+#ifdef __MAC_10_6
+#include <Security/SecImportExport.h>
+#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<typename T>
+  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<CFStringRef> cerr(SecCopyErrorMessageString(err, nullptr));
+    if (!cerr) {
+      return rv;
+    }
+    size_t len = CFStringGetLength(cerr.get()) * 4;
+    auto buf = make_unique<char[]>(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<std::string> supported)
   {
-    SecCertificateRef ref = nullptr;
-    if (SecIdentityCopyCertificate(id, &ref) != errSecSuccess) {
+    CFRef<SecCertificateRef> 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<CFDataRef> 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<SecPolicyRef> 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<CFDictionaryRef> 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<SecIdentitySearchRef> 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<CFDataRef> 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<CFStringRef> passwordRef;
+  if (password) {
+    passwordRef.reset(CFStringCreateWithBytes(
+        nullptr, (const UInt8*)password, strlen(password),
+        kCFStringEncodingUTF8, false));
+  }
+  const void *keys[] = { kSecImportExportPassphrase };
+  const void *values[] = { passwordRef.get() };
+  CFRef<CFDictionaryRef> options(CFDictionaryCreate(
+      nullptr, keys, values, 1, nullptr, nullptr));
+  if (!options) {
+    A2_LOG_ERROR("Failed to create options");
+    return false;
+  }
+
+  CFRef<CFArrayRef> 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

+ 2 - 0
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