aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Zitzmann <nickzman@gmail.com>2013-09-05 18:57:06 -0500
committerNick Zitzmann <nickzman@gmail.com>2013-09-05 18:57:06 -0500
commitd2fe616e7e44a106ac976aaeaa441ad7d8a6df11 (patch)
tree58aa781073e509b882386d827b12c1e882b307d6
parent316ca865e34bac3f64214c046ded06a1fcecc7c6 (diff)
darwinssl: add support for PKCS#12 files for client authentication
I also documented the fact that the OpenSSL engine also supports them.
-rw-r--r--docs/curl.110
-rw-r--r--docs/libcurl/curl_easy_setopt.328
-rw-r--r--lib/curl_darwinssl.c109
3 files changed, 124 insertions, 23 deletions
diff --git a/docs/curl.1 b/docs/curl.1
index a7e2c6044..cee54e017 100644
--- a/docs/curl.1
+++ b/docs/curl.1
@@ -394,7 +394,8 @@ If this option is used several times, the last one will be used.
.IP "-E, --cert <certificate[:password]>"
(SSL) Tells curl to use the specified client certificate file when getting a
file with HTTPS, FTPS or another SSL-based protocol. The certificate must be
-in PEM format. If the optional password isn't specified, it will be queried
+in PKCS#12 format if using Secure Transport, or PEM format if using any other
+engine. If the optional password isn't specified, it will be queried
for on the terminal. Note that this option assumes a \&"certificate" file that
is the private key and the private certificate concatenated! See \fI--cert\fP
and \fI--key\fP to specify them independently.
@@ -410,9 +411,10 @@ recognized as password delimiter. If the nickname contains "\\", it needs to
be escaped as "\\\\" so that it is not recognized as an escape character.
(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.
+certificate string can either be the name of a certificate/private key in the
+system or user keychain, or the path to a PKCS#12-encoded certificate and
+private key. If you want to use a file from the current directory, please
+precede it with "./" prefix, in order to avoid confusion with a nickname.
If this option is used several times, the last one will be used.
.IP "--engine <name>"
diff --git a/docs/libcurl/curl_easy_setopt.3 b/docs/libcurl/curl_easy_setopt.3
index 0478fac54..f4084823d 100644
--- a/docs/libcurl/curl_easy_setopt.3
+++ b/docs/libcurl/curl_easy_setopt.3
@@ -2305,22 +2305,20 @@ timeout is set, the internal default of 60000 will be used. (Added in 7.24.0)
.SH SSL and SECURITY OPTIONS
.IP CURLOPT_SSLCERT
Pass a pointer to a zero terminated string as parameter. The string should be
-the file name of your certificate. The default format is "PEM" and can be
-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. (Added in 7.31.0)
+the file name of your certificate. The default format is "P12" on Secure
+Transport and "PEM" on other engines, and can be changed with
+\fICURLOPT_SSLCERTTYPE\fP.
+
+With NSS or Secure Transport, this can also be the nickname of the certificate
+you wish to authenticate with as it is named in the security database. If you
+want to use a file from the current directory, please precede it with "./"
+prefix, in order to avoid confusion with a nickname.
.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
-in 7.9.3)
+the format of your certificate. Supported formats are "PEM" and "DER", except
+with Secure Transport. OpenSSL (versions 0.9.3 and later) and Secure Transport
+(on iOS 5 or later, or OS X 10.6 or later) also support "P12" for
+PKCS#12-encoded files. (Added in 7.9.3)
.IP CURLOPT_SSLKEY
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
@@ -2328,7 +2326,7 @@ 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.
+the keychain or PKCS#12 file 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 4ecf2d93f..414b7f523 100644
--- a/lib/curl_darwinssl.c
+++ b/lib/curl_darwinssl.c
@@ -819,6 +819,68 @@ static OSStatus CopyIdentityWithLabel(char *label,
return status;
}
+static OSStatus CopyIdentityFromPKCS12File(const char *cPath,
+ const char *cPassword,
+ SecIdentityRef *out_cert_and_key)
+{
+ OSStatus status = errSecItemNotFound;
+ CFURLRef pkcs_url = CFURLCreateFromFileSystemRepresentation(NULL,
+ (const UInt8 *)cPath, strlen(cPath), false);
+ CFStringRef password = cPassword ? CFStringCreateWithCString(NULL,
+ cPassword, kCFStringEncodingUTF8) : NULL;
+ CFDataRef pkcs_data = NULL;
+
+ /* We can import P12 files on iOS or OS X 10.6 or later: */
+#if CURL_BUILD_MAC_10_6 || CURL_BUILD_IOS
+ if(CFURLCreateDataAndPropertiesFromResource(NULL, pkcs_url, &pkcs_data,
+ NULL, NULL, &status)) {
+ const void *cKeys[] = {kSecImportExportPassphrase};
+ const void *cValues[] = {password};
+ CFDictionaryRef options = CFDictionaryCreate(NULL, cKeys, cValues,
+ password ? 1L : 0L, NULL, NULL);
+ CFArrayRef items = NULL;
+
+ /* Here we go: */
+ status = SecPKCS12Import(pkcs_data, options, &items);
+ if(status == noErr) {
+ CFDictionaryRef identity_and_trust = CFArrayGetValueAtIndex(items, 0L);
+ const void *temp_identity = CFDictionaryGetValue(identity_and_trust,
+ kSecImportItemIdentity);
+
+ /* Retain the identity; we don't care about any other data... */
+ CFRetain(temp_identity);
+ *out_cert_and_key = (SecIdentityRef)temp_identity;
+ CFRelease(items);
+ }
+ CFRelease(options);
+ CFRelease(pkcs_data);
+ }
+#endif /* CURL_BUILD_MAC_10_6 || CURL_BUILD_IOS */
+ if(password)
+ CFRelease(password);
+ CFRelease(pkcs_url);
+ return status;
+}
+
+/* This code was borrowed from nss.c, with some modifications:
+ * Determine whether the nickname passed in is a filename that needs to
+ * be loaded as a PEM or a regular NSS nickname.
+ *
+ * returns 1 for a file
+ * returns 0 for not a file
+ */
+CF_INLINE bool is_file(const char *filename)
+{
+ struct_stat st;
+
+ if(filename == NULL)
+ return false;
+
+ if(stat(filename, &st) == 0)
+ return S_ISREG(st.st_mode);
+ return false;
+}
+
static CURLcode darwinssl_connect_step1(struct connectdata *conn,
int sockindex)
{
@@ -988,9 +1050,27 @@ static CURLcode darwinssl_connect_step1(struct connectdata *conn,
if(data->set.str[STRING_CERT]) {
SecIdentityRef cert_and_key = NULL;
+ bool is_cert_file = is_file(data->set.str[STRING_CERT]);
+
+ /* User wants to authenticate with a client cert. Look for it:
+ If we detect that this is a file on disk, then let's load it.
+ Otherwise, assume that the user wants to use an identity loaded
+ from the Keychain. */
+ if(is_cert_file) {
+ if(!data->set.str[STRING_CERT_TYPE])
+ infof(data, "WARNING: SSL: Certificate type not set, assuming "
+ "PKCS#12 format.\n");
+ else if(strncmp(data->set.str[STRING_CERT_TYPE], "P12",
+ strlen(data->set.str[STRING_CERT_TYPE])) != 0)
+ infof(data, "WARNING: SSL: The Security framework only supports "
+ "loading identities that are in PKCS#12 format.\n");
+
+ err = CopyIdentityFromPKCS12File(data->set.str[STRING_CERT],
+ data->set.str[STRING_KEY_PASSWD], &cert_and_key);
+ }
+ else
+ err = CopyIdentityWithLabel(data->set.str[STRING_CERT], &cert_and_key);
- /* 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];
@@ -1027,8 +1107,29 @@ static CURLcode darwinssl_connect_step1(struct connectdata *conn,
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]);
+ switch(err) {
+ case errSecPkcs12VerifyFailure: case errSecAuthFailed:
+ failf(data, "SSL: Incorrect password for the certificate \"%s\" "
+ "and its private key.", data->set.str[STRING_CERT]);
+ break;
+ case errSecDecode: case errSecUnknownFormat:
+ failf(data, "SSL: Couldn't make sense of the data in the "
+ "certificate \"%s\" and its private key.",
+ data->set.str[STRING_CERT]);
+ break;
+ case errSecPassphraseRequired:
+ failf(data, "SSL The certificate \"%s\" requires a password.",
+ data->set.str[STRING_CERT]);
+ break;
+ case errSecItemNotFound:
+ failf(data, "SSL: Can't find the certificate \"%s\" and its private "
+ "key in the Keychain.", data->set.str[STRING_CERT]);
+ break;
+ default:
+ failf(data, "SSL: Can't load the certificate \"%s\" and its private "
+ "key: OSStatus %d", data->set.str[STRING_CERT], err);
+ break;
+ }
return CURLE_SSL_CERTPROBLEM;
}
}