Files
heimdal/lib/hx509/ks_keychain.c
Nicolas Williams cbe156d927 Use OpenSSL 3.x _only_ and implement RFC 8636
- No more OpenSSL 1.x support
 - Remove 1DES and 3DES
 - Remove NETLOGON, NTLM (client and 'digest' service)
2026-01-18 19:06:16 -06:00

516 lines
13 KiB
C

/*
* Copyright (c) 2007 Kungliga Tekniska Högskolan
* (Royal Institute of Technology, Stockholm, Sweden).
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the Institute nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "hx_locl.h"
#ifdef HAVE_FRAMEWORK_SECURITY
#include <Security/Security.h>
/* Suppress deprecation warnings for SecKeychain APIs - still needed for file-based keychains */
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
/*
* Convert a SecKeyRef to an EVP_PKEY by exporting and re-importing.
* Returns NULL if the key cannot be exported (e.g., hardware-backed keys).
*/
static EVP_PKEY *
seckey_to_evp_pkey(hx509_context context, SecKeyRef seckey)
{
CFDictionaryRef attrs = NULL;
CFDataRef keydata = NULL;
CFErrorRef error = NULL;
EVP_PKEY *pkey = NULL;
CFStringRef keytype;
const unsigned char *p;
attrs = SecKeyCopyAttributes(seckey);
if (attrs == NULL)
return NULL;
keytype = CFDictionaryGetValue(attrs, kSecAttrKeyType);
if (keytype == NULL) {
CFRelease(attrs);
return NULL;
}
/* Try to export the key - this fails for hardware-backed keys */
keydata = SecKeyCopyExternalRepresentation(seckey, &error);
if (keydata == NULL) {
if (error)
CFRelease(error);
CFRelease(attrs);
return NULL;
}
p = CFDataGetBytePtr(keydata);
if (CFEqual(keytype, kSecAttrKeyTypeRSA)) {
/* RSA keys are exported in PKCS#1 format */
pkey = d2i_PrivateKey(EVP_PKEY_RSA, NULL, &p, CFDataGetLength(keydata));
} else if (CFEqual(keytype, kSecAttrKeyTypeECSECPrimeRandom)) {
/* EC keys are exported in ANSI X9.63 format, need to convert */
CFNumberRef keysizeRef;
int keysize = 0;
size_t coord_len = 0;
OSSL_PARAM_BLD *bld = NULL;
OSSL_PARAM *params = NULL;
EVP_PKEY_CTX *pctx = NULL;
BIGNUM *priv = NULL;
const char *group_name = NULL;
keysizeRef = CFDictionaryGetValue(attrs, kSecAttrKeySizeInBits);
if (keysizeRef)
CFNumberGetValue(keysizeRef, kCFNumberIntType, &keysize);
switch (keysize) {
case 256:
group_name = "prime256v1";
coord_len = 32;
break;
case 384:
group_name = "secp384r1";
coord_len = 48;
break;
case 521:
group_name = "secp521r1";
coord_len = 66;
break;
default:
goto ec_out;
}
/*
* X9.63 private key format: 04 || X || Y || D
* where X, Y are public key coordinates and D is private key
*/
if ((size_t)CFDataGetLength(keydata) != 1 + 3 * coord_len)
goto ec_out;
if (p[0] != 0x04)
goto ec_out;
/* Build EC key using OSSL_PARAM */
bld = OSSL_PARAM_BLD_new();
if (bld == NULL)
goto ec_out;
/* Private key is after the public point (04 || X || Y) */
priv = BN_bin2bn(p + 1 + 2 * coord_len, coord_len, NULL);
if (priv == NULL)
goto ec_out;
if (!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_PKEY_PARAM_GROUP_NAME,
group_name, 0) ||
!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_PUB_KEY,
p, 1 + 2 * coord_len) ||
!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_PRIV_KEY, priv))
goto ec_out;
params = OSSL_PARAM_BLD_to_param(bld);
if (params == NULL)
goto ec_out;
pctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL);
if (pctx == NULL)
goto ec_out;
if (EVP_PKEY_fromdata_init(pctx) <= 0 ||
EVP_PKEY_fromdata(pctx, &pkey, EVP_PKEY_KEYPAIR, params) <= 0)
pkey = NULL;
ec_out:
BN_free(priv);
OSSL_PARAM_free(params);
OSSL_PARAM_BLD_free(bld);
EVP_PKEY_CTX_free(pctx);
}
CFRelease(keydata);
CFRelease(attrs);
return pkey;
}
static int
set_private_key(hx509_context context,
SecKeyRef keyRef,
hx509_cert cert)
{
hx509_private_key key;
EVP_PKEY *pkey;
int pkey_type;
int ret;
pkey = seckey_to_evp_pkey(context, keyRef);
if (pkey == NULL) {
/* Key couldn't be exported - likely hardware-backed */
return 0;
}
ret = hx509_private_key_init(&key, NULL, NULL);
if (ret) {
EVP_PKEY_free(pkey);
return ret;
}
/* Assign EVP_PKEY directly */
key->private_key.pkey = pkey;
/* Set default signature algorithm based on key type */
pkey_type = EVP_PKEY_base_id(pkey);
switch (pkey_type) {
case EVP_PKEY_RSA:
key->signature_alg = ASN1_OID_ID_PKCS1_SHA256WITHRSAENCRYPTION;
break;
case EVP_PKEY_EC:
key->signature_alg = ASN1_OID_ID_ECDSA_WITH_SHA256;
break;
default:
break;
}
_hx509_cert_assign_key(cert, key);
return 0;
}
/*
*
*/
struct ks_keychain {
int anchors;
SecKeychainRef keychain;
};
static int
keychain_init(hx509_context context,
hx509_certs certs, void **data, int flags,
const char *residue, hx509_lock lock)
{
struct ks_keychain *ctx;
ctx = calloc(1, sizeof(*ctx));
if (ctx == NULL) {
hx509_clear_error_string(context);
return ENOMEM;
}
if (residue) {
if (strcasecmp(residue, "system-anchors") == 0) {
ctx->anchors = 1;
} else if (strncasecmp(residue, "FILE:", 5) == 0) {
OSStatus ret;
ret = SecKeychainOpen(residue + 5, &ctx->keychain);
if (ret != noErr) {
hx509_set_error_string(context, 0, ENOENT,
"Failed to open %s", residue);
free(ctx);
return ENOENT;
}
} else {
hx509_set_error_string(context, 0, ENOENT,
"Unknown subtype %s", residue);
free(ctx);
return ENOENT;
}
}
*data = ctx;
return 0;
}
/*
*
*/
static int
keychain_free(hx509_certs certs, void *data)
{
struct ks_keychain *ctx = data;
if (ctx->keychain)
CFRelease(ctx->keychain);
memset(ctx, 0, sizeof(*ctx));
free(ctx);
return 0;
}
/*
*
*/
struct iter {
hx509_certs certs;
void *cursor;
CFArrayRef search_result;
CFIndex search_index;
};
static int
keychain_iter_start(hx509_context context,
hx509_certs certs, void *data, void **cursor)
{
struct ks_keychain *ctx = data;
struct iter *iter;
iter = calloc(1, sizeof(*iter));
if (iter == NULL) {
hx509_set_error_string(context, 0, ENOMEM, "out of memory");
return ENOMEM;
}
if (ctx->anchors) {
CFArrayRef anchors;
int ret;
CFIndex i;
ret = hx509_certs_init(context, "MEMORY:ks-file-create",
0, NULL, &iter->certs);
if (ret) {
free(iter);
return ret;
}
ret = SecTrustCopyAnchorCertificates(&anchors);
if (ret != 0) {
hx509_certs_free(&iter->certs);
free(iter);
hx509_set_error_string(context, 0, ENOMEM,
"Can't get trust anchors from Keychain");
return ENOMEM;
}
for (i = 0; i < CFArrayGetCount(anchors); i++) {
SecCertificateRef cr;
CFDataRef certData;
hx509_cert cert;
cr = (SecCertificateRef)(uintptr_t)CFArrayGetValueAtIndex(anchors, i);
certData = SecCertificateCopyData(cr);
if (certData == NULL)
continue;
cert = hx509_cert_init_data(context,
CFDataGetBytePtr(certData),
CFDataGetLength(certData),
NULL);
CFRelease(certData);
if (cert == NULL)
continue;
ret = hx509_certs_add(context, iter->certs, cert);
hx509_cert_free(cert);
}
CFRelease(anchors);
} else if (ctx->keychain) {
/* Search for certificates in the specified keychain */
CFMutableDictionaryRef query;
CFArrayRef searchList;
OSStatus ret;
query = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (query == NULL) {
free(iter);
hx509_set_error_string(context, 0, ENOMEM, "out of memory");
return ENOMEM;
}
CFDictionarySetValue(query, kSecClass, kSecClassCertificate);
CFDictionarySetValue(query, kSecMatchLimit, kSecMatchLimitAll);
CFDictionarySetValue(query, kSecReturnRef, kCFBooleanTrue);
{
const void *keychains[1] = { ctx->keychain };
searchList = CFArrayCreate(kCFAllocatorDefault,
keychains, 1,
&kCFTypeArrayCallBacks);
}
if (searchList) {
CFDictionarySetValue(query, kSecMatchSearchList, searchList);
CFRelease(searchList);
}
ret = SecItemCopyMatching(query, (CFTypeRef *)&iter->search_result);
CFRelease(query);
if (ret != errSecSuccess && ret != errSecItemNotFound) {
free(iter);
hx509_set_error_string(context, 0, EINVAL,
"Failed to search keychain");
return EINVAL;
}
iter->search_index = 0;
}
if (iter->certs) {
int ret;
ret = hx509_certs_start_seq(context, iter->certs, &iter->cursor);
if (ret) {
hx509_certs_free(&iter->certs);
free(iter);
return ret;
}
}
*cursor = iter;
return 0;
}
/*
*
*/
static int
keychain_iter(hx509_context context,
hx509_certs certs, void *data, void *cursor, hx509_cert *cert)
{
struct iter *iter = cursor;
if (iter->certs)
return hx509_certs_next_cert(context, iter->certs, iter->cursor, cert);
*cert = NULL;
if (iter->search_result == NULL)
return 0;
while (iter->search_index < CFArrayGetCount(iter->search_result)) {
SecCertificateRef certRef;
SecIdentityRef identity = NULL;
CFDataRef certData;
heim_error_t error = NULL;
OSStatus ret;
certRef = (SecCertificateRef)(uintptr_t)CFArrayGetValueAtIndex(
iter->search_result, iter->search_index++);
certData = SecCertificateCopyData(certRef);
if (certData == NULL)
continue;
*cert = hx509_cert_init_data(context,
CFDataGetBytePtr(certData),
CFDataGetLength(certData),
&error);
CFRelease(certData);
if (*cert == NULL) {
if (error)
heim_release(error);
continue;
}
/*
* Try to find a matching private key via SecIdentity
*/
ret = SecIdentityCreateWithCertificate(NULL, certRef, &identity);
if (ret == errSecSuccess && identity) {
SecKeyRef keyRef = NULL;
ret = SecIdentityCopyPrivateKey(identity, &keyRef);
if (ret == errSecSuccess && keyRef) {
set_private_key(context, keyRef, *cert);
CFRelease(keyRef);
}
CFRelease(identity);
}
return 0;
}
return 0;
}
/*
*
*/
static int
keychain_iter_end(hx509_context context,
hx509_certs certs,
void *data,
void *cursor)
{
struct iter *iter = cursor;
if (iter->certs) {
hx509_certs_end_seq(context, iter->certs, iter->cursor);
hx509_certs_free(&iter->certs);
}
if (iter->search_result)
CFRelease(iter->search_result);
memset(iter, 0, sizeof(*iter));
free(iter);
return 0;
}
/*
*
*/
struct hx509_keyset_ops keyset_keychain = {
"KEYCHAIN",
0,
keychain_init,
NULL,
keychain_free,
NULL,
NULL,
keychain_iter_start,
keychain_iter,
keychain_iter_end,
NULL,
NULL,
NULL,
NULL
};
#pragma clang diagnostic pop
#endif /* HAVE_FRAMEWORK_SECURITY */
/*
*
*/
HX509_LIB_FUNCTION void HX509_LIB_CALL
_hx509_ks_keychain_register(hx509_context context)
{
#ifdef HAVE_FRAMEWORK_SECURITY
_hx509_ks_register(context, &keyset_keychain);
#endif
}