Index: generic/tls.c ================================================================== --- generic/tls.c +++ generic/tls.c @@ -1749,10 +1749,122 @@ } /* *------------------------------------------------------------------- * + * TlsLoadClientCAFileFromMemory -- Load certificates from a client + * CA file from VFS into memory. + * + * Results: + * Number of certificates loaded or 0 for none. + * + * Side effects: + * Loads CA certificates + * + *------------------------------------------------------------------- + */ +static int +TlsLoadClientCAFileFromMemory(Tcl_Interp *interp, SSL_CTX *ctx, Tcl_Obj *file) { + BIO *bio = NULL; + X509 *cert = NULL; + X509_STORE *store = NULL; + Tcl_Obj *buf = NULL; + const void *data = NULL; + X509_NAME *name = NULL; + STACK_OF(X509_NAME) *certNames = NULL; + int ret = 0; + Tcl_Size len = 0; + + /* Read file into memory */ + Tcl_Channel in = Tcl_FSOpenFileChannel(interp, file, "r", 0); + if (in == NULL) { + goto cleanup; + } + Tcl_SetChannelOption(interp, in, "-encoding", "binary"); + buf = Tcl_NewObj(); + Tcl_IncrRefCount(buf); + + if (Tcl_ReadChars(in, buf, -1, 0) < 0) { + Tcl_Close(interp, in); + goto cleanup; + } + Tcl_Close(interp, in); + + data = (const void *) Tcl_GetByteArrayFromObj(buf, &len); + bio = BIO_new_mem_buf(data, len); + if (bio == NULL) { + goto cleanup; + } + + /* Where the certs go */ + store = SSL_CTX_get_cert_store(ctx); + if (store == NULL) { + store = X509_STORE_new(); + if (store == NULL) { + goto cleanup; + } + } + + /* Where the CA names go */ + certNames = sk_X509_NAME_new_null(); + if (!certNames) { + goto cleanup; + } + + /* Attempt to load all certs from the PEM file */ + while ((cert = PEM_read_bio_X509(bio, NULL, 0, NULL)) != NULL) { + if (X509_STORE_add_cert(store, cert) == 0) { + X509_free(cert); + ret = 0; + goto cleanup; + } + /* Copy name to stack before certificate gets freed */ + name = X509_get_subject_name(cert); + if (name) { + X509_NAME *name_copy = X509_NAME_dup(name); + if (!name_copy || !sk_X509_NAME_push(certNames, name_copy)) { + X509_free(cert); + ret = 0; + goto cleanup; + } + } + X509_free(cert); + ret ++; + } + + /* At least one cert was added so retain the store and CA list */ + if (ret) { + if (SSL_CTX_get_cert_store(ctx) == NULL) { + SSL_CTX_set_cert_store(ctx, store); + } + SSL_CTX_set_client_CA_list(ctx, certNames); + } + + cleanup: + + if (! ret) { + /* New store is not required */ + if (store != SSL_CTX_get_cert_store(ctx)) { + X509_STORE_free(store); + } + /* Cert names will not be used */ + if (certNames) { + sk_X509_NAME_pop_free(certNames, X509_NAME_free); + } + } + + BIO_free(bio); + + if (buf) + Tcl_DecrRefCount(buf); + + return ret; +} + +/* + *------------------------------------------------------------------- + * * CTX_Init -- construct a SSL_CTX instance * * Results: * A valid SSL_CTX instance or NULL. * @@ -2117,21 +2229,45 @@ Tcl_DStringFree(&ds); } /* Set file of CA certificates in PEM format. */ if (CAfile != NULL) { - if (!SSL_CTX_load_verify_file(ctx, F2N(CAfile, &ds))) { - abort++; - } - Tcl_DStringFree(&ds); - - /* Set list of CAs to send to client when requesting a client certificate */ - STACK_OF(X509_NAME) *certNames = SSL_load_client_CA_file(F2N(CAfile, &ds)); - if (certNames != NULL) { - SSL_CTX_set_client_CA_list(ctx, certNames); - } - Tcl_DStringFree(&ds); + Tcl_Obj *cafileobj = Tcl_NewStringObj(CAfile, -1); + Tcl_IncrRefCount(cafileobj); + + Tcl_Obj *fsinfo = Tcl_FSFileSystemInfo(cafileobj); + if (fsinfo) { + Tcl_IncrRefCount(fsinfo); + + Tcl_Obj *fstype = NULL; + Tcl_ListObjIndex(interp, fsinfo, 0, &fstype); + + if (Tcl_StringMatch("native", Tcl_GetString(fstype))) { + if (!SSL_CTX_load_verify_file(ctx, F2N(CAfile, &ds))) { + abort++; + } + Tcl_DStringFree(&ds); + + /* Set list of CAs to send to client when requesting a client certificate */ + STACK_OF(X509_NAME) *certNames = SSL_load_client_CA_file(F2N(CAfile, &ds)); + if (certNames != NULL) { + SSL_CTX_set_client_CA_list(ctx, certNames); + } + Tcl_DStringFree(&ds); + + } else { + /* Load certificate into memory */ + if (!TlsLoadClientCAFileFromMemory(interp, ctx, cafileobj)) { + abort++; + } + } + Tcl_DecrRefCount(fsinfo); + + } else { + abort++; /* Path is not recognized */ + } + Tcl_DecrRefCount(cafileobj); } #endif } if (abort > 0) {