/* * Copyright (C) 2003-2005 Samuel Mimram * * This file is part of Ocaml-ssl. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /** * Libssl bindings for OCaml. * * @author Samuel Mimram */ /* $Id: ssl_stubs.c 2684 2006-08-29 12:46:05Z smimram $ */ /* * WARNING: because of thread callbacks, all ssl functions should be in * blocking sections. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int client_verify_callback(int, X509_STORE_CTX *); /******************* * Data structures * *******************/ /* Contexts */ #define Ctx_val(v) (*((SSL_CTX**)Data_custom_val(v))) static void finalize_ctx(value block) { SSL_CTX *ctx = Ctx_val(block); caml_enter_blocking_section(); SSL_CTX_free(ctx); caml_leave_blocking_section(); } static struct custom_operations ctx_ops = { "ocaml_ssl_ctx", finalize_ctx, custom_compare_default, custom_hash_default, custom_serialize_default, custom_deserialize_default }; /* Sockets */ #define SSL_val(v) (*((SSL**)Data_custom_val(v))) static void finalize_ssl_socket(value block) { SSL *ssl = SSL_val(block); caml_enter_blocking_section(); SSL_free(ssl); caml_leave_blocking_section(); } static struct custom_operations socket_ops = { "ocaml_ssl_socket", finalize_ssl_socket, custom_compare_default, custom_hash_default, custom_serialize_default, custom_deserialize_default }; /****************** * Initialization * ******************/ CAMLprim value ocaml_ssl_crypto_num_locks(value unit) { return Val_int(CRYPTO_num_locks()); } void locking_function(int mode, int n, const char *file, int line) { value lf; caml_leave_blocking_section(); lf = Field(Field(*caml_named_value("caml_ssl_thread_locking_function"), 0), 0); if (mode & CRYPTO_LOCK) caml_callback2(lf, Val_int(n), Val_int(1)); else caml_callback2(lf, Val_int(n), Val_int(0)); caml_enter_blocking_section(); } unsigned long id_function() { return Int_val(caml_callback(Field(Field(*caml_named_value("caml_ssl_thread_id_function"), 0), 0), Val_unit)); } CAMLprim value ocaml_ssl_init(value use_threads) { SSL_library_init(); SSL_load_error_strings(); if(Int_val(use_threads)) { CRYPTO_set_id_callback(id_function); CRYPTO_set_locking_callback(locking_function); } return Val_unit; } CAMLprim value ocaml_ssl_get_error_string(value unit) { char buf[256]; ERR_error_string_n(ERR_get_error(), buf, sizeof(buf)); return caml_copy_string(buf); } /***************************** * Context-related functions * *****************************/ static SSL_METHOD *get_method(int protocol, int type) { SSL_METHOD *method = NULL; switch (protocol) { case 0: switch (type) { case 0: method = SSLv2_client_method(); break; case 1: method = SSLv2_server_method(); break; case 2: method = SSLv2_method(); break; } break; case 1: switch (type) { case 0: method = SSLv23_client_method(); break; case 1: method = SSLv23_server_method(); break; case 2: method = SSLv23_method(); break; } break; case 2: switch (type) { case 0: method = SSLv3_client_method(); break; case 1: method = SSLv3_server_method(); break; case 2: method = SSLv3_method(); break; } break; case 3: switch (type) { case 0: method = TLSv1_client_method(); break; case 1: method = TLSv1_server_method(); break; case 2: method = TLSv1_method(); break; } break; default: caml_invalid_argument("Unknown method (this should not have happened, please report)."); break; } if (method == NULL) caml_raise_constant(*caml_named_value("ssl_exn_method_error")); return method; } CAMLprim value ocaml_ssl_create_context(value protocol, value type) { value block; SSL_CTX *ctx; SSL_METHOD *method = get_method(Int_val(protocol), Int_val(type)); caml_enter_blocking_section(); ctx = SSL_CTX_new(method); caml_leave_blocking_section(); if (!ctx) caml_raise_constant(*caml_named_value("ssl_exn_context_error")); /* In non-blocking mode, accept a buffer with a different address on a write retry (since the GC may need to move it). In blocking mode, hide SSL_ERROR_WANT_(READ|WRITE) from us. */ SSL_CTX_set_mode(ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_AUTO_RETRY); block = caml_alloc_custom(&ctx_ops, sizeof(SSL_CTX*), 0, 1); Ctx_val(block) = ctx; return block; } CAMLprim value ocaml_ssl_ctx_use_certificate(value context, value cert, value privkey) { CAMLparam3(context, cert, privkey); SSL_CTX *ctx = Ctx_val(context); char *cert_name = String_val(cert); char *privkey_name = String_val(privkey); caml_enter_blocking_section(); if (SSL_CTX_use_certificate_file(ctx, cert_name, SSL_FILETYPE_PEM) <= 0) { caml_leave_blocking_section(); caml_raise_constant(*caml_named_value("ssl_exn_certificate_error")); } if (SSL_CTX_use_PrivateKey_file(ctx, privkey_name, SSL_FILETYPE_PEM) <= 0) { caml_leave_blocking_section(); caml_raise_constant(*caml_named_value("ssl_exn_private_key_error")); } if (!SSL_CTX_check_private_key(ctx)) { caml_leave_blocking_section(); caml_raise_constant(*caml_named_value("ssl_exn_unmatching_keys")); } caml_leave_blocking_section(); CAMLreturn(Val_unit); } CAMLprim value ocaml_ssl_get_verify_result(value socket) { CAMLparam1(socket); int ans; SSL *ssl = SSL_val(socket); caml_enter_blocking_section(); ans = SSL_get_verify_result(ssl); caml_leave_blocking_section(); CAMLreturn(Val_int(ans)); } CAMLprim value ocaml_ssl_get_client_verify_callback_ptr(value unit) { return (value)client_verify_callback; } CAMLprim value ocaml_ssl_ctx_set_verify(value context, value vmode, value vcallback) { CAMLparam3(context, vmode, vcallback); SSL_CTX *ctx = Ctx_val(context); int mode = 0; value mode_tl = vmode; int (*callback) (int, X509_STORE_CTX*) = NULL; if (Is_long(vmode)) mode = SSL_VERIFY_NONE; while (Is_block(mode_tl)) { switch(Int_val(Field(mode_tl, 0))) { case 0: mode |= SSL_VERIFY_PEER; break; case 1: mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT | SSL_VERIFY_PEER; break; case 2: mode |= SSL_VERIFY_CLIENT_ONCE | SSL_VERIFY_PEER; break; default: caml_invalid_argument("mode"); } mode_tl = Field(mode_tl, 1); } if (Is_block(vcallback)) callback = (int(*) (int, X509_STORE_CTX*))Field(vcallback, 0); caml_enter_blocking_section(); SSL_CTX_set_verify(ctx, mode, callback); caml_leave_blocking_section(); CAMLreturn(Val_unit); } CAMLprim value ocaml_ssl_ctx_set_verify_depth(value context, value vdepth) { SSL_CTX *ctx = Ctx_val(context); int depth = Int_val(vdepth); if (depth < 0) caml_invalid_argument("depth"); caml_enter_blocking_section(); SSL_CTX_set_verify_depth(ctx, depth); caml_leave_blocking_section(); return Val_unit; } CAMLprim value ocaml_ssl_ctx_set_client_CA_list_from_file(value context, value vfilename) { CAMLparam2(context, vfilename); SSL_CTX *ctx = Ctx_val(context); char *filename = String_val(vfilename); STACK_OF(X509_NAME) *cert_names; caml_enter_blocking_section(); cert_names = SSL_load_client_CA_file(filename); if (cert_names != 0) SSL_CTX_set_client_CA_list(ctx, cert_names); else { caml_leave_blocking_section(); caml_raise_constant(*caml_named_value("ssl_exn_certificate_error")); } caml_leave_blocking_section(); CAMLreturn(Val_unit); } static int pem_passwd_cb(char *buf, int size, int rwflag, void *userdata) { value s; int len; caml_leave_blocking_section(); s = caml_callback(*((value*)userdata), Val_int(rwflag)); len = caml_string_length(s); assert(len <= size); memcpy(buf, String_val(s), len); caml_enter_blocking_section(); return len; } CAMLprim value ocaml_ssl_ctx_set_default_passwd_cb(value context, value cb) { CAMLparam2(context, cb); SSL_CTX *ctx = Ctx_val(context); value *pcb; /* TODO: this never gets freed or even unregistered */ pcb = malloc(sizeof(value)); *pcb = cb; caml_register_global_root(pcb); caml_enter_blocking_section(); SSL_CTX_set_default_passwd_cb(ctx, pem_passwd_cb); SSL_CTX_set_default_passwd_cb_userdata(ctx, pcb); caml_leave_blocking_section(); CAMLreturn(Val_unit); } /**************************** * Cipher-related functions * ****************************/ CAMLprim value ocaml_ssl_ctx_set_cipher_list(value context, value ciphers_string) { CAMLparam2(context, ciphers_string); SSL_CTX *ctx = Ctx_val(context); char *ciphers = String_val(ciphers_string); if(*ciphers == 0) caml_raise_constant(*caml_named_value("ssl_exn_cipher_error")); caml_enter_blocking_section(); if(SSL_CTX_set_cipher_list(ctx, ciphers) != 1) { caml_leave_blocking_section(); caml_raise_constant(*caml_named_value("ssl_exn_cipher_error")); } caml_leave_blocking_section(); CAMLreturn(Val_unit); } CAMLprim value ocaml_ssl_get_current_cipher(value socket) { CAMLparam1(socket); SSL *ssl = SSL_val(socket); caml_enter_blocking_section(); SSL_CIPHER *cipher = (SSL_CIPHER*)SSL_get_current_cipher(ssl); caml_leave_blocking_section(); if (!cipher) caml_raise_constant(*caml_named_value("ssl_exn_cipher_error")); CAMLreturn((value)cipher); } CAMLprim value ocaml_ssl_get_cipher_description(value vcipher) { char buf[1024]; SSL_CIPHER *cipher = (SSL_CIPHER*)vcipher; caml_enter_blocking_section(); SSL_CIPHER_description(cipher, buf, 1024); caml_leave_blocking_section(); return caml_copy_string(buf); } CAMLprim value ocaml_ssl_get_cipher_name(value vcipher) { const char *name; SSL_CIPHER *cipher = (SSL_CIPHER*)vcipher; caml_enter_blocking_section(); name = SSL_CIPHER_get_name(cipher); caml_leave_blocking_section(); return caml_copy_string(name); } CAMLprim value ocaml_ssl_get_cipher_version(value vcipher) { char *version; SSL_CIPHER *cipher = (SSL_CIPHER*)vcipher; caml_enter_blocking_section(); version = SSL_CIPHER_get_version(cipher); caml_leave_blocking_section(); return caml_copy_string(version); } /********************************* * Certificate-related functions * *********************************/ #define Cert_val(v) (*((X509**)Data_custom_val(v))) static void finalize_cert(value block) { X509 *cert = Cert_val(block); caml_enter_blocking_section(); X509_free(cert); caml_leave_blocking_section(); } static struct custom_operations cert_ops = { "ocaml_ssl_cert", finalize_cert, custom_compare_default, custom_hash_default, custom_serialize_default, custom_deserialize_default }; CAMLprim value ocaml_ssl_read_certificate(value vfilename) { value block; char *filename = String_val(vfilename); X509 *cert = NULL; FILE *fh = NULL; if((fh = fopen(filename, "r")) == NULL) caml_raise_constant(*caml_named_value("ssl_exn_certificate_error")); caml_enter_blocking_section(); if((PEM_read_X509(fh, &cert, 0, 0)) == NULL) { fclose(fh); caml_leave_blocking_section(); caml_raise_constant(*caml_named_value("ssl_exn_certificate_error")); } fclose(fh); caml_leave_blocking_section(); block = caml_alloc_custom(&cert_ops, sizeof(X509*), 0, 1); Cert_val(block) = cert; return block; } CAMLprim value ocaml_ssl_get_certificate(value socket) { CAMLparam1(socket); SSL *ssl = SSL_val(socket); caml_enter_blocking_section(); X509 *cert = SSL_get_peer_certificate(ssl); caml_leave_blocking_section(); if (!cert) caml_raise_constant(*caml_named_value("ssl_exn_certificate_error")); CAMLlocal1(block); block = caml_alloc_final(2, finalize_cert, 0, 1); Store_field(block, 1, (value)cert); CAMLreturn(block); } CAMLprim value ocaml_ssl_get_issuer(value certificate) { CAMLparam1(certificate); X509 *cert = Cert_val(certificate); caml_enter_blocking_section(); char *issuer = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0); caml_leave_blocking_section(); if (!issuer) caml_raise_not_found (); CAMLreturn(caml_copy_string(issuer)); } CAMLprim value ocaml_ssl_get_subject(value certificate) { CAMLparam1(certificate); X509 *cert = Cert_val(certificate); caml_enter_blocking_section(); char *subject = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0); caml_leave_blocking_section(); if (subject == NULL) caml_raise_not_found (); CAMLreturn(caml_copy_string(subject)); } CAMLprim value ocaml_ssl_ctx_load_verify_locations(value context, value ca_file, value ca_path) { CAMLparam3(context, ca_file, ca_path); SSL_CTX *ctx = Ctx_val(context); char *CAfile = String_val(ca_file); char *CApath = String_val(ca_path); if(*CAfile == 0) CAfile = NULL; if(*CApath == 0) CApath = NULL; caml_enter_blocking_section(); if(SSL_CTX_load_verify_locations(ctx, CAfile, CApath) != 1) { caml_leave_blocking_section(); caml_invalid_argument("cafile or capath"); } caml_leave_blocking_section(); CAMLreturn(Val_unit); } /************************* * Operations on sockets * *************************/ CAMLprim value ocaml_ssl_get_file_descr(value socket) { CAMLparam1(socket); SSL *ssl = SSL_val(socket); int fd; caml_enter_blocking_section(); fd = SSL_get_fd(ssl); caml_leave_blocking_section(); CAMLreturn(Val_int(fd)); } CAMLprim value ocaml_ssl_embed_socket(value socket_, value context) { CAMLparam1(context); CAMLlocal1(block); int socket = Int_val(socket_); SSL_CTX *ctx = Ctx_val(context); SSL *ssl; block = caml_alloc_custom(&socket_ops, sizeof(SSL*), 0, 1); if (socket < 0) caml_raise_constant(*caml_named_value("ssl_exn_invalid_socket")); caml_enter_blocking_section(); ssl = SSL_new(ctx); if (!ssl) { caml_leave_blocking_section(); caml_raise_constant(*caml_named_value("ssl_exn_handler_error")); } SSL_set_fd(ssl, socket); caml_leave_blocking_section(); SSL_val(block) = ssl; CAMLreturn(block); } CAMLprim value ocaml_ssl_connect(value socket) { CAMLparam1(socket); int ret; SSL *ssl = SSL_val(socket); caml_enter_blocking_section(); ret = SSL_connect(ssl); caml_leave_blocking_section(); if (ret < 0) { int err; caml_enter_blocking_section(); err = SSL_get_error(ssl, ret); caml_leave_blocking_section(); caml_raise_with_arg(*caml_named_value("ssl_exn_connection_error"), Val_int(err)); } CAMLreturn(Val_unit); } CAMLprim value ocaml_ssl_verify(value socket) { CAMLparam1(socket); SSL *ssl = SSL_val(socket); long ans; caml_enter_blocking_section(); ans = SSL_get_verify_result(ssl); caml_leave_blocking_section(); if (ans != 0) { if (2 <= ans && ans <= 32) caml_raise_with_arg(*caml_named_value("ssl_exn_verify_error"), Val_int(ans - 2)); /* Not very nice, but simple */ else caml_raise_with_arg(*caml_named_value("ssl_exn_verify_error"), Val_int(31)); } CAMLreturn(Val_unit); } CAMLprim value ocaml_ssl_write(value socket, value buffer, value start, value length) { CAMLparam2(socket, buffer); int ret, err; int buflen = Int_val(length); char *buf = malloc(buflen); SSL *ssl = SSL_val(socket); if (Int_val(start) + Int_val(length) > caml_string_length(buffer)) caml_invalid_argument("Buffer too short."); memmove(buf, (char*)String_val(buffer) + Int_val(start), buflen); caml_enter_blocking_section(); ret = SSL_write(ssl, buf, buflen); err = SSL_get_error(ssl, ret); caml_leave_blocking_section(); free(buf); if (err != SSL_ERROR_NONE) caml_raise_with_arg(*caml_named_value("ssl_exn_write_error"), Val_int(err)); CAMLreturn(Val_int(ret)); } CAMLprim value ocaml_ssl_read(value socket, value buffer, value start, value length) { CAMLparam2(socket, buffer); int ret, err; int buflen = Int_val(length); char *buf = malloc(buflen); SSL *ssl = SSL_val(socket); if (Int_val(start) + Int_val(length) > caml_string_length(buffer)) caml_invalid_argument("Buffer too short."); caml_enter_blocking_section(); ret = SSL_read(ssl, buf, buflen); err = SSL_get_error(ssl, ret); if (err != SSL_ERROR_NONE) err = SSL_get_error(ssl, ret); caml_leave_blocking_section(); memmove(((char*)String_val(buffer)) + Int_val(start), buf, buflen); free(buf); if (err != SSL_ERROR_NONE) caml_raise_with_arg(*caml_named_value("ssl_exn_read_error"), Val_int(err)); CAMLreturn(Val_int(ret)); } CAMLprim value ocaml_ssl_accept(value socket) { CAMLparam1(socket); SSL *ssl = SSL_val(socket); int ret, err; caml_enter_blocking_section(); ret = SSL_accept(ssl); if (ret <= 0) { err = SSL_get_error(ssl, ret); caml_leave_blocking_section(); caml_raise_with_arg(*caml_named_value("ssl_exn_accept_error"), Val_int(err)); } caml_leave_blocking_section(); CAMLreturn(Val_unit); } CAMLprim value ocaml_ssl_flush(value socket) { CAMLparam1(socket); SSL *ssl = SSL_val(socket); BIO *bio; int ans; caml_enter_blocking_section(); bio = SSL_get_wbio(ssl); if(bio) { ans = BIO_flush(bio); } caml_leave_blocking_section(); CAMLreturn(Val_unit); } CAMLprim value ocaml_ssl_shutdown(value socket) { CAMLparam1(socket); SSL *ssl = SSL_val(socket); int ret; caml_enter_blocking_section(); ret = SSL_shutdown(ssl); if (!ret) SSL_shutdown(ssl); caml_leave_blocking_section(); /* close(SSL_get_fd(SSL_val(socket))); */ CAMLreturn(Val_unit); } /* ======================================================== */ /* T.F.: Here, we steal the client_verify_callback function from netkit-telnet-ssl-0.17.24+0.1/libtelnet/ssl.c From the original file header: The modifications to support SSLeay were done by Tim Hudson tjh@mincom.oz.au You can do whatever you like with these patches except pretend that you wrote them. Email ssl-users-request@mincom.oz.au to get instructions on how to join the mailing list that discusses SSLeay and also these patches. */ #define ONELINE_NAME(X) X509_NAME_oneline(X, 0, 0) /* Quick translation ... */ #ifndef VERIFY_ERR_UNABLE_TO_GET_ISSUER #define VERIFY_ERR_UNABLE_TO_GET_ISSUER X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT #endif #ifndef VERIFY_ERR_DEPTH_ZERO_SELF_SIGNED_CERT #define VERIFY_ERR_DEPTH_ZERO_SELF_SIGNED_CERT X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT #endif #ifndef VERIFY_OK #define VERIFY_OK X509_V_OK #endif #ifndef VERIFY_ERR_UNABLE_TO_GET_ISSUER #define VERIFY_ERR_UNABLE_TO_GET_ISSUER X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT #endif /* Need to think about this mapping in terms of what the real * equivalent of this actually is. */ #ifndef VERIFY_ROOT_OK #define VERIFY_ROOT_OK VERIFY_OK #endif static int client_verify_callback(int ok, X509_STORE_CTX *ctx) { char *subject, *issuer; int depth, error; char *xs; depth = ctx->error_depth; error = ctx->error; xs = (char *)X509_STORE_CTX_get_current_cert(ctx); subject = issuer = NULL; /* First thing is to have a meaningful name for the current * certificate that is being verified ... and if we cannot * determine that then something is seriously wrong! */ subject=(char*)ONELINE_NAME(X509_get_subject_name((X509*)xs)); if (subject == NULL) { ERR_print_errors_fp(stderr); ok = 0; goto return_time; } issuer = (char*)ONELINE_NAME(X509_get_issuer_name((X509*)xs)); if (issuer == NULL) { ERR_print_errors_fp(stderr); ok = 0; goto return_time; } /* If the user wants us to be chatty about things then this * is a good time to wizz the certificate chain past quickly :-) */ if (1) { fprintf(stderr, "Certificate[%d] subject=%s\n", depth, subject); fprintf(stderr, "Certificate[%d] issuer =%s\n", depth, issuer); fflush(stderr); } /* If the server is using a self signed certificate then * we need to decide if that is good enough for us to * accept ... */ if (error == VERIFY_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) { if (1) { /* Make 100% sure that in secure more we drop the * connection if the server does not have a * real certificate! */ fprintf(stderr,"SSL: rejecting connection - server has a self-signed certificate\n"); fflush(stderr); /* Sometimes it is really handy to be able to debug things * and still get a connection! */ ok = 0; goto return_time; } else { ok = 1; goto return_time; } } /* If we have any form of error in secure mode we reject the connection. */ if (!((error == VERIFY_OK) || (error == VERIFY_ROOT_OK))) { if (1) { fprintf(stderr, "SSL: rejecting connection - error=%d\n", error); if (error == VERIFY_ERR_UNABLE_TO_GET_ISSUER) { fprintf(stderr, "unknown issuer: %s\n", issuer); } else { ERR_print_errors_fp(stderr); } fflush(stderr); ok = 0; goto return_time; } else { /* Be nice and display a lot more meaningful stuff * so that we know which issuer is unknown no matter * what the callers options are ... */ if (error == VERIFY_ERR_UNABLE_TO_GET_ISSUER) { fprintf(stderr, "SSL: unknown issuer: %s\n", issuer); fflush(stderr); } } } return_time: /* Clean up things. */ if (subject) free(subject); if (issuer) free(issuer); return ok; }