1 line
41 KiB
C++
Executable File
1 line
41 KiB
C++
Executable File
/* Copyright (c) 2017, Computer History Museum
|
|
All rights reserved.
|
|
Redistribution and use in source and binary forms, with or without modification, are permitted (subject to
|
|
the limitations in the disclaimer below) provided that the following conditions are met:
|
|
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
* 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.
|
|
* Neither the name of Computer History Museum nor the names of its contributors may be used to endorse or promote products
|
|
derived from this software without specific prior written permission.
|
|
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE
|
|
COPYRIGHT HOLDERS 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 COPYRIGHT
|
|
HOLDER 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. */
|
|
|
|
/*
|
|
OpenSSL.cp
|
|
|
|
OpenSSL bits for Eudora. Match up the OpenSSL interfaces to the Eudora ones
|
|
|
|
Basic data structures:
|
|
SSL_CTX -- an SSL context. Has an connection method (client/server),
|
|
(SSL2/SSL3/TLS1) and some certs assoctiate with it. SSL_CTX_new/SSL_CTX_free.
|
|
|
|
SSL -- a SSL connection object. Manages a single SSL connection. Has an
|
|
associated SSL_CTX. SSL_new/SSL_free.
|
|
|
|
BIO - Basic I/O -- an object to run the IO. Talks to a file/socket/whatever.
|
|
There are filter bios, too. Some handle the encryption and decryption. Another
|
|
might handle logging, etc.
|
|
|
|
We define a new BIO class, that talks to an OpenTransport socket via an Eudora TransStream;
|
|
since OpenSSL doesn't know OpenTransport from Adam.
|
|
|
|
Gotchas -
|
|
1. Since the SSL_CTX knows the connection method (v2, v3, TLSV1), we can't
|
|
share the SSL_CTX between all the connections. Rats.
|
|
|
|
2. Since OpenSSL is Mach-O, but Eudora is (currently) PEF, we can't call
|
|
OpenSSL functions directly, but will have to bridge them. Also, we have to
|
|
make the function pointers that OpenSSL calls into Mach-O function pointers,
|
|
since it doesn't (again) know PEF from Adam. In particular, the BIO is a table
|
|
of function pointers.
|
|
|
|
3. OpenSSL wants the certificates in PEM format; we've got them in BER.
|
|
|
|
*/
|
|
|
|
|
|
#include "OpenSSL.h"
|
|
#include "MachOWrapper.h"
|
|
|
|
/* NO MORE "relaxed pointer rules"!!! */
|
|
#pragma mpwc_relax off
|
|
|
|
static void *MachOFunctionPointerForCFMFunctionPointer( void *cfmfp );
|
|
|
|
extern TransVector ESSLSubTrans;
|
|
|
|
#define SSL_CTRL_OPTIONS 32
|
|
#define SSL_CTX_set_options(ctx,op) \
|
|
SSL_CTX_ctrl((ctx),SSL_CTRL_OPTIONS,(op),NULL)
|
|
|
|
/* Maybe.. */
|
|
int BIO_ot_should_retry ( OTResult err );
|
|
int BIO_ot_non_fatal_error ( OTResult err );
|
|
|
|
BIO * BIO_new ( BIO_METHOD *type );
|
|
long BIO_int_ctrl(BIO *bp,int cmd,long larg,int iarg);
|
|
long SSL_CTX_ctrl(SSL_CTX *ctx, int cmd, long larg, void *parg);
|
|
|
|
/* These are the MachO function pointers for the BIO */
|
|
void *gOTWrite = NULL;
|
|
void *gOTRead = NULL;
|
|
void *gOTPuts = NULL;
|
|
void *gOTCtrl = NULL;
|
|
void *gOTNew = NULL;
|
|
void *gOTFree = NULL;
|
|
|
|
CFBundleRef gSSLBundle = NULL;
|
|
|
|
#define BIO_TYPE_OT_SOCKET (25|0x0400|0x0100)
|
|
|
|
/* use either SSL_VERIFY_NONE or SSL_VERIFY_PEER, the last 2 options
|
|
* are 'ored' with SSL_VERIFY_PEER if they are desired */
|
|
#define SSL_VERIFY_NONE 0x00
|
|
#define SSL_VERIFY_PEER 0x01
|
|
#define SSL_VERIFY_FAIL_IF_NO_PEER_CERT 0x02
|
|
#define SSL_VERIFY_CLIENT_ONCE 0x04
|
|
|
|
#pragma mark -- OT & OpenSSL setup --
|
|
|
|
/* Private functions */
|
|
extern "C" void InitMachOFunctions ();
|
|
OSStatus OpenSSLReadCerts ( SSL *ssl, ConstStr255Param hostName );
|
|
|
|
/* We have to add these! */
|
|
|
|
X509 *d2i_X509(X509 **px, unsigned char **in, int len);
|
|
int SSL_CTX_use_certificate(SSL_CTX *ctx, X509 *x);
|
|
void SSL_CTX_set_verify_depth(SSL_CTX *ctx,int depth);
|
|
|
|
BIO_METHOD *BIO_s_otsocket ( void ) {
|
|
if ( NULL == gOTWrite )
|
|
InitMachOFunctions ();
|
|
|
|
static BIO_METHOD methods_otp = {
|
|
BIO_TYPE_OT_SOCKET,
|
|
"ot_socket_in_Eudora",
|
|
( int (*) ( bio_st *, const char *, int )) gOTWrite,
|
|
( int (*) ( bio_st *, char *, int )) gOTRead,
|
|
( int (*) ( bio_st *, const char * )) gOTPuts,
|
|
NULL, /* sock_gets, */
|
|
( long (*) ( bio_st *, int, long, void * )) gOTCtrl,
|
|
( int (*) ( bio_st * )) gOTNew,
|
|
( int (*) ( bio_st * )) gOTFree,
|
|
NULL,
|
|
};
|
|
|
|
|
|
return &methods_otp;
|
|
}
|
|
|
|
|
|
static int ot_write(BIO *h, const char *buf, int num);
|
|
static int ot_read(BIO *h, char *buf, int size);
|
|
static int ot_puts(BIO *h, const char *str);
|
|
static long ot_ctrl(BIO *h, int cmd, long arg1, void *arg2);
|
|
static int ot_new(BIO *h);
|
|
static int ot_free(BIO *data);
|
|
|
|
BIO *BIO_new_ot ( TransStream ts, int close_flag ) {
|
|
BIO *ret;
|
|
|
|
ret= BIO_new ( BIO_s_otsocket ());
|
|
if ( NULL == ret )
|
|
return NULL;
|
|
BIO_set_fd ( ret, (int) ts, close_flag );
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int ot_new ( BIO *bi ) {
|
|
bi->init=0;
|
|
bi->num=0;
|
|
bi->ptr=NULL;
|
|
bi->flags=0;
|
|
return(1);
|
|
}
|
|
|
|
static int ot_free ( BIO *a ) {
|
|
if ( NULL == a )
|
|
return 0;
|
|
|
|
if (a->shutdown) { // Should we close?
|
|
if (a->init) { // Did we init??
|
|
// Close the socket
|
|
// SHUTDOWN2(a->num);
|
|
}
|
|
a->init=0; // no longer opened
|
|
a->flags=0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
// See <http://www.freebsdchina.org/utils/phpMan.php/man/BIO_read/3> for more info here
|
|
static int ot_read ( BIO *b, char *out, int outl ) {
|
|
long err = 0;
|
|
OTFlags flags = 0;
|
|
|
|
ASSERT ( outl > 0 );
|
|
if ( outl <= 0 )
|
|
return 0;
|
|
|
|
if ( out != NULL ) {
|
|
// Read some bytes from the connection
|
|
TransStream stream = (TransStream) b->num;
|
|
SInt32 processed;
|
|
|
|
processed = outl;
|
|
err = (*ESSLSubTrans.vRecvTrans) ( stream, (unsigned char *) out, &processed );
|
|
if ( noErr == err )
|
|
err = processed;
|
|
|
|
// if ( err > 0 )
|
|
// print_data ( out, err );
|
|
BIO_clear_retry_flags(b);
|
|
if ( err <= noErr ) { // we didn't read any bytes
|
|
if ( BIO_ot_should_retry ( err )) {
|
|
BIO_set_retry_read ( b );
|
|
err = -1; // retry me!
|
|
}
|
|
else
|
|
err = 0; // general purpose error
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int ot_write(BIO *b, const char *in, int inl) {
|
|
long err = 0;
|
|
|
|
ASSERT ( NULL != in );
|
|
ASSERT ( inl > 0 );
|
|
|
|
if ( NULL == in || inl <= 0 )
|
|
return 0;
|
|
|
|
// Instead of pushing the bytes directly, we should call via Eudora's TransVector.
|
|
err = (*ESSLSubTrans.vSendTrans)((TransStream) b->num, (unsigned char *) in, inl, nil);
|
|
if ( noErr == err )
|
|
err = inl;
|
|
|
|
// if ( err > 0 )
|
|
// print_data ( in, err );
|
|
BIO_clear_retry_flags ( b );
|
|
if ( err < 0 ) { // we didn't write any bytes
|
|
if ( BIO_ot_should_retry ( err )) {
|
|
BIO_set_retry_write ( b );
|
|
err = -1; // retry me!
|
|
}
|
|
else
|
|
err = 0; // general purpose error
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
// See <http://www.freebsdchina.org/utils/phpMan.php?parameter=BIO_ctrl&mode=man>
|
|
static long ot_ctrl ( BIO *b, int cmd, long num, void *ptr ) {
|
|
long ret=1;
|
|
|
|
// printf ( "ot_ctrl: %d\n", cmd );
|
|
switch (cmd) {
|
|
case BIO_CTRL_RESET: num=0; // fall into seek code
|
|
case BIO_C_FILE_SEEK:
|
|
return 0;
|
|
|
|
case BIO_C_FILE_TELL:
|
|
case BIO_CTRL_INFO:
|
|
return 0;
|
|
|
|
case BIO_C_SET_FD: // Set the endpoint for the BIO
|
|
ot_free(b); // Close whatever's already there
|
|
b->num= *((int *)ptr); // Set the new endpoint
|
|
b->shutdown=(int)num; // Set the close mode
|
|
b->init=1; // We've been initialized
|
|
break;
|
|
|
|
case BIO_C_GET_FD: // Return the endpoint for the BIO
|
|
if ( b->init ) {
|
|
if ( NULL != ptr )
|
|
* ((int *) ptr ) = ret = b->num;
|
|
}
|
|
else
|
|
ret= -1;
|
|
break;
|
|
|
|
case BIO_CTRL_GET_CLOSE: // Get the close flag
|
|
ret = b->shutdown;
|
|
break;
|
|
|
|
case BIO_CTRL_SET_CLOSE:
|
|
b->shutdown = (int)num; // Set the close flag
|
|
break;
|
|
|
|
case BIO_CTRL_PENDING:
|
|
case BIO_CTRL_WPENDING:
|
|
ret=0;
|
|
break;
|
|
|
|
case BIO_CTRL_DUP:
|
|
case BIO_CTRL_FLUSH:
|
|
ret=1;
|
|
break;
|
|
|
|
default:
|
|
ret=0;
|
|
break;
|
|
}
|
|
|
|
return(ret);
|
|
}
|
|
|
|
static int ot_puts ( BIO *bp, const char *str ) {
|
|
ASSERT ( NULL != str );
|
|
return ot_write ( bp, str, strlen ( str ));
|
|
}
|
|
|
|
|
|
// Given an OTResult, decide if it's an error that we should retry on
|
|
int BIO_ot_should_retry ( OTResult err ) {
|
|
ASSERT ( err <= 0 );
|
|
// printf ( "BIO_ot_should_retry: %d\n", err );
|
|
return BIO_ot_non_fatal_error ( err );
|
|
}
|
|
|
|
|
|
// Is this an error that we should abort on?
|
|
int BIO_ot_non_fatal_error ( OTResult err ) {
|
|
ASSERT ( err <= 0 );
|
|
// printf ( "BIO_ot_non_fatal_error: %d\n", err );
|
|
switch ( err ) {
|
|
case noErr: // no problems, just no data right now
|
|
case kOTNoDataErr: // no data to read right now
|
|
case kOTFlowErr: // can't send data right now
|
|
case kOTStateChangeErr: // state is changing
|
|
// More cases to go here
|
|
// printf ( "BIO_ot_non_fatal_error returning 1\n" );
|
|
return 1; // non-fatal errors
|
|
|
|
default:
|
|
// printf ( "BIO_ot_non_fatal_error returning 0\n" );
|
|
return 0; // fatal errors
|
|
}
|
|
|
|
// printf ( "BIO_ot_non_fatal_error returning 0 (2)\n" );
|
|
return 0; // belt and suspenders (and to keep the compiler quiet)
|
|
}
|
|
|
|
|
|
#pragma mark --Combo Routines--
|
|
OSStatus SetupSSLConnection ( TransStream ep, long method, CertVerifyProc callback ) {
|
|
ASSERT ( ep->ctx == NULL );
|
|
ASSERT ( ep->ssl == NULL );
|
|
ASSERT ( ep->bio == NULL );
|
|
|
|
if ( NULL == gSSLBundle ) {
|
|
OSStatus err = InitOpenSSL ();
|
|
if ( noErr != err )
|
|
return err;
|
|
}
|
|
|
|
SSL_METHOD *meth = NULL;
|
|
// These are SSL-plus numbers, but they're enshrined in Eudora's settings
|
|
switch ( method ) {
|
|
// SSL_Version_3_0_With_2_0_Hello
|
|
case 100: meth = SSLv23_client_method (); ASSERT ( meth != NULL ); break;
|
|
|
|
// SSL_Version_2_0
|
|
case 2: meth = SSLv2_client_method (); ASSERT ( meth != NULL ); break;
|
|
|
|
// SSL_Version_3_0_Only and SSL_Version_3_0
|
|
case 0x0300:
|
|
case 101: meth = SSLv3_client_method (); ASSERT ( meth != NULL ); break;
|
|
|
|
// TLS_Version_1_0 and TLS_Version_1_0_Only
|
|
case 0x0301:
|
|
case 102: meth = TLSv1_client_method (); ASSERT ( meth != NULL ); break;
|
|
|
|
// If something else, give it our best shot
|
|
default:
|
|
meth = SSLv23_client_method (); ASSERT ( meth != NULL );
|
|
break;
|
|
}
|
|
|
|
/*
|
|
On Mac OS X the trusted root certificates are stored in a keychain at:
|
|
/System/Library/Keychains/X509Anchors
|
|
*/
|
|
|
|
ep->ctx = SSL_CTX_new ( meth ); ASSERT ( ep->ctx != NULL );
|
|
SSL_CTX_set_options ( ep->ctx, 0 );
|
|
if ( NULL == callback )
|
|
SSL_CTX_set_verify ( ep->ctx, SSL_VERIFY_NONE, NULL );
|
|
else
|
|
SSL_CTX_set_verify ( ep->ctx, SSL_VERIFY_PEER, callback );
|
|
|
|
// Read the CA certs
|
|
OpenSSLReadCerts ( ep->ctx, ep->serverName );
|
|
|
|
SSL_CTX_set_verify_depth ( ep->ctx, 4 );
|
|
|
|
ep->ssl = SSL_new ( ep->ctx ); ASSERT ( ep->ssl != NULL );
|
|
ep->bio = BIO_new_ot ( ep, BIO_NOCLOSE ); ASSERT ( ep->bio != NULL );
|
|
SSL_set_bio ( ep->ssl, ep->bio, ep->bio ); // Connect the bio to the SSL engine
|
|
SSL_set_connect_state ( ep->ssl ); // Tell it that it's a client
|
|
return noErr;
|
|
}
|
|
|
|
OSStatus CleanupSSLConnection ( TransStream ep ) {
|
|
|
|
if ( NULL != ep->ssl ) { SSL_free ( ep->ssl ); ep->ssl = NULL; }
|
|
if ( NULL != ep->ctx ) { SSL_CTX_free ( ep->ctx ); ep->ctx = NULL; }
|
|
// We don't have to do this, because according to "Programming with OpenSSL,
|
|
// page 126, 'SSL_free automatically frees the SSL object's underlying BIOs for us.'
|
|
// if ( NULL != bio ) SSL_BIO_free ( bio );
|
|
ep->bio = NULL;
|
|
return noErr;
|
|
}
|
|
|
|
|
|
#pragma mark --Bridge Routines--
|
|
|
|
SSL * SSL_new ( SSL_CTX *ctx ) {
|
|
static MachOWrapper < SSL * (*) ( SSL_CTX * ) > gSSL_new ( gSSLBundle, CFSTR ( "SSL_new" ));
|
|
ASSERT ( gSSL_new.IsValid ());
|
|
return gSSL_new ( ctx );
|
|
}
|
|
|
|
|
|
void SSL_free ( SSL *ssl ) {
|
|
static MachOWrapper < void (*) ( SSL * ) > gSSL_free ( gSSLBundle, CFSTR ( "SSL_free" ));
|
|
ASSERT ( gSSL_free.IsValid ());
|
|
gSSL_free ( ssl );
|
|
}
|
|
|
|
void SSL_set_bio ( SSL *ssl, BIO *rbio, BIO *wbio ) {
|
|
static MachOWrapper < void (*) ( SSL *, BIO *, BIO * ) > gSSL_set_bio ( gSSLBundle, CFSTR ( "SSL_set_bio" ));
|
|
ASSERT ( gSSL_set_bio.IsValid ());
|
|
gSSL_set_bio ( ssl, rbio, wbio );
|
|
}
|
|
|
|
SSL_METHOD *SSLv2_client_method(void) {
|
|
static MachOWrapper < SSL_METHOD * (*) ( void ) > gSSLv2_client_method ( gSSLBundle, CFSTR ( "SSLv2_client_method" ));
|
|
ASSERT ( gSSLv2_client_method.IsValid ());
|
|
return gSSLv2_client_method ();
|
|
}
|
|
|
|
SSL_METHOD *SSLv3_client_method(void) {
|
|
static MachOWrapper < SSL_METHOD * (*) ( void ) > gSSLv3_client_method ( gSSLBundle, CFSTR ( "SSLv3_client_method" ));
|
|
ASSERT ( gSSLv3_client_method.IsValid ());
|
|
return gSSLv3_client_method ();
|
|
}
|
|
|
|
SSL_METHOD *SSLv23_client_method(void) {
|
|
static MachOWrapper < SSL_METHOD * (*) ( void ) > gSSLv23_client_method ( gSSLBundle, CFSTR ( "SSLv23_client_method" ));
|
|
ASSERT ( gSSLv23_client_method.IsValid ());
|
|
return gSSLv23_client_method ();
|
|
}
|
|
|
|
SSL_METHOD *TLSv1_client_method(void) {
|
|
static MachOWrapper < SSL_METHOD * (*) ( void ) > gTLSv1_client_method ( gSSLBundle, CFSTR ( "TLSv1_client_method" ));
|
|
ASSERT ( gTLSv1_client_method.IsValid ());
|
|
return gTLSv1_client_method ();
|
|
}
|
|
|
|
SSL_CTX * SSL_CTX_new ( SSL_METHOD *meth ) {
|
|
static MachOWrapper < SSL_CTX * (*) ( SSL_METHOD * ) > gSSL_CTX_new ( gSSLBundle, CFSTR ( "SSL_CTX_new" ));
|
|
ASSERT ( gSSL_CTX_new.IsValid ());
|
|
return gSSL_CTX_new ( meth );
|
|
}
|
|
|
|
void SSL_CTX_set_verify ( SSL_CTX *ctx,int mode, CertVerifyProc callback ) {
|
|
static MachOWrapper < void (*) ( SSL_CTX *,int , CertVerifyProc ) > gSSL_CTX_set_verify ( gSSLBundle, CFSTR ( "SSL_CTX_set_verify" ));
|
|
ASSERT ( gSSL_CTX_set_verify.IsValid ());
|
|
// We just leak this small block of memory
|
|
void *cb = NULL != callback ? MachOFunctionPointerForCFMFunctionPointer ( callback ) : NULL;
|
|
gSSL_CTX_set_verify ( ctx, mode, (CertVerifyProc) cb );
|
|
}
|
|
|
|
void SSL_CTX_free ( SSL_CTX * ctx ) {
|
|
static MachOWrapper < void (*) ( SSL_CTX * ) > gSSL_CTX_free ( gSSLBundle, CFSTR ( "SSL_CTX_free" ));
|
|
ASSERT ( gSSL_CTX_free.IsValid ());
|
|
gSSL_CTX_free ( ctx );
|
|
}
|
|
|
|
long SSL_CTX_ctrl ( SSL_CTX *ctx, int cmd, long larg, void *parg ) {
|
|
static MachOWrapper < long (*) ( SSL_CTX *ctx, int, long, void * ) > gSSL_CTX_ctrl ( gSSLBundle, CFSTR ( "SSL_CTX_ctrl" ));
|
|
ASSERT ( gSSL_CTX_ctrl.IsValid ());
|
|
return gSSL_CTX_ctrl ( ctx, cmd, larg, parg );
|
|
}
|
|
|
|
void SSL_set_connect_state ( SSL *ssl ) {
|
|
static MachOWrapper < void (*) ( SSL * ) > gSSL_set_connect_state ( gSSLBundle, CFSTR ( "SSL_set_connect_state" ));
|
|
ASSERT ( gSSL_set_connect_state.IsValid ());
|
|
gSSL_set_connect_state ( ssl );
|
|
}
|
|
|
|
int SSL_do_handshake ( SSL * ssl ) {
|
|
static MachOWrapper < int (*) ( SSL * ) > gSSL_do_handshake ( gSSLBundle, CFSTR ( "SSL_do_handshake" ));
|
|
ASSERT ( gSSL_do_handshake.IsValid ());
|
|
return gSSL_do_handshake ( ssl );
|
|
}
|
|
|
|
int SSL_write ( SSL * ssl, const void *buffer, int len ) {
|
|
static MachOWrapper < int (*) ( SSL *, const void *, int ) > gSSL_write ( gSSLBundle, CFSTR ( "SSL_write" ));
|
|
ASSERT ( gSSL_write.IsValid ());
|
|
return gSSL_write ( ssl, buffer, len );
|
|
}
|
|
|
|
int SSL_read ( SSL * ssl, void *buffer, int len ) {
|
|
static MachOWrapper < int (*) ( SSL *, void *, int ) > gSSL_read ( gSSLBundle, CFSTR ( "SSL_read" ));
|
|
ASSERT ( gSSL_read.IsValid ());
|
|
return gSSL_read ( ssl, buffer, len );
|
|
}
|
|
|
|
int SSL_get_error ( SSL *ssl, int ret ) {
|
|
static MachOWrapper < int (*) ( SSL *, int ) > gSSL_get_error ( gSSLBundle, CFSTR ( "SSL_get_error" ));
|
|
ASSERT ( gSSL_get_error.IsValid ());
|
|
return gSSL_get_error ( ssl, ret );
|
|
}
|
|
|
|
|
|
BIO * BIO_new ( BIO_METHOD *type ) {
|
|
static MachOWrapper < BIO * (*) ( BIO_METHOD * ) > gBIO_new ( gSSLBundle, CFSTR ( "BIO_new" ));
|
|
ASSERT ( gBIO_new.IsValid ());
|
|
return gBIO_new ( type );
|
|
}
|
|
|
|
long BIO_int_ctrl ( BIO *bp,int cmd,long larg,int iarg ) {
|
|
static MachOWrapper < long (*) ( BIO *, int, long, int ) > gBIO_int_ctrl ( gSSLBundle, CFSTR ( "BIO_int_ctrl" ));
|
|
ASSERT ( gBIO_int_ctrl.IsValid ());
|
|
return gBIO_int_ctrl ( bp, cmd, larg, iarg );
|
|
}
|
|
|
|
X509 * d2i_X509 ( X509 **px, unsigned char **in, int len ) {
|
|
static MachOWrapper < X509 * (*) ( X509 **, unsigned char **, int ) > gd2i_X509 ( gSSLBundle, CFSTR ( "d2i_X509" ));
|
|
ASSERT ( gd2i_X509.IsValid ());
|
|
return gd2i_X509 ( px, in, len );
|
|
}
|
|
|
|
int SSL_CTX_use_certificate ( SSL_CTX *ctx, X509 *x ) {
|
|
static MachOWrapper < int (*) ( SSL_CTX *, X509 * ) > gSSL_CTX_use_certificate ( gSSLBundle, CFSTR ( "SSL_CTX_use_certificate" ));
|
|
ASSERT ( gSSL_CTX_use_certificate.IsValid ());
|
|
return gSSL_CTX_use_certificate ( ctx, x );
|
|
}
|
|
|
|
X509 * X509_STORE_CTX_get_current_cert ( X509_STORE_CTX *ctx ) {
|
|
static MachOWrapper < X509 * (*) ( X509_STORE_CTX * ) > gX509_STORE_CTX_get_current_cert ( gSSLBundle, CFSTR ( "X509_STORE_CTX_get_current_cert" ));
|
|
ASSERT ( gX509_STORE_CTX_get_current_cert.IsValid ());
|
|
return gX509_STORE_CTX_get_current_cert ( ctx );
|
|
}
|
|
|
|
int X509_STORE_CTX_get_error_depth ( X509_STORE_CTX *ctx ) {
|
|
static MachOWrapper < int (*) ( X509_STORE_CTX * ) > gX509_STORE_CTX_get_error_depth ( gSSLBundle, CFSTR ( "X509_STORE_CTX_get_error_depth" ));
|
|
ASSERT ( gX509_STORE_CTX_get_error_depth.IsValid ());
|
|
return gX509_STORE_CTX_get_error_depth ( ctx );
|
|
}
|
|
|
|
int X509_STORE_CTX_get_error ( X509_STORE_CTX *ctx ) {
|
|
static MachOWrapper < int (*) ( X509_STORE_CTX * ) > gX509_STORE_CTX_get_error ( gSSLBundle, CFSTR ( "X509_STORE_CTX_get_error" ));
|
|
ASSERT ( gX509_STORE_CTX_get_error.IsValid ());
|
|
return gX509_STORE_CTX_get_error ( ctx );
|
|
}
|
|
|
|
X509_NAME * X509_get_subject_name ( X509 *a ) {
|
|
static MachOWrapper < X509_NAME * (*) ( X509 * ) > gX509_get_subject_name ( gSSLBundle, CFSTR ( "X509_get_subject_name" ));
|
|
ASSERT ( gX509_get_subject_name.IsValid ());
|
|
return gX509_get_subject_name ( a );
|
|
}
|
|
|
|
char * X509_NAME_oneline ( X509_NAME *a, char *buf, int size ) {
|
|
static MachOWrapper < char * (*) ( X509_NAME*, char *, int ) > gX509_NAME_oneline ( gSSLBundle, CFSTR ( "X509_NAME_oneline" ));
|
|
ASSERT ( gX509_NAME_oneline.IsValid ());
|
|
return gX509_NAME_oneline ( a, buf, size );
|
|
}
|
|
|
|
int X509_NAME_get_text_by_NID ( X509_NAME *name, int nid, char *buf,int len ) {
|
|
static MachOWrapper < int (*) ( X509_NAME *, int, char *, int ) > gX509_NAME_get_text_by_NID ( gSSLBundle, CFSTR ( "X509_NAME_get_text_by_NID" ));
|
|
ASSERT ( gX509_NAME_get_text_by_NID.IsValid ());
|
|
return gX509_NAME_get_text_by_NID ( name, nid, buf, len );
|
|
}
|
|
|
|
int X509_STORE_add_cert ( X509_STORE *store, X509 *x ) {
|
|
static MachOWrapper < int (*) ( X509_STORE *, X509 * ) > gX509_STORE_add_cert ( gSSLBundle, CFSTR ( "X509_STORE_add_cert" ));
|
|
ASSERT ( gX509_STORE_add_cert.IsValid ());
|
|
return gX509_STORE_add_cert ( store, x );
|
|
}
|
|
|
|
X509_STORE *SSL_CTX_get_cert_store ( SSL_CTX *ctx ) {
|
|
static MachOWrapper < X509_STORE * (*) ( SSL_CTX * ) > gSSL_CTX_get_cert_store ( gSSLBundle, CFSTR ( "SSL_CTX_get_cert_store" ));
|
|
ASSERT ( gSSL_CTX_get_cert_store.IsValid ());
|
|
return gSSL_CTX_get_cert_store ( ctx );
|
|
}
|
|
|
|
static void X509_free ( X509 *x ) {
|
|
static MachOWrapper < void (*) ( X509 * ) > gX509_free ( gSSLBundle, CFSTR ( "X509_free" ));
|
|
ASSERT ( gX509_free.IsValid ());
|
|
return gX509_free ( x );
|
|
}
|
|
|
|
static int X509_cmp ( const X509 *a, const X509 *b ) {
|
|
static MachOWrapper < int (*) ( const X509 *, const X509 * ) > gX509_cmp ( gSSLBundle, CFSTR ( "X509_cmp" ));
|
|
ASSERT ( gX509_cmp.IsValid ());
|
|
return gX509_cmp ( a, b );
|
|
}
|
|
|
|
void SSL_CTX_set_verify_depth ( SSL_CTX *ctx,int depth ) {
|
|
static MachOWrapper < void (*) ( SSL_CTX *, int ) > gSSL_CTX_set_verify_depth ( gSSLBundle, CFSTR ( "SSL_CTX_set_verify_depth" ));
|
|
ASSERT ( gSSL_CTX_set_verify_depth.IsValid ());
|
|
return gSSL_CTX_set_verify_depth ( ctx, depth );
|
|
}
|
|
|
|
void *CRYPTO_malloc ( int num, const char *file, int line ) {
|
|
static MachOWrapper < void * (*) ( int, const char *, int ) > gCRYPTO_malloc ( gSSLBundle, CFSTR ( "CRYPTO_malloc" ));
|
|
ASSERT ( gCRYPTO_malloc.IsValid ());
|
|
return gCRYPTO_malloc ( num, file, line );
|
|
}
|
|
|
|
/*static*/ void CRYPTO_free ( void *p ) {
|
|
static MachOWrapper < void (*) ( void * ) > gCRYPTO_free ( gSSLBundle, CFSTR ( "CRYPTO_free" ));
|
|
ASSERT ( gCRYPTO_free.IsValid ());
|
|
return gCRYPTO_free ( p );
|
|
}
|
|
|
|
static int i2d_X509 ( X509 *x, unsigned char **out ) {
|
|
static MachOWrapper < int (*) ( X509 *, unsigned char ** ) > gi2d_X509 ( gSSLBundle, CFSTR ( "i2d_X509" ));
|
|
ASSERT ( gi2d_X509.IsValid ());
|
|
return gi2d_X509 ( x, out );
|
|
}
|
|
|
|
static int X509_print ( BIO *bp,X509 *x ) {
|
|
static MachOWrapper < int (*) ( BIO *, X509 * ) > gX509_print ( gSSLBundle, CFSTR ( "X509_print" ));
|
|
ASSERT ( gX509_print.IsValid ());
|
|
return gX509_print ( bp, x );
|
|
}
|
|
|
|
BIO_METHOD * BIO_s_mem ( void ) {
|
|
static MachOWrapper < BIO_METHOD * (*) () > gBIO_s_mem ( gSSLBundle, CFSTR ( "BIO_s_mem" ));
|
|
ASSERT ( gBIO_s_mem.IsValid ());
|
|
return gBIO_s_mem ();
|
|
}
|
|
|
|
int BIO_free ( BIO *a ) {
|
|
static MachOWrapper < int (*) ( BIO * ) > gBIO_free ( gSSLBundle, CFSTR ( "BIO_free" ));
|
|
ASSERT ( gBIO_free.IsValid ());
|
|
return gBIO_free ( a );
|
|
}
|
|
|
|
int BIO_gets ( BIO *bp, char *buf, int size ) {
|
|
static MachOWrapper < int (*) ( BIO *, char *, int ) > gBIO_gets ( gSSLBundle, CFSTR ( "BIO_gets" ));
|
|
ASSERT ( gBIO_gets.IsValid ());
|
|
return gBIO_gets ( bp, buf, size );
|
|
}
|
|
|
|
unsigned long SSLeay ( void ) {
|
|
static MachOWrapper < int (*) () > gSSLeay ( gSSLBundle, CFSTR ( "SSLeay" ));
|
|
ASSERT ( gSSLeay.IsValid ());
|
|
return gSSLeay ();
|
|
}
|
|
|
|
|
|
#pragma mark --Glue Routines--
|
|
/************************************************************************
|
|
* MachOFunctionPointerForCFMFunctionPointer - This function allocates a
|
|
* block of MachO glue code which contains the instructions to call CFM
|
|
* routines. Thanks, Marshall! :)
|
|
************************************************************************/
|
|
UInt32 gMachOTempl[6] = {0x3D800000, 0x618C0000, 0x800C0000, 0x804C0004, 0x7C0903A6, 0x4E800420};
|
|
static void *MachOFunctionPointerForCFMFunctionPointer( void *cfmfp ) {
|
|
UInt32 *mfp = (UInt32*) NewPtr( sizeof(gMachOTempl) ); // Must later dispose of allocated memory
|
|
if (mfp) {
|
|
mfp[0] = gMachOTempl [0] | ((UInt32)cfmfp >> 16);
|
|
mfp[1] = gMachOTempl [1] | ((UInt32)cfmfp & 0xFFFF);
|
|
mfp[2] = gMachOTempl [2];
|
|
mfp[3] = gMachOTempl [3];
|
|
mfp[4] = gMachOTempl [4];
|
|
mfp[5] = gMachOTempl [5];
|
|
MakeDataExecutable( mfp, sizeof(gMachOTempl) );
|
|
}
|
|
|
|
return( mfp );
|
|
}
|
|
|
|
/* See <http://www.freebsdchina.org/utils/phpMan.php?parameter=i2d_X509&mode=man>
|
|
i2d_X509() encodes the structure pointed to by x into DER format. If
|
|
out is not NULL is writes the DER encoded data to the buffer at *out,
|
|
and increments it to point after the data just written. If the return
|
|
value is negative an error occurred, otherwise it returns the length of
|
|
the encoded data.
|
|
|
|
For OpenSSL 0.9.7 and later if *out is NULL memory will be allocated
|
|
for a buffer and the encoded data written to it. In this case *out is
|
|
not incremented and it points to the start of the data just written.
|
|
*/
|
|
static int i2d_X509_compat ( X509 *x, unsigned char **out ) {
|
|
ASSERT ( NULL == *out );
|
|
unsigned long vers = SSLeay ();
|
|
|
|
if ( vers >= 0x00907000 ) // OpenSSL 0.9.7 has "the fix"
|
|
return i2d_X509 ( x, out );
|
|
else {
|
|
int len = i2d_X509 ( x, NULL );
|
|
unsigned char *ptr = *out = (unsigned char *) OPENSSL_malloc ( len );
|
|
return i2d_X509 ( x, &ptr );
|
|
}
|
|
|
|
ASSERT ( 0 );
|
|
return 0;
|
|
}
|
|
|
|
|
|
// Create the SSL Bundle on the fly
|
|
static void CreateSSLBundle () {
|
|
MachOWrapper < int (*) ( const char * ) > lSystem ( CFSTR ( "System.framework" ), CFSTR ( "system" ));
|
|
ASSERT ( lSystem.IsValid ());
|
|
|
|
// Make a fake framework that we can call
|
|
// Note that in the center of the framework, where the shared library is supposed to be,
|
|
// we have a symlink to the openSSL shared library, which lives in a "well known place"
|
|
// (and in fact, is a symlink to somewhere else.
|
|
//
|
|
// Yes, this is a horrible hack. Marshall - 08-18-2004
|
|
lSystem ( "mkdir ~/Library/Application\\ Support/Eudora" );
|
|
lSystem ( "mkdir ~/Library/Application\\ Support/Eudora/OpenSSL.framework" );
|
|
lSystem ( "mkdir ~/Library/Application\\ Support/Eudora/OpenSSL.framework/Contents" );
|
|
lSystem ( "mkdir ~/Library/Application\\ Support/Eudora/OpenSSL.framework/Contents/Resources" );
|
|
lSystem ( "mkdir ~/Library/Application\\ Support/Eudora/OpenSSL.framework/Contents/Versions" );
|
|
lSystem ( "mkdir ~/Library/Application\\ Support/Eudora/OpenSSL.framework/Contents/Versions/A" );
|
|
|
|
lSystem ( "ln -s /usr/lib/libssl.dylib ~/Library/Application\\ Support/Eudora/OpenSSL.framework/Contents/Versions/A/OpenSSL" );
|
|
lSystem ( "ln -s Contents/Versions/A/OpenSSL ~/Library/Application\\ Support/Eudora/OpenSSL.framework/OpenSSL" );
|
|
lSystem ( "ln -s A ~/Library/Application\\ Support/Eudora/OpenSSL.framework/Contents/Versions/Current" );
|
|
}
|
|
|
|
OSStatus InitOpenSSL () {
|
|
OSStatus err = noErr;
|
|
FSRef fsRef;
|
|
|
|
// The library should exist at "~/Library/Application Support/Eudora/OpenSSL.framework"
|
|
err = FSFindFolder ( kUserDomain, kApplicationSupportFolderType, kCreateFolder, &fsRef );
|
|
if ( noErr == err ) {
|
|
CFURLRef asURL = CFURLCreateFromFSRef ( NULL, &fsRef );
|
|
ASSERT ( asURL != NULL );
|
|
if ( NULL != asURL ) {
|
|
// CFShow ( asURL );
|
|
CFURLRef sslLibURL = CFURLCreateCopyAppendingPathComponent ( NULL, asURL, CFSTR ( "Eudora/OpenSSL.framework" ), true );
|
|
|
|
ASSERT ( sslLibURL != NULL );
|
|
if ( NULL != sslLibURL ) {
|
|
// CFShow ( sslLibURL );
|
|
gSSLBundle = CFBundleCreate ( NULL, sslLibURL );
|
|
|
|
// If the framework isn't there, create it and try again....
|
|
if ( NULL == gSSLBundle ) {
|
|
CreateSSLBundle ();
|
|
gSSLBundle = CFBundleCreate ( NULL, sslLibURL );
|
|
}
|
|
|
|
if ( NULL == gSSLBundle || !CFBundleLoadExecutable ( gSSLBundle )) {
|
|
err = coreFoundationUnknownErr;
|
|
ASSERT ( false );
|
|
}
|
|
|
|
CFRelease ( sslLibURL );
|
|
}
|
|
|
|
CFRelease ( asURL );
|
|
}
|
|
|
|
if ( noErr == err ) {
|
|
ASSERT ( gSSLBundle != NULL );
|
|
// int SSL_library_init(void );
|
|
MachOWrapper < int (*) ( void ) > lSSL_library_init ( gSSLBundle, CFSTR ( "SSL_library_init" ));
|
|
ASSERT ( lSSL_library_init.IsValid ());
|
|
lSSL_library_init ();
|
|
|
|
// void SSL_load_error_strings(void );
|
|
MachOWrapper < void (*) ( void ) > lSSL_load_error_strings ( gSSLBundle, CFSTR ( "SSL_load_error_strings" ));
|
|
ASSERT ( lSSL_load_error_strings.IsValid ());
|
|
lSSL_load_error_strings ();
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
// Type safety!
|
|
// template < typename FP >
|
|
// inline FP MakeMachOFunctionPointer ( FP cfmProc ) { return (FP) MachOFunctionPointerForCFMFunctionPointer ( cfmProc ); }
|
|
|
|
void InitMachOFunctions () {
|
|
gOTWrite = MachOFunctionPointerForCFMFunctionPointer ( ot_write );
|
|
gOTRead = MachOFunctionPointerForCFMFunctionPointer ( ot_read );
|
|
gOTPuts = MachOFunctionPointerForCFMFunctionPointer ( ot_puts );
|
|
gOTCtrl = MachOFunctionPointerForCFMFunctionPointer ( ot_ctrl );
|
|
gOTNew = MachOFunctionPointerForCFMFunctionPointer ( ot_new );
|
|
gOTFree = MachOFunctionPointerForCFMFunctionPointer ( ot_free );
|
|
}
|
|
|
|
#include <KeychainCore.h>
|
|
#include <KeychainHI.h>
|
|
|
|
#define kCDSACertClass (0x80000000 + 0x1000)
|
|
typedef struct OpaqueSecCertificateRef *SecCertificateRef;
|
|
typedef struct OpaqueSecTrustRef *SecTrustRef;
|
|
typedef struct OpaquePolicySearchRef *SecPolicySearchRef;
|
|
typedef struct OpaqueSecPolicyRef *SecPolicyRef;
|
|
|
|
typedef UInt32 CSSM_CERT_TYPE;
|
|
typedef UInt32 CSSM_CERT_ENCODING;
|
|
|
|
typedef struct cssm_data {
|
|
UInt32 Length; /* in bytes */
|
|
UInt8 *Data;
|
|
} CSSM_DATA, *CSSM_DATA_PTR;
|
|
|
|
#define CSSM_CERT_X_509v3 0x03
|
|
#define CSSM_CERT_ENCODING_DER 0x03
|
|
|
|
#define NID_commonName 13
|
|
|
|
static OSErr OpenSSLAddCertToStore ( SSL_CTX *ctx, CSSM_DATA_PTR cert, const char *hostName ) {
|
|
|
|
X509 *X509Ptr = d2i_X509 ( NULL, &cert->Data, cert->Length );
|
|
if ( NULL != X509Ptr ) {
|
|
X509_STORE *store = SSL_CTX_get_cert_store ( ctx );
|
|
if ( 0 == X509_STORE_add_cert ( store, X509Ptr ))
|
|
ASSERT ( false );
|
|
X509_free ( X509Ptr );
|
|
}
|
|
|
|
return noErr;
|
|
}
|
|
|
|
/* This is used to hold everything. It is used for all certificate
|
|
* validation. Once we have a certificate chain, the 'verify'
|
|
* function is then called to actually check the cert chain. */
|
|
struct my_x509_store_st {
|
|
|
|
/* The following is a cache of trusted certs */
|
|
int cache; /* if true, stash any hits */
|
|
STACK_OF(X509_OBJECT) *objs; /* Cache of all objects */
|
|
|
|
/* These are external lookup methods */
|
|
STACK_OF(X509_LOOKUP) *get_cert_methods;
|
|
|
|
/* The following fields are not used by X509_STORE but are
|
|
* inherited by X509_STORE_CTX when it is initialised.
|
|
*/
|
|
|
|
unsigned long flags; /* Various verify flags */
|
|
int purpose;
|
|
int trust;
|
|
/* Callbacks for various operations */
|
|
int (*verify)(X509_STORE_CTX *ctx); /* called to verify a certificate */
|
|
int (*verify_cb)(int ok,X509_STORE_CTX *ctx); /* error callback */
|
|
int (*get_issuer)(X509 **issuer, X509_STORE_CTX *ctx, X509 *x); /* get issuers cert from ctx */
|
|
int (*check_issued)(X509_STORE_CTX *ctx, X509 *x, X509 *issuer); /* check issued */
|
|
int (*check_revocation)(X509_STORE_CTX *ctx); /* Check revocation status of chain */
|
|
int (*get_crl)(X509_STORE_CTX *ctx, void **crl, X509 *x); /* retrieve CRL */
|
|
int (*check_crl)(X509_STORE_CTX *ctx, void *crl); /* Check CRL validity */
|
|
int (*cert_crl)(X509_STORE_CTX *ctx, void *crl, X509 *x); /* Check certificate against CRL */
|
|
int (*cleanup)(X509_STORE_CTX *ctx);
|
|
|
|
CRYPTO_EX_DATA ex_data;
|
|
int references;
|
|
int depth; /* how deep to look (still unused -- X509_STORE_CTX's depth is used) */
|
|
} /* X509_STORE */;
|
|
|
|
typedef my_x509_store_st MyX509_STORE;
|
|
|
|
struct my_x509_object_st {
|
|
/* one of the above types */
|
|
int type;
|
|
union {
|
|
char *ptr;
|
|
X509 *x509;
|
|
void *crl;
|
|
void *pkey;
|
|
} data;
|
|
};
|
|
typedef my_x509_object_st MyX509_OBJECT;
|
|
|
|
|
|
void DumpCertStore ( X509_STORE *store ) {
|
|
ASSERT ( NULL != store );
|
|
#if 1
|
|
MyX509_STORE *myStore = (MyX509_STORE *) store;
|
|
|
|
int numCerts = myStore->objs->num;
|
|
|
|
/*
|
|
void X509_STORE_set_flags(X509_STORE *ctx, long flags);
|
|
int X509_STORE_set_purpose(X509_STORE *ctx, int purpose);
|
|
int X509_STORE_set_trust(X509_STORE *ctx, int trust);
|
|
*/
|
|
|
|
fprintf ( stderr, "Cert Store contains %d objects\n", numCerts );
|
|
|
|
for ( int i = 0; i < numCerts; ++i ) {
|
|
MyX509_OBJECT *anObj = (MyX509_OBJECT *) myStore->objs->data [ i ];
|
|
if ( 1 == anObj->type ) { // it's a cert!
|
|
X509 *aCert = anObj->data.x509;
|
|
char peer_CN[256];
|
|
|
|
X509_NAME_get_text_by_NID ( X509_get_subject_name ( aCert ),
|
|
NID_commonName, peer_CN, sizeof ( peer_CN ));
|
|
fprintf ( stderr, "Cert %d: '%s'\n", i, peer_CN );
|
|
}
|
|
}
|
|
|
|
fprintf ( stderr, "\n" );
|
|
fflush ( stderr );
|
|
#endif
|
|
}
|
|
|
|
|
|
OSStatus OpenSSLReadCerts ( SSL_CTX *ctx, StringPtr hostName ) {
|
|
OSStatus err = noErr;
|
|
SecKeychainItemRef item = NULL;
|
|
CFArrayRef searchList = NULL;
|
|
SecKeychainSearchRef search = NULL;
|
|
|
|
MachOWrapper<OSStatus (*)(const char *, SecKeychainRef *)>
|
|
SecKeychainOpen ( CFSTR ( "Security.framework" ), CFSTR ( "SecKeychainOpen" ));
|
|
MachOWrapper<OSStatus (*)(CFArrayRef *)>
|
|
SecKeychainCopySearchList ( CFSTR ( "Security.framework" ), CFSTR ( "SecKeychainCopySearchList" ));
|
|
MachOWrapper<OSStatus (*)(CFTypeRef, KCItemClass, const SecKeychainAttributeList *, SecKeychainSearchRef *)>
|
|
SecKeychainSearchCreateFromAttributes ( CFSTR ( "Security.framework" ), CFSTR ( "SecKeychainSearchCreateFromAttributes" ));
|
|
MachOWrapper<OSStatus (*)(SecKeychainSearchRef, SecKeychainItemRef *)>
|
|
SecKeychainSearchCopyNext ( CFSTR ( "Security.framework" ), CFSTR ( "SecKeychainSearchCopyNext" ));
|
|
// I've kind of butchered the prototype here; the second param to SecCertificateGetData is supposed to
|
|
// be a CSSM_DATA record, but that has the same structure as an SSLBuffer, and that's what I need here.
|
|
MachOWrapper<OSStatus (*)(SecKeychainItemRef, CSSM_DATA *)>
|
|
SecCertificateGetData ( CFSTR ( "Security.framework" ), CFSTR ( "SecCertificateGetData" ));
|
|
|
|
// DumpCertStore ( SSL_CTX_get_cert_store ( ctx ));
|
|
|
|
#if 0
|
|
// Make a list of keychains to search
|
|
err = SecKeychainCopySearchList ( &searchList );
|
|
if ( noErr == err ) {
|
|
SecKeychainRef anchors = NULL;
|
|
if ( noErr == SecKeychainOpen ( "/System/Library/Keychains/X509Anchors", &anchors )) {
|
|
CFMutableArrayRef mute = CFArrayCreateMutableCopy ( NULL, CFArrayGetCount ( searchList ) + 1, searchList );
|
|
CFArrayAppendValue ( mute, anchors );
|
|
CFRelease ( anchors );
|
|
CFRelease ( searchList );
|
|
searchList = mute;
|
|
}
|
|
}
|
|
|
|
if ( noErr == err )
|
|
err = SecKeychainSearchCreateFromAttributes ( searchList, kCDSACertClass, NULL, &search );
|
|
#else
|
|
|
|
SecKeychainRef anchors = NULL;
|
|
err = SecKeychainOpen ( "/System/Library/Keychains/X509Anchors", &anchors );
|
|
if ( noErr == err )
|
|
err = SecKeychainSearchCreateFromAttributes ( anchors, kCDSACertClass, NULL, &search );
|
|
#endif
|
|
|
|
if ( err == noErr ) {
|
|
do {
|
|
item = NULL;
|
|
|
|
err = SecKeychainSearchCopyNext ( search, &item );
|
|
|
|
// On a brand new keychain in 10.3, this routine returns a whacked out error code
|
|
// (0x80011109 to be precise) instead of 'errKCItemNotFound' - we ignore this.
|
|
if ( err != noErr )
|
|
err = errKCItemNotFound;
|
|
|
|
if ( err == noErr ) {
|
|
CSSM_DATA derCert;
|
|
// Get the data from the certificate item [ Certs are always stored as DER ]
|
|
err = SecCertificateGetData ( item, &derCert );
|
|
if ( err == noErr )
|
|
err = OpenSSLAddCertToStore ( ctx, &derCert, (const char *) hostName );
|
|
|
|
CFRelease ( item );
|
|
}
|
|
} while ( err == noErr );
|
|
|
|
CFRelease ( search );
|
|
}
|
|
|
|
if ( NULL != anchors ) CFRelease ( anchors );
|
|
if ( NULL != searchList ) CFRelease ( searchList );
|
|
|
|
// DumpCertStore ( SSL_CTX_get_cert_store ( ctx ));
|
|
return err;
|
|
}
|
|
|
|
|
|
Boolean IsCertInKeychain ( X509 *theCert ) {
|
|
Boolean retVal = false;
|
|
OSStatus err = noErr;
|
|
SecKeychainItemRef item = NULL;
|
|
SecKeychainSearchRef search = NULL;
|
|
|
|
MachOWrapper<OSStatus (*)(CFTypeRef, KCItemClass, const SecKeychainAttributeList *, SecKeychainSearchRef *)>
|
|
SecKeychainSearchCreateFromAttributes ( CFSTR ( "Security.framework" ), CFSTR ( "SecKeychainSearchCreateFromAttributes" ));
|
|
MachOWrapper<OSStatus (*)(SecKeychainSearchRef, SecKeychainItemRef *)>
|
|
SecKeychainSearchCopyNext ( CFSTR ( "Security.framework" ), CFSTR ( "SecKeychainSearchCopyNext" ));
|
|
MachOWrapper<OSStatus (*)(SecKeychainItemRef, CSSM_DATA *)>
|
|
SecCertificateGetData ( CFSTR ( "Security.framework" ), CFSTR ( "SecCertificateGetData" ));
|
|
|
|
err = SecKeychainSearchCreateFromAttributes ( NULL, kCDSACertClass, NULL, &search );
|
|
if ( err == noErr ) {
|
|
do {
|
|
item = NULL;
|
|
|
|
err = SecKeychainSearchCopyNext ( search, &item );
|
|
|
|
// On a brand new keychain in 10.3, this routine returns a whacked out error code
|
|
// (0x80011109 to be precise) instead of 'errKCItemNotFound' - we ignore this.
|
|
if ( err != noErr )
|
|
err = errKCItemNotFound;
|
|
|
|
if ( err == noErr ) {
|
|
CSSM_DATA derCert;
|
|
// Get the data from the certificate item [ Certs are always stored as DER ]
|
|
err = SecCertificateGetData ( item, &derCert );
|
|
if ( err == noErr ) {
|
|
X509 *X509Ptr = d2i_X509 ( NULL, &derCert.Data, derCert.Length );
|
|
if ( NULL != X509Ptr ) {
|
|
retVal = 0 == X509_cmp ( theCert, X509Ptr );
|
|
X509_free ( X509Ptr );
|
|
}
|
|
}
|
|
|
|
CFRelease ( item );
|
|
}
|
|
} while ( !retVal && err == noErr );
|
|
|
|
CFRelease ( search );
|
|
}
|
|
|
|
ASSERT ( retVal || errKCItemNotFound == err );
|
|
return retVal;
|
|
}
|
|
|
|
static Handle GetDialogItemHandle ( DialogPtr theDialog, short itemNo ) {
|
|
DialogItemType itemType;
|
|
Handle item;
|
|
Rect box;
|
|
|
|
GetDialogItem ( theDialog, itemNo, &itemType, &item, &box );
|
|
return item;
|
|
}
|
|
|
|
|
|
// All we want to do is to prevent people from typing chars into the edit-text item
|
|
static pascal Boolean XShowFilterProc ( DialogRef theDialog, EventRecord *theEvent, DialogItemIndex *itemHit ) {
|
|
// All non-key events are handled by the standard filter
|
|
if ( theEvent->what != keyDown && theEvent->what != autoKey )
|
|
return StdFilterProc ( theDialog, theEvent, itemHit );
|
|
|
|
// Command keys are fine, too
|
|
if ( theEvent->modifiers & cmdKey )
|
|
return StdFilterProc ( theDialog, theEvent, itemHit );
|
|
|
|
// OK, cancel
|
|
char key = (char) ( theEvent->message & charCodeMask );
|
|
switch ( key ) {
|
|
case kHomeCharCode:
|
|
case kEndCharCode:
|
|
case kPageUpCharCode:
|
|
case kPageDownCharCode:
|
|
case kLeftArrowCharCode:
|
|
case kRightArrowCharCode:
|
|
case kUpArrowCharCode:
|
|
case kDownArrowCharCode:
|
|
return StdFilterProc ( theDialog, theEvent, itemHit );
|
|
|
|
|
|
// For some reason, if I try to let the standard filter proc
|
|
// handle these three keys, then they go into the TextEdit field,
|
|
// instead of doing to the dialog
|
|
case kReturnCharCode:
|
|
case kEnterCharCode:
|
|
*itemHit = kStdOkItemIndex;
|
|
return true;
|
|
|
|
case kEscapeCharCode:
|
|
*itemHit = kStdCancelItemIndex;
|
|
return true;
|
|
|
|
default:
|
|
*itemHit = -1;
|
|
SysBeep ( 1 );
|
|
return true;
|
|
}
|
|
|
|
// Shouldn't ever get here
|
|
return false;
|
|
}
|
|
|
|
OSStatus ShowOpenSSLCertToUser ( X509 *theCert ) {
|
|
OSStatus err = noErr;
|
|
|
|
// Show the alert with the text
|
|
|
|
MyWindowPtr theWin = GetNewMyDialog ( SSL_CERT_DLOG_X, NULL, NULL, (WindowPtr) InFront );
|
|
if ( theWin == NULL )
|
|
return memFullErr;
|
|
|
|
DialogPtr theDialog = GetMyWindowDialogPtr ( theWin );
|
|
|
|
// Set the dialog prompt
|
|
Str255 aString;
|
|
|
|
GetRString ( aString, SSL_CERT_PROMPT );
|
|
SetDialogItemText ( GetDialogItemHandle ( theDialog, 4 ), aString );
|
|
|
|
// Set the certificate information
|
|
#if 1
|
|
Handle certText = GetOpenSSLCertText ( theCert );
|
|
TESetText ( *certText, GetHandleSize ( certText ), GetDialogTextEditHandle ( theDialog ));
|
|
DisposeHandle ( certText );
|
|
#endif
|
|
|
|
// Set the ok and cancel buttons
|
|
SetDialogDefaultItem ( theDialog, kStdOkItemIndex );
|
|
SetDialogCancelItem ( theDialog, kStdCancelItemIndex );
|
|
|
|
// Display the dialog
|
|
ModalFilterUPP filterProc = NewModalFilterUPP ( XShowFilterProc );
|
|
|
|
short dItem;
|
|
Boolean done = false;
|
|
|
|
StartMovableModal (theDialog );
|
|
ShowWindow ( GetDialogWindow ( theDialog ));
|
|
HiliteButtonOne ( theDialog );
|
|
while ( !done ) {
|
|
MovableModalDialog ( theDialog, filterProc, &dItem );
|
|
switch ( dItem ) {
|
|
case kStdOkItemIndex:
|
|
case kStdCancelItemIndex:
|
|
done = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
EndMovableModal ( theDialog );
|
|
MyDisposeDialog ( theDialog );
|
|
DisposeModalFilterUPP ( filterProc );
|
|
|
|
return dItem == kStdOkItemIndex ? noErr : userCanceledErr ;
|
|
}
|
|
|
|
|
|
// How many certs are there in the default keychain?
|
|
|
|
static int CountKCCerts ( SecKeychainRef theKC ) {
|
|
int retVal = 0;
|
|
OSErr err = noErr;
|
|
|
|
MachOWrapper<OSStatus (*)( CFTypeRef keychainOrArray, unsigned long itemClass, const SecKeychainAttributeList *attrList, SecKeychainSearchRef *searchRef )>
|
|
SecKeychainSearchCreateFromAttributes ( CFSTR ( "Security.framework" ), CFSTR ( "SecKeychainSearchCreateFromAttributes" ));
|
|
|
|
SecKeychainSearchRef searchRef;
|
|
if ( noErr == ( err = SecKeychainSearchCreateFromAttributes ( theKC, kCDSACertClass, NULL, &searchRef ))) {
|
|
MachOWrapper<OSStatus (*)( SecKeychainSearchRef searchRef, SecKeychainItemRef *itemRef )>
|
|
SecKeychainSearchCopyNext ( CFSTR ( "Security.framework" ), CFSTR ( "SecKeychainSearchCopyNext" ));
|
|
|
|
SecKeychainItemRef itemRef;
|
|
while ( noErr == ( err = SecKeychainSearchCopyNext ( searchRef, &itemRef ))) {
|
|
retVal++;
|
|
CFRelease ( itemRef );
|
|
}
|
|
|
|
CFRelease ( searchRef );
|
|
}
|
|
|
|
|
|
return retVal;
|
|
}
|
|
|
|
|
|
OSStatus AddOpenSSLCertToKeychain ( X509 *theCert ) {
|
|
OSStatus err = noErr;
|
|
SecCertificateRef certRef;
|
|
|
|
MachOWrapper<OSStatus (*)(const CSSM_DATA *, CSSM_CERT_TYPE, CSSM_CERT_ENCODING, SecCertificateRef *)>
|
|
SecCertificateCreateFromData ( CFSTR ( "Security.framework" ), CFSTR ( "SecCertificateCreateFromData" ));
|
|
|
|
// Create DER data from the X509 *
|
|
CSSM_DATA cert;
|
|
|
|
cert.Data = NULL;
|
|
cert.Length = i2d_X509_compat ( theCert, &cert.Data );
|
|
|
|
// Create the cert from the cert data
|
|
err = SecCertificateCreateFromData ( &cert, CSSM_CERT_X_509v3, CSSM_CERT_ENCODING_DER, &certRef );
|
|
if ( err == noErr ) {
|
|
|
|
MachOWrapper<OSStatus (*)( SecKeychainRef *)>
|
|
SecKeychainCopyDefault ( CFSTR ( "Security.framework" ), CFSTR ( "SecKeychainCopyDefault" ));
|
|
|
|
SecKeychainRef defKC;
|
|
if ( noErr == ( err = SecKeychainCopyDefault ( &defKC ))) {
|
|
MachOWrapper<OSStatus (*)(SecCertificateRef, SecKeychainRef )>
|
|
SecCertificateAddToKeychain ( CFSTR ( "Security.framework" ), CFSTR ( "SecCertificateAddToKeychain" ));
|
|
|
|
int oldCount = CountKCCerts ( defKC );
|
|
int newCount;
|
|
|
|
err = SecCertificateAddToKeychain ( certRef, NULL );
|
|
newCount = CountKCCerts ( defKC );
|
|
ASSERT ( noErr == err && newCount == oldCount + 1 );
|
|
CFRelease ( defKC );
|
|
}
|
|
|
|
CFRelease ( certRef );
|
|
}
|
|
|
|
OPENSSL_free ( cert.Data );
|
|
return err;
|
|
}
|
|
|
|
static OSStatus AppendHandleNL ( Handle h, char *text ) {
|
|
OSStatus err = noErr;
|
|
long oldSize = GetHandleSize ( h );
|
|
long len = strlen ( text );
|
|
text [ len - 1 ] = '\r'; // replace the '\n' that's already there.
|
|
SetHandleSize ( h, oldSize + len );
|
|
if ( noErr == ( err = MemError ()))
|
|
BlockMoveData ( text, *h + oldSize, len );
|
|
return err;
|
|
}
|
|
|
|
|
|
Handle GetOpenSSLCertText ( X509 *theCert ) {
|
|
BIO * bMem = BIO_new ( BIO_s_mem ());
|
|
X509_print ( bMem, theCert );
|
|
|
|
Handle retVal = NewHandle ( 0 );
|
|
char line [ 256 ];
|
|
while ( BIO_gets ( bMem, line, sizeof ( line ))) {
|
|
(void) AppendHandleNL ( retVal, line );
|
|
}
|
|
BIO_free ( bMem );
|
|
return retVal;
|
|
}
|
|
|