[rt-commit] r162 - in /redwax-tool/trunk: redwax_keychain.c redwax_keychain.h
rt-commit at redwax.eu
rt-commit at redwax.eu
Fri Nov 3 21:22:42 CET 2023
Author: minfrin at redwax.eu
Date: Fri Nov 3 21:22:42 2023
New Revision: 162
Log:
Add support for reading certificates from the MacOS
Keychain.
Added:
redwax-tool/trunk/redwax_keychain.c
redwax-tool/trunk/redwax_keychain.h
Added: redwax-tool/trunk/redwax_keychain.c
==============================================================================
--- redwax-tool/trunk/redwax_keychain.c (added)
+++ redwax-tool/trunk/redwax_keychain.c Fri Nov 3 21:22:42 2023
@@ -0,0 +1,660 @@
+/**
+ * Copyright (C) 2023 Graham Leggett <minfrin at sharp.fm>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+/*
+ * redwax_keychain - import / export routines for MacOS keychains.
+ *
+ */
+
+#include <apr_strings.h>
+
+#include "config.h"
+#include "redwax-tool.h"
+
+#include "redwax_util.h"
+
+#if HAVE_SECURITY_SECURITY_H
+
+#include <CoreServices/CoreServices.h>
+#include <Security/Security.h>
+//#include <LocalAuthentication/LocalAuthentication.h>
+
+module keychain_module;
+
+typedef struct {
+ unsigned int in:1;
+} keychain_config_t;
+
+typedef struct {
+ CFDictionaryRef kref;
+ const UInt8 *fingerprint;
+ CFIndex fingerprint_len;
+} keychain_key_config_t;
+
+#if 0
+static apr_status_t cleanup_key(void *dummy)
+{
+ if (dummy) {
+
+ redwax_key_t *key = dummy;
+
+ keychain_key_config_t *key_config;
+
+ key_config = redwax_get_module_config(key->per_module, &keychain_module);
+
+ if (key_config) {
+
+ if (key_config->kref) {
+ CFRelease(key_config->kref);
+ }
+
+ if (key->keys_index && key_config->fingerprint) {
+
+ apr_hash_set(key->keys_index,
+ key_config->fingerprint,
+ key_config->fingerprint_len, NULL);
+
+
+ }
+
+ }
+ }
+
+ return APR_SUCCESS;
+}
+#endif
+
+static apr_status_t redwax_keychain_initialise(redwax_tool_t *r)
+{
+
+ return OK;
+}
+
+static apr_status_t redwax_keychain_complete_keychain_in(redwax_tool_t *r,
+ const char *url, apr_hash_t *urls)
+{
+ return OK;
+}
+
+static apr_status_t redwax_keychain_process_certificates(redwax_tool_t *r,
+ const char *name)
+{
+ CFTypeRef certs = NULL;
+
+ CFIndex count;
+ CFIndex i;
+
+ CFStringRef keys[] = {
+ kSecClass,
+ kSecMatchLimit,
+ kSecReturnRef
+ };
+
+ CFTypeRef values[] = {
+ kSecClassCertificate,
+ kSecMatchLimitAll,
+ kCFBooleanTrue
+ };
+
+ CFDictionaryRef query = CFDictionaryCreate(
+ NULL,
+ (const void **) keys,
+ values,
+ sizeof(keys) / sizeof(keys[0]),
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks
+ );
+
+ OSStatus err = SecItemCopyMatching(query, &certs);
+ if (err != errSecSuccess) {
+
+ CFStringRef error = SecCopyErrorMessageString(err, NULL);
+
+ CFIndex len = CFStringGetLength(error);
+ CFIndex max = CFStringGetMaximumSizeForEncoding(len, kCFStringEncodingUTF8) + 1;
+
+ char *buffer = apr_palloc(r->pool, max);
+
+ if (CFStringGetCString(error, buffer, max,
+ kCFStringEncodingUTF8)) {
+
+ redwax_print_error(r, "keychain-in: certificates could not be returned: %s\n",
+ buffer);
+
+ }
+
+ return APR_EGENERAL;
+ }
+
+ count = CFArrayGetCount(certs);
+
+ for (i = 0; i < count; i++) {
+
+ SecCertificateRef cref = (SecCertificateRef) CFArrayGetValueAtIndex(certs, i);
+
+ apr_pool_t *p;
+
+ apr_pool_create(&p, r->pool);
+
+ redwax_certificate_t *cert = apr_pcalloc(p,
+ sizeof(redwax_certificate_t));
+ cert->pool = p;
+
+ cert->common.type = REDWAX_CERTIFICATE_X509;
+
+ CFDataRef der = SecCertificateCopyData(cref);
+
+ cert->len = CFDataGetLength(der);
+ cert->der = apr_pmemdup(p, CFDataGetBytePtr(der), cert->len);
+
+ rt_run_normalise_certificate(r, cert, 1);
+
+ switch (cert->common.category) {
+ case REDWAX_CERTIFICATE_END_ENTITY: {
+
+ redwax_certificate_t *c = apr_array_push(r->certs_in);
+ memcpy(c, cert, sizeof(*cert));
+
+ redwax_print_error(r, "keychain-in: certificate: %s\n",
+ cert->common.subject);
+
+ break;
+ }
+ case REDWAX_CERTIFICATE_INTERMEDIATE: {
+
+ redwax_certificate_t *c = apr_array_push(r->intermediates_in);
+ memcpy(c, cert, sizeof(*cert));
+
+ redwax_print_error(r, "keychain-in: intermediate: %s\n",
+ cert->common.subject);
+
+ break;
+ }
+ case REDWAX_CERTIFICATE_ROOT: {
+
+ redwax_certificate_t *c = apr_array_push(r->intermediates_in);
+ memcpy(c, cert, sizeof(*cert));
+
+ redwax_print_error(r, "keychain-in: root: %s\n",
+ cert->common.subject);
+
+ break;
+ }
+ case REDWAX_CERTIFICATE_TRUSTED: {
+
+ redwax_certificate_t *c = apr_array_push(r->trusted_in);
+ memcpy(c, cert, sizeof(*cert));
+
+ redwax_print_error(r, "keychain-in: trusted: %s\n",
+ cert->common.subject);
+
+ break;
+ }
+ default: {
+
+ redwax_print_debug(r, "keychain-in: unrecognised "
+ "certificate, skipped: %s\n",
+ cert->common.subject);
+
+ break;
+ }
+ }
+
+ }
+
+ CFRelease(certs);
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t redwax_keychain_process_trusted(redwax_tool_t *r,
+ const char *name)
+{
+ CFArrayRef trusted = NULL;
+
+ CFIndex count;
+ CFIndex i;
+
+ OSStatus err = SecTrustCopyAnchorCertificates(&trusted);
+ if (err != errSecSuccess) {
+
+ CFStringRef error = SecCopyErrorMessageString(err, NULL);
+
+ CFIndex len = CFStringGetLength(error);
+ CFIndex max = CFStringGetMaximumSizeForEncoding(len, kCFStringEncodingUTF8) + 1;
+
+ char *buffer = apr_palloc(r->pool, max);
+
+ if (CFStringGetCString(error, buffer, max,
+ kCFStringEncodingUTF8)) {
+
+ redwax_print_error(r, "keychain-in: trusted certificates could not be returned: %s\n",
+ buffer);
+
+ }
+
+ return APR_EGENERAL;
+ }
+
+ count = CFArrayGetCount(trusted);
+
+ for (i = 0; i < count; i++) {
+
+ SecCertificateRef cref = (SecCertificateRef) CFArrayGetValueAtIndex(trusted, i);
+
+ apr_pool_t *p;
+
+ apr_pool_create(&p, r->pool);
+
+ redwax_certificate_t *cert = apr_pcalloc(p,
+ sizeof(redwax_certificate_t));
+ cert->pool = p;
+
+ cert->common.type = REDWAX_CERTIFICATE_X509;
+
+ CFDataRef der = SecCertificateCopyData(cref);
+
+ cert->len = CFDataGetLength(der);
+ cert->der = apr_pmemdup(p, CFDataGetBytePtr(der), cert->len);
+
+ rt_run_normalise_certificate(r, cert, 1);
+
+ switch (cert->common.category) {
+ case REDWAX_CERTIFICATE_INTERMEDIATE:
+ case REDWAX_CERTIFICATE_ROOT:
+ case REDWAX_CERTIFICATE_TRUSTED: {
+
+ redwax_certificate_t *c = apr_array_push(r->trusted_in);
+ memcpy(c, cert, sizeof(*cert));
+
+ redwax_print_error(r, "keychain-in: trusted: %s\n",
+ cert->common.subject);
+
+ break;
+ }
+ default: {
+
+ redwax_print_debug(r, "keychain-in: unrecognised "
+ "certificate, skipped: %s\n",
+ cert->common.subject);
+
+ break;
+ }
+ }
+
+ }
+
+ CFRelease(trusted);
+
+ return APR_SUCCESS;
+}
+
+#if 0
+
+/*
+ * This code demonstrates how the keychain might be queried for
+ * keys the same way we query certificates.
+ *
+ * The current mechanism pulls keys in the search_key hook.
+ */
+static apr_status_t redwax_keychain_process_keys(redwax_tool_t *r,
+ const char *name)
+{
+ CFTypeRef keys = NULL;
+
+ CFIndex count;
+ CFIndex i;
+
+ CFStringRef dictkeys[] = {
+ kSecClass,
+ kSecMatchLimit,
+ kSecAttrKeyClass,
+ kSecReturnRef,
+#if 0
+ kSecReturnAttributes
+#endif
+ };
+
+ CFTypeRef dictvalues[] = {
+ kSecClassKey,
+ kSecMatchLimitAll,
+ kSecAttrKeyClassPublic,
+ kCFBooleanTrue,
+#if 0
+ kCFBooleanTrue
+#endif
+ };
+
+ CFDictionaryRef query = CFDictionaryCreate(
+ NULL,
+ (const void **) dictkeys,
+ dictvalues,
+ sizeof(dictkeys) / sizeof(dictkeys[0]),
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks
+ );
+
+ OSStatus err = SecItemCopyMatching(query, &keys);
+ if (err != errSecSuccess) {
+
+ CFStringRef error = SecCopyErrorMessageString(err, NULL);
+
+ CFIndex len = CFStringGetLength(error);
+ CFIndex max = CFStringGetMaximumSizeForEncoding(len, kCFStringEncodingUTF8) + 1;
+
+ char *buffer = apr_palloc(r->pool, max);
+
+ if (CFStringGetCString(error, buffer, max,
+ kCFStringEncodingUTF8)) {
+
+ redwax_print_error(r, "keychain-in: keys could not be returned: %s\n",
+ buffer);
+
+ }
+
+ return APR_EGENERAL;
+ }
+
+#if 0
+ CFShow(keys);
+#endif
+
+ count = CFArrayGetCount(keys);
+ for (i = 0; i < count; i++) {
+
+ SecKeyRef keyref = (SecKeyRef) CFArrayGetValueAtIndex(keys, i);
+
+ redwax_key_t *key;
+
+ keychain_key_config_t *key_config;
+
+ CFShow(keyref);
+
+ CFDataRef der;
+
+ /* kSecFormatOpenSSL is undefined - experimentation shows
+ * it creates SubjectPublicKeyInfo for most public keys.
+ */
+ err = SecItemExport(keyref, kSecFormatOpenSSL, 0, NULL, &der);
+
+ if (err != errSecSuccess) {
+
+ CFStringRef error = SecCopyErrorMessageString(err, NULL);
+
+ CFIndex len = CFStringGetLength(error);
+ CFIndex max = CFStringGetMaximumSizeForEncoding(len, kCFStringEncodingUTF8) + 1;
+
+ char *buffer = apr_palloc(r->pool, max);
+
+ if (CFStringGetCString(error, buffer, max,
+ kCFStringEncodingUTF8)) {
+
+ redwax_print_error(r, "keychain-in: public key could not be converted: %s\n",
+ buffer);
+
+ }
+
+ continue;
+
+ }
+
+ CFDictionaryRef kdict = CFRetain(SecKeyCopyAttributes(keyref));
+
+ key = apr_array_push(r->keys_in);
+
+ apr_pool_create(&key->pool, r->keys_in->pool);
+
+ key->common.subjectpublickeyinfo_len = CFDataGetLength(der);
+ key->common.subjectpublickeyinfo_der = apr_pmemdup(key->pool, CFDataGetBytePtr(der), key->common.subjectpublickeyinfo_len);
+
+ CFRelease(der);
+
+ key->per_module = redwax_create_module_config(key->pool);
+ key_config = apr_pcalloc(key->pool, sizeof(keychain_key_config_t));
+ redwax_set_module_config(key->per_module, &keychain_module, key_config);
+
+ key_config->kref = kdict;
+
+ apr_pool_cleanup_register(key->pool, key, cleanup_key,
+ apr_pool_cleanup_null);
+
+ CFStringRef label = CFDictionaryGetValue(kdict, kSecAttrLabel);
+
+ if (label) {
+
+ CFIndex len = CFStringGetLength(label);
+ CFIndex max = CFStringGetMaximumSizeForEncoding(len, kCFStringEncodingUTF8) + 1;
+
+ char *buffer = apr_palloc(r->pool, max);
+
+ if (CFStringGetCString(label, buffer, max,
+ kCFStringEncodingUTF8)) {
+
+ redwax_print_error(r, "keychain-in: key: %s\n",
+ buffer);
+
+ }
+
+ }
+ else {
+ redwax_print_error(r, "keychain-in: key\n");
+ }
+
+ /* index the key */
+ CFDataRef fingerprint = CFDictionaryGetValue(kdict, kSecAttrApplicationLabel);
+
+ if (fingerprint) {
+
+ key_config->fingerprint_len = CFDataGetLength(fingerprint);
+ key_config->fingerprint = apr_pmemdup(key->pool, CFDataGetBytePtr(fingerprint), key_config->fingerprint_len);
+
+
+ }
+
+ if (!apr_hash_get(r->keys_index,
+ key->common.subjectpublickeyinfo_der,
+ key->common.subjectpublickeyinfo_len)) {
+
+ key->keys_index = r->keys_index;
+
+ apr_hash_set(key->keys_index,
+ key->common.subjectpublickeyinfo_der,
+ key->common.subjectpublickeyinfo_len, key);
+
+ }
+
+ }
+
+ CFRelease(keys);
+
+ return APR_SUCCESS;
+}
+#endif
+
+static apr_status_t redwax_keychain_process_keychain_in(redwax_tool_t *r,
+ const char *name)
+{
+ apr_status_t status;
+
+ keychain_config_t *config;
+
+ r->per_module = redwax_create_module_config(r->pool);
+ config = apr_pcalloc(r->pool, sizeof(keychain_config_t));
+ redwax_set_module_config(r->per_module, &keychain_module, config);
+
+ config->in = 1;
+
+ if (APR_SUCCESS != (status = redwax_keychain_process_certificates(r, name))) {
+ return status;
+ }
+
+ if (APR_SUCCESS != (status = redwax_keychain_process_trusted(r, name))) {
+ return status;
+ }
+
+#if 0
+ if (APR_SUCCESS != (status = redwax_keychain_process_keys(r, name))) {
+ return status;
+ }
+#endif
+
+ return OK;
+}
+
+static apr_status_t redwax_keychain_search_key(redwax_tool_t *r,
+ const redwax_certificate_t *cert)
+{
+ keychain_config_t *config;
+
+ config = redwax_get_module_config(r->per_module, &keychain_module);
+
+ if (cert->der && config->in) {
+
+ OSStatus err;
+
+ redwax_key_t *key;
+
+ SecIdentityRef idref = NULL;
+
+ CFDataRef data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, cert->der, cert->len, kCFAllocatorNull);
+
+ SecCertificateRef certref = SecCertificateCreateWithData(kCFAllocatorDefault, data);
+
+ CFRelease(data);
+
+ err = SecIdentityCreateWithCertificate(NULL, certref, &idref);
+
+ CFRelease(certref);
+
+ if (err != errSecSuccess) {
+
+ CFStringRef error = SecCopyErrorMessageString(err, NULL);
+
+ CFIndex len = CFStringGetLength(error);
+ CFIndex max = CFStringGetMaximumSizeForEncoding(len, kCFStringEncodingUTF8) + 1;
+
+ char *buffer = apr_palloc(r->pool, max);
+
+ if (CFStringGetCString(error, buffer, max,
+ kCFStringEncodingUTF8)) {
+
+ if (err == errSecItemNotFound) {
+
+ redwax_print_debug(r, "keychain-in: key could not be searched: %s\n",
+ buffer);
+
+ }
+ else {
+
+ redwax_print_error(r, "keychain-in: key could not be searched: %s\n",
+ buffer);
+
+ }
+
+ }
+
+ return DECLINED;
+ }
+
+ SecKeyRef keyref;
+
+ err = SecIdentityCopyPrivateKey(idref, &keyref);
+
+ CFRelease(idref);
+
+ if (err != errSecSuccess) {
+
+ CFStringRef error = SecCopyErrorMessageString(err, NULL);
+
+ CFIndex len = CFStringGetLength(error);
+ CFIndex max = CFStringGetMaximumSizeForEncoding(len, kCFStringEncodingUTF8) + 1;
+
+ char *buffer = apr_palloc(r->pool, max);
+
+ if (CFStringGetCString(error, buffer, max,
+ kCFStringEncodingUTF8)) {
+
+ redwax_print_error(r, "keychain-in: key found but could not be retrieved: %s\n",
+ buffer);
+
+ }
+
+ return DECLINED;
+ }
+
+ CFDataRef der;
+
+ err = SecItemExport(keyref, kSecFormatBSAFE, 0, NULL, &der);
+
+ CFRelease(keyref);
+
+ if (err != errSecSuccess) {
+
+ CFStringRef error = SecCopyErrorMessageString(err, NULL);
+
+ CFIndex len = CFStringGetLength(error);
+ CFIndex max = CFStringGetMaximumSizeForEncoding(len, kCFStringEncodingUTF8) + 1;
+
+ char *buffer = apr_palloc(r->pool, max);
+
+ if (CFStringGetCString(error, buffer, max,
+ kCFStringEncodingUTF8)) {
+
+ redwax_print_error(r, "keychain-in: key retrieved but could not be exported: %s\n",
+ buffer);
+
+ }
+
+ return DECLINED;
+ }
+
+ key = apr_array_push(r->keys_out);
+
+ apr_pool_create(&key->pool, r->keys_out->pool);
+
+ key->len = CFDataGetLength(der);
+ key->der = apr_pmemdup(key->pool, CFDataGetBytePtr(der), key->len);
+
+ CFRelease(der);
+
+ return APR_SUCCESS;
+ }
+
+ return DECLINED;
+}
+
+void redwax_add_default_keychain_hooks()
+{
+ rt_hook_complete_keychain_in(redwax_keychain_complete_keychain_in, NULL, NULL, APR_HOOK_MIDDLE);
+ rt_hook_process_keychain_in(redwax_keychain_process_keychain_in, NULL, NULL, APR_HOOK_MIDDLE);
+ rt_hook_search_key(redwax_keychain_search_key, NULL, NULL, APR_HOOK_MIDDLE);
+ rt_hook_initialise(redwax_keychain_initialise, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+#else
+
+void redwax_add_default_keychain_hooks()
+{
+}
+
+#endif
+
+REDWAX_DECLARE_MODULE(keychain) =
+{
+ STANDARD_MODULE_STUFF,
+ redwax_add_default_keychain_hooks /* register hooks */
+};
Added: redwax-tool/trunk/redwax_keychain.h
==============================================================================
--- redwax-tool/trunk/redwax_keychain.h (added)
+++ redwax-tool/trunk/redwax_keychain.h Fri Nov 3 21:22:42 2023
@@ -0,0 +1,33 @@
+/**
+ * Copyright (C) 2023 Graham Leggett <minfrin at sharp.fm>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+/*
+ * redwax_keychain - import / export routines for MacOS keychains.
+ *
+ */
+
+#ifndef REDWAX_KEYCHAIN_H_
+#define REDWAX_KEYCHAIN_H_
+
+#include "config.h"
+
+#if HAVE_SECURITY_SECURITY_H
+
+void redwax_add_default_keychain_hooks();
+
+#endif
+#endif /* REDWAX_KEYCHAIN_H_ */
More information about the rt-commit
mailing list