
Although "macOS" is the correct name for Apple's current desktop OS, we've decided to go with "Mac OS" to cover all of Apple's Unix-like desktop OS versions. The label "Mac OS" is more readable, clear in context (i.e., unlikely to be confused with the Mac OSes that proceeded Mac OS X), and as likely to match Apple's future OS names as anything.
146 lines
4.4 KiB
Racket
146 lines
4.4 KiB
Racket
;; Support for loading root cerficates from Mac OS keychains.
|
|
|
|
#lang racket/base
|
|
(require ffi/unsafe
|
|
ffi/unsafe/objc
|
|
ffi/unsafe/define
|
|
"add-cert.rkt")
|
|
(provide load-macosx-keychain)
|
|
|
|
;; TO DO:
|
|
;; - better error handling
|
|
;; - reliable mem management
|
|
;; - alternatives to deprecated functions
|
|
|
|
;; Alternative: generate PEM file with
|
|
;; security export -k /System/Library/Keychains/... -t certs -f pemseq -o foo.pem
|
|
|
|
(define libcf
|
|
(case (system-type)
|
|
[(macosx) (ffi-lib "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation")]
|
|
[else #f]))
|
|
|
|
(define-ffi-definer define-cf libcf
|
|
#:default-make-fail make-not-available)
|
|
|
|
(define _fourchar _uint32)
|
|
(define _OSStatus _sint32)
|
|
(define _CFIndex _slong)
|
|
|
|
(define-cpointer-type _CFDataRef)
|
|
|
|
(define-cf CFRelease (_fun _pointer -> _void))
|
|
|
|
(define-cf CFDataGetLength
|
|
(_fun _CFDataRef -> _CFIndex))
|
|
|
|
(define-cf CFDataGetBytePtr
|
|
(_fun _CFDataRef -> _pointer))
|
|
|
|
(define (CFData->bytes data)
|
|
(let* ([len (CFDataGetLength data)]
|
|
[buf (make-bytes len)]
|
|
[data-ptr (CFDataGetBytePtr data)])
|
|
(memcpy buf data-ptr len)
|
|
buf))
|
|
|
|
;; ----
|
|
|
|
(define libsec
|
|
(case (system-type)
|
|
[(macosx) (ffi-lib "/System/Library/Frameworks/Security.framework/Security")]
|
|
[else #f]))
|
|
(define-ffi-definer define-sec libsec
|
|
#:default-make-fail make-not-available)
|
|
|
|
(define CSSM_DB_RECORDTYPE_APP_DEFINED_START #x80000000)
|
|
(define kSecCertificateItemClass
|
|
(+ CSSM_DB_RECORDTYPE_APP_DEFINED_START #x1000))
|
|
(define kSecFormatX509Cert 9)
|
|
|
|
(define _SecExternalFormat _int)
|
|
(define-cpointer-type _SecKeychainRef)
|
|
(define-cpointer-type _SecKeychainSearchRef)
|
|
(define-cpointer-type _SecKeychainAttributeListRef)
|
|
|
|
(define-sec SecKeychainOpen
|
|
(_fun _path
|
|
(ref : (_ptr o _SecKeychainRef/null))
|
|
-> (result : _OSStatus)
|
|
-> (values result ref)))
|
|
|
|
(define-sec SecKeychainSearchCreateFromAttributes
|
|
(_fun _SecKeychainRef/null ;; FIXME: or array of keychains
|
|
_fourchar
|
|
_SecKeychainAttributeListRef/null
|
|
(ref : (_ptr o _SecKeychainSearchRef/null))
|
|
-> (result : _OSStatus)
|
|
-> (values result ref)))
|
|
|
|
(define-sec SecKeychainSearchCopyNext ;; deprecated in 10.7
|
|
(_fun _SecKeychainSearchRef
|
|
(next : (_ptr o _id))
|
|
-> (result : _OSStatus)
|
|
-> (values result next)))
|
|
|
|
(define item-export-type
|
|
(_fun (item) ::
|
|
(item : _id) ;; FIXME: SecCertificateRef or array
|
|
(_SecExternalFormat = kSecFormatX509Cert) ;; DER
|
|
(_int = 0)
|
|
(_pointer = #f)
|
|
(ref : (_ptr o _CFDataRef))
|
|
-> (result : _OSStatus)
|
|
-> (values result ref)))
|
|
|
|
(define-sec SecKeychainItemExport #| deprecated in 10.7 |# item-export-type)
|
|
(define-sec SecItemExport #|since 10.7|# item-export-type
|
|
#:fail (lambda () SecKeychainItemExport))
|
|
|
|
;; ----
|
|
|
|
(define (load-macosx-keychain who ssl-ctx path try?)
|
|
(define ders (keychain-path->ders who path try?))
|
|
(define xstore (SSL_CTX_get_cert_store ssl-ctx))
|
|
(for ([der (in-list ders)])
|
|
(let ([x509 (d2i_X509 der)])
|
|
(cond [x509
|
|
;; FIXME: check result for errors (?)
|
|
(X509_STORE_add_cert xstore x509)]
|
|
[try? (void)]
|
|
[else
|
|
(error who "retrieved invalid certificate from keychain: ~e" path)]))))
|
|
|
|
(define (keychain-path->ders who path try?)
|
|
(define path* (path->complete-path (cleanse-path path)))
|
|
(define-values (status keychain)
|
|
(SecKeychainOpen path*))
|
|
(begin0 (cond [(= status 0)
|
|
(keychain->ders who keychain try?)]
|
|
[try? (void)]
|
|
[else
|
|
(error who "failed to open keychain: ~e" path)])
|
|
(CFRelease keychain)))
|
|
|
|
(define (keychain->ders who keychain try?)
|
|
(define-values (status search)
|
|
(SecKeychainSearchCreateFromAttributes keychain kSecCertificateItemClass #f))
|
|
(begin0 (cond [(= status 0)
|
|
(keychain-search->ders who search try?)]
|
|
[try? (void)]
|
|
[else (error "internal error: failed to open keychain search")])
|
|
(CFRelease search)))
|
|
|
|
(define (keychain-search->ders who search try?)
|
|
(let loop ()
|
|
(define-values (status next)
|
|
(SecKeychainSearchCopyNext search))
|
|
(cond [(= status 0)
|
|
(let-values ([(status* data) (SecItemExport next)])
|
|
(let ([der (CFData->bytes data)])
|
|
(CFRelease next)
|
|
(CFRelease data)
|
|
(cons der (loop))))]
|
|
;; FIXME: other error codes?
|
|
[else null])))
|