فهرست منبع

AppleTLS: Support credentials via KeyChain fingerprints

Nils Maier 12 سال پیش
والد
کامیت
82a861f8d8
7فایلهای تغییر یافته به همراه291 افزوده شده و 21 حذف شده
  1. 185 2
      src/AppleTLSContext.cc
  2. 8 2
      src/AppleTLSContext.h
  3. 57 1
      src/AppleTLSSession.cc
  4. 20 11
      src/MessageDigest.cc
  5. 5 1
      src/MessageDigest.h
  6. 5 2
      src/MultiUrlRequestInfo.cc
  7. 11 2
      src/OptionHandlerFactory.cc

+ 185 - 2
src/AppleTLSContext.cc

@@ -34,21 +34,111 @@
 /* copyright --> */
 #include "AppleTLSContext.h"
 
+#include <algorithm>
+#include <functional>
+
 #include "LogFactory.h"
 #include "Logger.h"
+#include "MessageDigest.h"
 #include "fmt.h"
 #include "message.h"
+#include "util.h"
+
+namespace {
+  using namespace aria2;
+
+#if defined(__MAC_10_6)
+  static const void *query_keys[] = {
+    kSecClass,
+    kSecReturnRef,
+    kSecMatchPolicy,
+    kSecMatchLimit
+  };
+
+  class cfrelease {
+    const void *ptr_;
+  public:
+    inline cfrelease(const void *ptr) : ptr_(ptr) {}
+    inline ~cfrelease() { if (ptr_) CFRelease(ptr_); }
+  };
+
+  static inline bool isWhitespace(char c)
+  {
+    // 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());
+    return str;
+  }
+
+  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_);
+    }
+  };
+
+  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();
+      hash = util::toHex(hash);
+      return hash == hash_;
+    }
+  };
+
+
+  std::string errToString(OSStatus err)
+  {
+    std::string rv = "Unkown error";
+    CFStringRef cerr = SecCopyErrorMessageString(err, 0);
+    if (cerr) {
+      size_t len = CFStringGetLength(cerr) * 4;
+      char *buf = new char[len];
+      if (CFStringGetCString(cerr, buf, len, kCFStringEncodingUTF8)) {
+        rv = buf;
+      }
+      delete [] buf;
+      CFRelease(cerr);
+    }
+    return rv;
+  }
+
+#endif // defined(__MAC_10_6)
+
+}
 
 namespace aria2 {
 
-TLSContext* TLSContext::make(TLSSessionSide side) {
+TLSContext* TLSContext::make(TLSSessionSide side)
+{
   return new AppleTLSContext(side);
 }
 
+AppleTLSContext::~AppleTLSContext()
+{
+  if (credentials_) {
+    CFRelease(credentials_);
+    credentials_ = 0;
+  }
+}
+
 bool AppleTLSContext::addCredentialFile(const std::string& certfile,
                                         const std::string& keyfile)
 {
-  A2_LOG_WARN("TLS credential files are not supported. Use the KeyChain to manage your certificates.");
+  if (tryAsFingerprint(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.");
   return false;
 }
 
@@ -58,5 +148,98 @@ bool AppleTLSContext::addTrustedCACertFile(const std::string& certfile)
   return false;
 }
 
+SecIdentityRef AppleTLSContext::getCredentials()
+{
+  return credentials_;
+}
+
+bool AppleTLSContext::tryAsFingerprint(const std::string& fingerprint)
+{
+  std::string fp = stripWhitespace(fingerprint);
+  // Verify this is a valid hex representation and normalize.
+  fp = util::toHex(util::fromHex(fp.begin(), fp.end()));
+
+  // Verify this can represent a hash
+  std::vector<std::string> ht = MessageDigest::getSupportedHashTypes();
+  if (std::find_if(ht.begin(), ht.end(), hash_validator(fp)) == ht.end()) {
+    A2_LOG_INFO(fmt("%s is not a fingerprint, invalid hash representation", fingerprint.c_str()));
+    return false;
+  }
+
+#if defined(__MAC_10_6)
+  A2_LOG_DEBUG(fmt("Looking for cert with fingerprint %s", fp.c_str()));
+
+  // Build and run the KeyChain the query.
+  SecPolicyRef policy = SecPolicyCreateSSL(true, 0);
+  if (!policy) {
+    A2_LOG_ERROR("Failed to create SecPolicy");
+    return false;
+  }
+  cfrelease del_policy(policy);
+  const void *query_values[] = {
+    kSecClassIdentity,
+    kCFBooleanTrue,
+    policy,
+    kSecMatchLimitAll
+  };
+  CFDictionaryRef query = CFDictionaryCreate(0, query_keys, query_values,
+                                             4, 0, 0);
+  if (!query) {
+    A2_LOG_ERROR("Failed to create identity query");
+    return false;
+  }
+  cfrelease del_query(query);
+  CFArrayRef identities;
+  OSStatus err = SecItemCopyMatching(query, (CFTypeRef*)&identities);
+  if (err != errSecSuccess) {
+    A2_LOG_ERROR("Query failed: " + errToString(err));
+    return false;
+  }
+
+  // Alrighty, search the fingerprint.
+  const size_t nvals = CFArrayGetCount(identities);
+  for (size_t i = 0; i < nvals; ++i) {
+    SecIdentityRef id = (SecIdentityRef)CFArrayGetValueAtIndex(identities, i);
+    if (!id) {
+      A2_LOG_ERROR("Failed to get a value!");
+      continue;
+    }
+    SecCertificateRef ref = 0;
+    if (SecIdentityCopyCertificate(id, &ref) != errSecSuccess) {
+      A2_LOG_ERROR("Failed to get a certref!");
+      continue;
+    }
+    cfrelease del_ref(ref);
+    CFDataRef data = SecCertificateCopyData(ref);
+    if (!data) {
+      A2_LOG_ERROR("Failed to get a data!");
+      continue;
+    }
+    cfrelease 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.
+    if (std::find_if(ht.begin(), ht.end(), hash_finder(data, fp)) == ht.end()) {
+      continue;
+    }
+    A2_LOG_INFO("Found cert with matching fingerprint");
+    credentials_ = id;
+    CFRetain(id);
+    return true;
+  }
+
+  A2_LOG_ERROR(fmt("Failed to lookup %s in your KeyChain", fingerprint.c_str()));
+  return false;
+
+#else // defined(__MAC_10_6)
+
+  A2_LOG_ERROR("Your system does not support creditials via fingerprints; Upgrade to OSX 10.6 or later");
+  return false;
+
+#endif // defined(__MAC_10_6)
+}
 
 } // namespace aria2

+ 8 - 2
src/AppleTLSContext.h

@@ -50,10 +50,11 @@ class AppleTLSContext : public TLSContext {
 public:
   AppleTLSContext(TLSSessionSide side)
     : side_(side),
-      verifyPeer_(true)
+      verifyPeer_(true),
+      credentials_(0)
   {}
 
-  virtual ~AppleTLSContext() {}
+  virtual ~AppleTLSContext();
 
   // private key `keyfile' must be decrypted.
   virtual bool addCredentialFile(const std::string& certfile,
@@ -80,9 +81,14 @@ public:
     verifyPeer_ = verify;
   }
 
+  SecIdentityRef getCredentials();
+
 private:
   TLSSessionSide side_;
   bool verifyPeer_;
+  SecIdentityRef credentials_;
+
+  bool tryAsFingerprint(const std::string& fingerprint);
 };
 
 } // namespace aria2

+ 57 - 1
src/AppleTLSSession.cc

@@ -39,8 +39,9 @@
 
 #include <CoreFoundation/CoreFoundation.h>
 
-#include "fmt.h"
 #include "LogFactory.h"
+#include "a2functional.h"
+#include "fmt.h"
 
 #define ioErr -36
 #define paramErr -50
@@ -52,6 +53,28 @@ namespace {
   static const SSLProtocol kTLSProtocol12 = (SSLProtocol)(kSSLProtocolAll + 2);
 #endif
 
+#ifndef CIPHER_NO_DHPARAM
+  // Diffie-Hellman params, to seed the engine instead of having it spend up
+  // to 30 seconds on generating them. It should be save to share these. :p
+  // This was generated using: openssl dhparam -outform DER 2048
+  static const uint8_t dhparam[] =
+    "\x30\x82\x01\x08\x02\x82\x01\x01\x00\x97\xea\xd0\x46\xf7\xae\xa7\x76\x80"
+    "\x9c\x74\x56\x98\xd8\x56\x97\x2b\x20\x6c\x77\xe2\x82\xbb\xc8\x84\xbe\xe7"
+    "\x63\xaf\xcc\x30\xd0\x67\x97\x7d\x1b\xab\x59\x30\xa9\x13\x67\x21\xd7\xd4"
+    "\x0e\x46\xcf\xe5\x80\xdf\xc9\xb9\xba\x54\x9b\x46\x2f\x3b\x45\xfc\x2f\xaf"
+    "\xad\xc0\x17\x56\xdd\x52\x42\x57\x45\x70\x14\xe5\xbe\x67\xaa\xde\x69\x75"
+    "\x30\x0d\xf9\xa2\xc4\x63\x4d\x7a\x39\xef\x14\x62\x18\x33\x44\xa1\xf9\xc1"
+    "\x52\xd1\xb6\x72\x21\x98\xf8\xab\x16\x1b\x7b\x37\x65\xe3\xc5\x11\x00\xf6"
+    "\x36\x1f\xd8\x5f\xd8\x9f\x43\xa8\xce\x9d\xbf\x5e\xd6\x2d\xfa\x0a\xc2\x01"
+    "\x54\xc2\xd9\x81\x54\x55\xb5\x26\xf8\x88\x37\xf5\xfe\xe0\xef\x4a\x34\x81"
+    "\xdc\x5a\xb3\x71\x46\x27\xe3\xcd\x24\xf6\x1b\xf1\xe2\x0f\xc2\xa1\x39\x53"
+    "\x5b\xc5\x38\x46\x8e\x67\x4c\xd9\xdd\xe4\x37\x06\x03\x16\xf1\x1d\x7a\xba"
+    "\x2d\xc1\xe4\x03\x1a\x58\xe5\x29\x5a\x29\x06\x69\x61\x7a\xd8\xa9\x05\x9f"
+    "\xc1\xa2\x45\x9c\x17\xad\x52\x69\x33\xdc\x18\x8d\x15\xa6\x5e\xcd\x94\xf4"
+    "\x45\xbb\x9f\xc2\x7b\x85\x00\x61\xb0\x1a\xdc\x3c\x86\xaa\x9f\x5c\x04\xb3"
+    "\x90\x0b\x35\x64\xff\xd9\xe3\xac\xf2\xf2\xeb\x3a\x63\x02\x01\x02";
+#endif // CIPHER_NO_DHPARAM
+
   static inline const char *protoToString(SSLProtocol proto) {
     switch (proto) {
       case kSSLProtocol2:
@@ -309,8 +332,41 @@ AppleTLSSession::AppleTLSSession(AppleTLSContext* ctx)
   if (SSLSetEnabledCiphers(sslCtx_, &enabled[0], enabled.size()) != noErr) {
     A2_LOG_ERROR("AppleTLS: Failed to set enabled ciphers list");
     state_ = st_error;
+    return;
   }
 #endif
+
+  if (ctx->getSide() == TLS_SERVER) {
+    SecIdentityRef creds = ctx->getCredentials();
+    if (!creds) {
+      A2_LOG_ERROR("AppleTLS: No credentials");
+      state_ = st_error;
+      return;
+    }
+    CFArrayRef certs = CFArrayCreate(0, (const void**)&creds, 1, 0);
+    if (!certs) {
+      A2_LOG_ERROR("AppleTLS: Failed to setup credentials");
+      state_ = st_error;
+      return;
+    }
+    auto_delete<const void*> del_certs(certs, CFRelease);
+    lastError_ = SSLSetCertificate(sslCtx_, certs);
+    if (lastError_ != noErr) {
+      A2_LOG_ERROR(fmt("AppleTLS: Failed to set credentials: %s", getLastErrorString().c_str()));
+      state_ = st_error;
+      return;
+    }
+
+#ifndef CIPHER_NO_DHPARAM
+    lastError_ = SSLSetDiffieHellmanParams(sslCtx_, dhparam, sizeof(dhparam));
+    if (lastError_ != noErr) {
+      A2_LOG_WARN(fmt("AppleTLS: Failed to set DHParams: %s", getLastErrorString().c_str()));
+      // Engine will still generate some for us, so this is no problem, except
+      // it will take longer.
+    }
+#endif // CIPHER_NO_DHPARAM
+
+  }
 }
 
 AppleTLSSession::~AppleTLSSession()

+ 20 - 11
src/MessageDigest.cc

@@ -33,6 +33,9 @@
  */
 /* copyright --> */
 #include "MessageDigest.h"
+
+#include <sstream>
+
 #include "MessageDigestImpl.h"
 #include "util.h"
 #include "array_fun.h"
@@ -84,19 +87,24 @@ bool MessageDigest::supports(const std::string& hashType)
   return MessageDigestImpl::supports(hashType);
 }
 
-std::string MessageDigest::getSupportedHashTypeString()
+std::vector<std::string> MessageDigest::getSupportedHashTypes()
 {
-  std::string s;
-  for(HashTypeEntry* i = vbegin(hashTypes), *eoi = vend(hashTypes); i != eoi;
-      ++i) {
-    if(MessageDigestImpl::supports(i->hashType)) {
-      if(!s.empty()) {
-        s += ", ";
+  std::vector<std::string> rv;
+  for (HashTypeEntry *i = vbegin(hashTypes), *eoi = vend(hashTypes);
+       i != eoi; ++i) {
+      if (MessageDigestImpl::supports(i->hashType)) {
+        rv.push_back(i->hashType);
       }
-      s += i->hashType;
-    }
   }
-  return s;
+  return rv;
+}
+
+std::string MessageDigest::getSupportedHashTypeString()
+{
+  std::vector<std::string> ht = getSupportedHashTypes();
+  std::stringstream ss;
+  std::copy(ht.begin(), ht.end(), std::ostream_iterator<std::string>(ss, ", "));
+  return ss.str().substr(ss.str().length() - 2);
 }
 
 size_t MessageDigest::getDigestLength(const std::string& hashType)
@@ -162,9 +170,10 @@ void MessageDigest::reset()
   pImpl_->reset();
 }
 
-void MessageDigest::update(const void* data, size_t length)
+MessageDigest& MessageDigest::update(const void* data, size_t length)
 {
   pImpl_->update(data, length);
+  return *this;
 }
 
 void MessageDigest::digest(unsigned char* md)

+ 5 - 1
src/MessageDigest.h

@@ -38,6 +38,7 @@
 #include "common.h"
 
 #include <string>
+#include <vector>
 
 #include "SharedHandle.h"
 
@@ -68,6 +69,9 @@ public:
   // Returns true if hashType is supported. Otherwise returns false.
   static bool supports(const std::string& hashType);
 
+  // Returns a vector containing supported hash function textual names.
+  static std::vector<std::string> getSupportedHashTypes();
+
   // Returns string containing supported hash function textual names
   // joined with ','.
   static std::string getSupportedHashTypeString();
@@ -93,7 +97,7 @@ public:
   // Resets this object so that it can be reused.
   void reset();
 
-  void update(const void* data, size_t length);
+  MessageDigest& update(const void* data, size_t length);
 
   // Stores digest in the region pointed by md. It is caller's
   // responsibility to allocate memory at least getDigestLength().

+ 5 - 2
src/MultiUrlRequestInfo.cc

@@ -141,8 +141,11 @@ error_code::Value MultiUrlRequestInfo::execute()
 #ifdef ENABLE_SSL
     if(option_->getAsBool(PREF_ENABLE_RPC) &&
        option_->getAsBool(PREF_RPC_SECURE)) {
-      if(!option_->blank(PREF_RPC_CERTIFICATE) &&
-         !option_->blank(PREF_RPC_PRIVATE_KEY)) {
+      if(!option_->blank(PREF_RPC_CERTIFICATE)
+#ifndef HAVE_APPLETLS
+         && !option_->blank(PREF_RPC_PRIVATE_KEY)
+#endif // HAVE_APPLETLS
+         ) {
         // We set server TLS context to the SocketCore before creating
         // DownloadEngine instance.
         SharedHandle<TLSContext> svTlsContext(TLSContext::make(TLS_SERVER));

+ 11 - 2
src/OptionHandlerFactory.cc

@@ -788,11 +788,20 @@ std::vector<OptionHandler*> OptionHandlerFactory::createOptionHandlers()
     handlers.push_back(op);
   }
   {
-    OptionHandler* op(new LocalFilePathOptionHandler
+    OptionHandler* op(
+#ifdef HAVE_APPLETLS
+                      new DefaultOptionHandler
+                      (PREF_RPC_CERTIFICATE,
+                       TEXT_RPC_CERTIFICATE,
+                       NO_DEFAULT_VALUE)
+#else // HAVE_APPLETLS
+                      new LocalFilePathOptionHandler
                       (PREF_RPC_CERTIFICATE,
                        TEXT_RPC_CERTIFICATE,
                        NO_DEFAULT_VALUE,
-                       false));
+                       false)
+#endif
+        );
     op->addTag(TAG_RPC);
     handlers.push_back(op);
   }