aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Zitzmann <nickzman@gmail.com>2013-04-27 23:15:07 -0600
committerNick Zitzmann <nickzman@gmail.com>2013-04-27 23:15:07 -0600
commita5c0e209392f39ccbbac6568a9635583a64d31eb (patch)
treeb837914bf246a75a6f187122358f1fd81c91e11f
parent128517649c73cc767a1bbe4e3f5d256797c7a80b (diff)
darwinssl: add TLS crypto authentication
Users using the Secure Transport (darwinssl) back-end can now use a certificate and private key to authenticate with a site using TLS. Because Apple's security system is based around the keychain and does not have any non-public function to create a SecIdentityRef data structure from data loaded outside of the Keychain, the certificate and private key have to be loaded into the Keychain first (using the certtool command line tool or the Security framework's C API) before we can find it and use it.
-rw-r--r--RELEASE-NOTES1
-rw-r--r--docs/curl.12
-rw-r--r--docs/libcurl/curl_easy_setopt.34
-rw-r--r--lib/curl_darwinssl.c162
4 files changed, 167 insertions, 2 deletions
diff --git a/RELEASE-NOTES b/RELEASE-NOTES
index 8686be50e..799b29735 100644
--- a/RELEASE-NOTES
+++ b/RELEASE-NOTES
@@ -15,6 +15,7 @@ Curl and libcurl 7.31.0
This release includes the following changes:
o darwinssl: add TLS session resumption
+ o darwinssl: add TLS crypto authentication
o imap/pop3/smtp: Added support for ;auth=<mech> in the URL
o imap/pop3/smtp: Added support for ;auth=<mech> to CURLOPT_USERPWD
o usercertinmem.c: add example showing user cert in memory
diff --git a/docs/curl.1 b/docs/curl.1
index 1aeeb4650..c4cce98d6 100644
--- a/docs/curl.1
+++ b/docs/curl.1
@@ -390,6 +390,8 @@ NSS PEM PKCS#11 module (libnsspem.so) is available then PEM files may be
loaded. If you want to use a file from the current directory, please precede
it with "./" prefix, in order to avoid confusion with a nickname.
+(iOS and Mac OS X only) If curl is built against Secure Transport, then the certificate string must match the name of a certificate that's in the system or user keychain. The private key corresponding to the certificate, and certificate chain (if any), must also be present in the keychain.
+
If this option is used several times, the last one will be used.
.IP "--engine <name>"
Select the OpenSSL crypto engine to use for cipher
diff --git a/docs/libcurl/curl_easy_setopt.3 b/docs/libcurl/curl_easy_setopt.3
index f828cb7cb..30bdd3a3f 100644
--- a/docs/libcurl/curl_easy_setopt.3
+++ b/docs/libcurl/curl_easy_setopt.3
@@ -2239,6 +2239,8 @@ changed with \fICURLOPT_SSLCERTTYPE\fP.
With NSS this can also be the nickname of the certificate you wish to
authenticate with. If you want to use a file from the current directory, please
precede it with "./" prefix, in order to avoid confusion with a nickname.
+
+(iOS and Mac OS X only) With Secure Transport, this string must match the name of a certificate that's in the system or user keychain. You should encode this string in UTF-8 format in case it contains non-ASCII characters. The private key corresponding to the certificate, and certificate chain (if any), must also be present in the keychain.
.IP CURLOPT_SSLCERTTYPE
Pass a pointer to a zero terminated string as parameter. The string should be
the format of your certificate. Supported formats are "PEM" and "DER". (Added
@@ -2247,6 +2249,8 @@ in 7.9.3)
Pass a pointer to a zero terminated string as parameter. The string should be
the file name of your private key. The default format is "PEM" and can be
changed with \fICURLOPT_SSLKEYTYPE\fP.
+
+(iOS and Mac OS X only) This option is ignored if curl was built against Secure Transport. Secure Transport expects the private key to be already present in the keychain containing the certificate.
.IP CURLOPT_SSLKEYTYPE
Pass a pointer to a zero terminated string as parameter. The string should be
the format of your private key. Supported formats are "PEM", "DER" and "ENG".
diff --git a/lib/curl_darwinssl.c b/lib/curl_darwinssl.c
index 949a1b224..7d39358cb 100644
--- a/lib/curl_darwinssl.c
+++ b/lib/curl_darwinssl.c
@@ -691,6 +691,101 @@ CF_INLINE CFStringRef CopyCertSubject(SecCertificateRef cert)
return server_cert_summary;
}
+#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE))
+static OSStatus CopyIdentityWithLabelOldSchool(char *label,
+ SecIdentityRef *out_c_a_k)
+{
+ OSStatus status = errSecItemNotFound;
+/* The SecKeychainSearch API was deprecated in Lion, and using it will raise
+ deprecation warnings, so let's not compile this unless it's necessary: */
+#if MAC_OS_X_VERSION_MIN_REQUIRED < 1070
+ SecKeychainAttributeList attr_list;
+ SecKeychainAttribute attr;
+ SecKeychainSearchRef search = NULL;
+ SecCertificateRef cert = NULL;
+
+ /* Set up the attribute list: */
+ attr_list.count = 1L;
+ attr_list.attr = &attr;
+
+ /* Set up our lone search criterion: */
+ attr.tag = kSecLabelItemAttr;
+ attr.data = label;
+ attr.length = (UInt32)strlen(label);
+
+ /* Start searching: */
+ status = SecKeychainSearchCreateFromAttributes(NULL,
+ kSecCertificateItemClass,
+ &attr_list,
+ &search);
+ if(status == noErr) {
+ status = SecKeychainSearchCopyNext(search,
+ (SecKeychainItemRef *)&cert);
+ if(status == noErr && cert) {
+ /* If we found a certificate, does it have a private key? */
+ status = SecIdentityCreateWithCertificate(NULL, cert, out_c_a_k);
+ CFRelease(cert);
+ }
+ }
+
+ if(search)
+ CFRelease(search);
+#else
+#pragma unused(label, out_c_a_k)
+#endif /* MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7 */
+ return status;
+}
+#endif /* (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) */
+
+static OSStatus CopyIdentityWithLabel(char *label,
+ SecIdentityRef *out_cert_and_key)
+{
+ OSStatus status = errSecItemNotFound;
+
+#if defined(__MAC_10_6) || defined(__IPHONE_2_0)
+ /* SecItemCopyMatching() was introduced in iOS and Snow Leopard. If it
+ exists, let's use that to find the certificate. */
+ if(SecItemCopyMatching != NULL) {
+ CFTypeRef keys[4];
+ CFTypeRef values[4];
+ CFDictionaryRef query_dict;
+ CFStringRef label_cf = CFStringCreateWithCString(NULL, label,
+ kCFStringEncodingUTF8);
+
+ /* Set up our search criteria and expected results: */
+ values[0] = kSecClassIdentity; /* we want a certificate and a key */
+ keys[0] = kSecClass;
+ values[1] = kCFBooleanTrue; /* we want a reference */
+ keys[1] = kSecReturnRef;
+ values[2] = kSecMatchLimitOne; /* one is enough, thanks */
+ keys[2] = kSecMatchLimit;
+ /* identity searches need a SecPolicyRef in order to work */
+ values[3] = SecPolicyCreateSSL(false, label_cf);
+ keys[3] = kSecMatchPolicy;
+ query_dict = CFDictionaryCreate(NULL, (const void **)keys,
+ (const void **)values, 4L,
+ &kCFCopyStringDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ CFRelease(values[3]);
+ CFRelease(label_cf);
+
+ /* Do we have a match? */
+ status = SecItemCopyMatching(query_dict, (CFTypeRef *)out_cert_and_key);
+ CFRelease(query_dict);
+ }
+ else {
+#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE))
+ /* On Leopard, fall back to SecKeychainSearch. */
+ status = CopyIdentityWithLabelOldSchool(label, out_cert_and_key);
+#endif /* (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) */
+ }
+#elif (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE))
+ /* For developers building on Leopard, we have no choice but to fall back. */
+ status = CopyIdentityWithLabelOldSchool(label, out_cert_and_key);
+#endif /* defined(__MAC_10_6) || defined(__IPHONE_2_0) */
+ return status;
+}
+
static CURLcode darwinssl_connect_step1(struct connectdata *conn,
int sockindex)
{
@@ -841,8 +936,57 @@ static CURLcode darwinssl_connect_step1(struct connectdata *conn,
}
#endif /* defined(__MAC_10_8) || defined(__IPHONE_5_0) */
- /* No need to load certificates here. SecureTransport uses the Keychain
- * (which is also part of the Security framework) to evaluate trust. */
+ if(data->set.str[STRING_KEY]) {
+ infof(data, "WARNING: SSL: CURLOPT_SSLKEY is ignored by Secure "
+ "Transport. The private key must be in the Keychain.");
+ }
+
+ if(data->set.str[STRING_CERT]) {
+ SecIdentityRef cert_and_key = NULL;
+
+ /* User wants to authenticate with a client cert. Look for it: */
+ err = CopyIdentityWithLabel(data->set.str[STRING_CERT], &cert_and_key);
+ if(err == noErr) {
+ SecCertificateRef cert = NULL;
+ CFTypeRef certs_c[1];
+ CFArrayRef certs;
+
+ /* If we found one, print it out: */
+ err = SecIdentityCopyCertificate(cert_and_key, &cert);
+ if(err == noErr) {
+ CFStringRef cert_summary = CopyCertSubject(cert);
+ char cert_summary_c[128];
+
+ if(cert_summary) {
+ memset(cert_summary_c, 0, 128);
+ if(CFStringGetCString(cert_summary,
+ cert_summary_c,
+ 128,
+ kCFStringEncodingUTF8)) {
+ infof(data, "Client certificate: %s\n", cert_summary_c);
+ }
+ CFRelease(cert_summary);
+ CFRelease(cert);
+ }
+ }
+ certs_c[0] = cert_and_key;
+ certs = CFArrayCreate(NULL, (const void **)certs_c, 1L,
+ &kCFTypeArrayCallBacks);
+ err = SSLSetCertificate(connssl->ssl_ctx, certs);
+ if(certs)
+ CFRelease(certs);
+ if(err != noErr) {
+ failf(data, "SSL: SSLSetCertificate() failed: OSStatus %d", err);
+ return CURLE_SSL_CERTPROBLEM;
+ }
+ CFRelease(cert_and_key);
+ }
+ else {
+ failf(data, "SSL: Can't find the certificate \"%s\" and its private key "
+ "in the Keychain.", data->set.str[STRING_CERT]);
+ return CURLE_SSL_CERTPROBLEM;
+ }
+ }
/* SSL always tries to verify the peer, this only says whether it should
* fail to connect if the verification fails, or if it should continue
@@ -1093,6 +1237,20 @@ darwinssl_connect_step2(struct connectdata *conn, int sockindex)
"certificate format");
return CURLE_SSL_CONNECT_ERROR;
+ /* These are all certificate problems with the client: */
+ case errSecAuthFailed:
+ failf(data, "SSL authentication failed");
+ return CURLE_SSL_CONNECT_ERROR;
+ case errSSLPeerHandshakeFail:
+ failf(data, "SSL peer handshake failed, the server most likely "
+ "requires a client certificate to connect");
+ return CURLE_SSL_CONNECT_ERROR;
+ case errSSLPeerUnknownCA:
+ failf(data, "SSL server rejected the client certificate due to "
+ "the certificate being signed by an unknown certificate "
+ "authority");
+ return CURLE_SSL_CONNECT_ERROR;
+
/* This error is raised if the server's cert didn't match the server's
host name: */
case errSSLHostNameMismatch: