mirror of https://github.com/aria2/aria2
AppleTLS: Support credentials via KeyChain fingerprints
parent
89cf6c0468
commit
82a861f8d8
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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::vector<std::string> MessageDigest::getSupportedHashTypes()
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
std::string MessageDigest::getSupportedHashTypeString()
|
||||
{
|
||||
std::string s;
|
||||
for(HashTypeEntry* i = vbegin(hashTypes), *eoi = vend(hashTypes); i != eoi;
|
||||
++i) {
|
||||
if(MessageDigestImpl::supports(i->hashType)) {
|
||||
if(!s.empty()) {
|
||||
s += ", ";
|
||||
}
|
||||
s += i->hashType;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
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)
|
||||
|
|
|
@ -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().
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue