[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