summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNils Maier <maierman@web.de>2013-04-08 04:12:31 +0200
committerNils Maier <maierman@web.de>2013-04-08 06:45:53 +0200
commit82a861f8d88bbe5d978dfa2ebfc184d903981ead (patch)
treef47d0dc583bc9181a571555138587fa5a3b13858
parent89cf6c046833c578e153f71b9ad40817464d0ba5 (diff)
AppleTLS: Support credentials via KeyChain fingerprints
-rw-r--r--src/AppleTLSContext.cc187
-rw-r--r--src/AppleTLSContext.h10
-rw-r--r--src/AppleTLSSession.cc58
-rw-r--r--src/MessageDigest.cc31
-rw-r--r--src/MessageDigest.h6
-rw-r--r--src/MultiUrlRequestInfo.cc7
-rw-r--r--src/OptionHandlerFactory.cc13
7 files changed, 291 insertions, 21 deletions
diff --git a/src/AppleTLSContext.cc b/src/AppleTLSContext.cc
index e301a0ed..3f011ebd 100644
--- a/src/AppleTLSContext.cc
+++ b/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
diff --git a/src/AppleTLSContext.h b/src/AppleTLSContext.h
index 549d432e..d7ac6c25 100644
--- a/src/AppleTLSContext.h
+++ b/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
diff --git a/src/AppleTLSSession.cc b/src/AppleTLSSession.cc
index 6ac36094..385c7df1 100644
--- a/src/AppleTLSSession.cc
+++ b/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()
diff --git a/src/MessageDigest.cc b/src/MessageDigest.cc
index 87010f78..c07c46cb 100644
--- a/src/MessageDigest.cc
+++ b/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)
diff --git a/src/MessageDigest.h b/src/MessageDigest.h
index 6441e748..f5e6ca55 100644
--- a/src/MessageDigest.h
+++ b/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().
diff --git a/src/MultiUrlRequestInfo.cc b/src/MultiUrlRequestInfo.cc
index a8bb2342..b0156aca 100644
--- a/src/MultiUrlRequestInfo.cc
+++ b/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));
diff --git a/src/OptionHandlerFactory.cc b/src/OptionHandlerFactory.cc
index f5cb61fe..835d2388 100644
--- a/src/OptionHandlerFactory.cc
+++ b/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);
}