darwinssl: add support for PKCS#12 files for client authentication
I also documented the fact that the OpenSSL engine also supports them.
This commit is contained in:
10
docs/curl.1
10
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]>"
|
.IP "-E, --cert <certificate[:password]>"
|
||||||
(SSL) Tells curl to use the specified client certificate file when getting a
|
(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
|
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
|
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
|
is the private key and the private certificate concatenated! See \fI--cert\fP
|
||||||
and \fI--key\fP to specify them independently.
|
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.
|
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
|
(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
|
certificate string can either be the name of a certificate/private key in the
|
||||||
user keychain. The private key corresponding to the certificate, and
|
system or user keychain, or the path to a PKCS#12-encoded certificate and
|
||||||
certificate chain (if any), must also be present in the keychain.
|
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.
|
If this option is used several times, the last one will be used.
|
||||||
.IP "--engine <name>"
|
.IP "--engine <name>"
|
||||||
|
|||||||
@@ -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
|
.SH SSL and SECURITY OPTIONS
|
||||||
.IP CURLOPT_SSLCERT
|
.IP CURLOPT_SSLCERT
|
||||||
Pass a pointer to a zero terminated string as parameter. The string should be
|
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
|
the file name of your certificate. The default format is "P12" on Secure
|
||||||
changed with \fICURLOPT_SSLCERTTYPE\fP.
|
Transport and "PEM" on other engines, and can be changed with
|
||||||
|
\fICURLOPT_SSLCERTTYPE\fP.
|
||||||
|
|
||||||
With NSS this can also be the nickname of the certificate you wish to
|
With NSS or Secure Transport, this can also be the nickname of the certificate
|
||||||
authenticate with. If you want to use a file from the current directory, please
|
you wish to authenticate with as it is named in the security database. If you
|
||||||
precede it with "./" prefix, in order to avoid confusion with a nickname.
|
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)
|
|
||||||
.IP CURLOPT_SSLCERTTYPE
|
.IP CURLOPT_SSLCERTTYPE
|
||||||
Pass a pointer to a zero terminated string as parameter. The string should be
|
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
|
the format of your certificate. Supported formats are "PEM" and "DER", except
|
||||||
in 7.9.3)
|
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
|
.IP CURLOPT_SSLKEY
|
||||||
Pass a pointer to a zero terminated string as parameter. The string should be
|
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
|
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
|
(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
|
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
|
.IP CURLOPT_SSLKEYTYPE
|
||||||
Pass a pointer to a zero terminated string as parameter. The string should be
|
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".
|
the format of your private key. Supported formats are "PEM", "DER" and "ENG".
|
||||||
|
|||||||
@@ -819,6 +819,68 @@ static OSStatus CopyIdentityWithLabel(char *label,
|
|||||||
return status;
|
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,
|
static CURLcode darwinssl_connect_step1(struct connectdata *conn,
|
||||||
int sockindex)
|
int sockindex)
|
||||||
{
|
{
|
||||||
@@ -988,9 +1050,27 @@ static CURLcode darwinssl_connect_step1(struct connectdata *conn,
|
|||||||
|
|
||||||
if(data->set.str[STRING_CERT]) {
|
if(data->set.str[STRING_CERT]) {
|
||||||
SecIdentityRef cert_and_key = NULL;
|
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) {
|
if(err == noErr) {
|
||||||
SecCertificateRef cert = NULL;
|
SecCertificateRef cert = NULL;
|
||||||
CFTypeRef certs_c[1];
|
CFTypeRef certs_c[1];
|
||||||
@@ -1027,8 +1107,29 @@ static CURLcode darwinssl_connect_step1(struct connectdata *conn,
|
|||||||
CFRelease(cert_and_key);
|
CFRelease(cert_and_key);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
failf(data, "SSL: Can't find the certificate \"%s\" and its private key "
|
switch(err) {
|
||||||
"in the Keychain.", data->set.str[STRING_CERT]);
|
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;
|
return CURLE_SSL_CERTPROBLEM;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user