aboutsummaryrefslogtreecommitdiffstats
path: root/ossl_ssl.c
diff options
context:
space:
mode:
authorMichal Rokos <m.rokos@sh.cvut.cz>2001-11-16 11:20:13 +0000
committerMichal Rokos <m.rokos@sh.cvut.cz>2001-11-16 11:20:13 +0000
commit8903e757c052d3a99aada758bb628ec135360e60 (patch)
tree5f0e99803c1fc5c6909fd7d325d0f279265b97cf /ossl_ssl.c
downloadruby-openssl-history-8903e757c052d3a99aada758bb628ec135360e60.tar.gz
Initial revision
Diffstat (limited to 'ossl_ssl.c')
-rw-r--r--ossl_ssl.c554
1 files changed, 554 insertions, 0 deletions
diff --git a/ossl_ssl.c b/ossl_ssl.c
new file mode 100644
index 0000000..1d605e4
--- /dev/null
+++ b/ossl_ssl.c
@@ -0,0 +1,554 @@
+/*-
+ * Copyright (c) 2000-2001 GOTOU YUUZOU <gotoyuzo@notwork.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ *
+ * $IPR: ssl.c,v 1.22 2001/09/21 17:35:51 gotoyuzo Exp $
+ */
+/*
+ * $Id$
+ * for 'OpenSSL for Ruby' project modified by Michal Rokos <m.rokos@sh.cvut.cz>
+ */
+#include "ossl.h"
+#include <rubysig.h>
+#include <rubyio.h>
+
+#define numberof(ary) (sizeof(ary)/sizeof((ary)[0]))
+
+#define ssl_def_const(x) rb_define_const(mSSL, #x, INT2FIX(SSL_##x))
+
+#define ssl_get_io(o) rb_ivar_get((o),rb_intern("@io"))
+#define ssl_get_cert(o) rb_ivar_get((o),rb_intern("@cert"))
+#define ssl_get_key(o) rb_ivar_get((o),rb_intern("@key"))
+#define ssl_get_ca(o) rb_ivar_get((o),rb_intern("@ca_cert"))
+#define ssl_get_ca_file(o) rb_ivar_get((o),rb_intern("@ca_file"))
+#define ssl_get_ca_path(o) rb_ivar_get((o),rb_intern("@ca_path"))
+#define ssl_get_timeout(o) rb_ivar_get((o),rb_intern("@timeout"))
+#define ssl_get_verify_mode(o) rb_ivar_get((o),rb_intern("@verify_mode"))
+#define ssl_get_verify_dep(o) rb_ivar_get((o),rb_intern("@verify_depth"))
+#define ssl_get_verify_cb(o) rb_ivar_get((o),rb_intern("@verify_callback"))
+
+#define ssl_set_io(o,v) rb_ivar_set((o),rb_intern("@io"),(v))
+#define ssl_set_cert(o,v) rb_ivar_set((o),rb_intern("@cert"),(v))
+#define ssl_set_key(o,v) rb_ivar_set((o),rb_intern("@key"),(v))
+#define ssl_set_ca(o,v) rb_ivar_set((o),rb_intern("@ca_cert"),(v))
+#define ssl_set_ca_file(o,v) rb_ivar_set((o),rb_intern("@ca_file"),(v))
+#define ssl_set_ca_path(o,v) rb_ivar_set((o),rb_intern("@ca_path"),(v))
+#define ssl_set_timeout(o,v) rb_ivar_set((o),rb_intern("@timeout"),(v))
+#define ssl_set_verify_mode(o,v) rb_ivar_set((o),rb_intern("@verify_mode"),(v))
+#define ssl_set_verify_dep(o,v) rb_ivar_set((o),rb_intern("@verify_depth"),(v))
+#define ssl_set_verify_cb(o,v) rb_ivar_set((o),rb_intern("@verify_callback"),(v))
+
+/*
+ * Classes
+ */
+VALUE cSSLSocket;
+VALUE eSSLError;
+
+/*
+ * List of instance vars
+ */
+char *ssl_attrs[] = {
+ /* "io", */"cert", "key", "ca_cert", "ca_file", "ca_path",
+ "timeout", "verify_mode", "verify_depth", "verify_callback"
+};
+
+/*
+ * Struct
+ */
+typedef struct ssl_st_t{
+ SSL *ssl;
+ SSL_CTX *ctx;
+} ssl_st;
+
+static void
+ssl_shutdown(p)
+ ssl_st *p;
+{
+ if(p->ssl){
+ SSL_shutdown(p->ssl);
+ SSL_clear(p->ssl);
+ }
+}
+
+static void
+ssl_free(p)
+ ssl_st *p;
+{
+ ssl_shutdown(p);
+ SSL_free(p->ssl);
+ SSL_CTX_free(p->ctx);
+ free(p);
+}
+
+static VALUE ssl_verify_callback_proc;
+
+static VALUE ssl_call_callback_proc(VALUE args)
+{
+ VALUE proc, ok, x509stc;
+
+ proc = rb_ary_entry(args, 0);
+ ok = rb_ary_entry(args, 1);
+ x509stc = rb_ary_entry(args, 2);
+
+ return rb_funcall(proc, rb_intern("call"), 2, ok, x509stc);
+}
+
+/*
+ * for rb_rescue in ssl_verify_callback
+ * see below
+ */
+static VALUE ssl_false(VALUE dummy)
+{
+ return Qfalse;
+}
+
+static int ssl_verify_callback(int ok, X509_STORE_CTX *ctx)
+{
+ VALUE x509stc, args, ret;
+
+ if (!NIL_P(ssl_verify_callback_proc)) {
+ x509stc = ossl_x509store_new2(ctx);
+ rb_funcall(x509stc, rb_intern("protect"), 0, NULL);
+ args = rb_ary_new2(3);
+ rb_ary_store(args, 0, ssl_verify_callback_proc);
+ rb_ary_store(args, 1, ok ? Qtrue : Qfalse);
+ rb_ary_store(args, 2, x509stc);
+ ret = rb_rescue(ssl_call_callback_proc, args, ssl_false, Qnil);
+ }
+
+ return (ret == Qtrue) ? 1 : 0;
+}
+
+static void ssl_ctx_setup(VALUE self)
+{
+ ssl_st *p = NULL;
+ X509 *cert = NULL, *ca = NULL;
+ EVP_PKEY *key = NULL;
+ char *ca_path = NULL, *ca_file = NULL;
+ int verify_mode;
+ VALUE val;
+
+ Data_Get_Struct(self, ssl_st, p);
+
+ /* private key may be bundled in certificate file. */
+ val = ssl_get_cert(self);
+ cert = NIL_P(val) ? NULL : ossl_x509_get_X509(val);
+ val = ssl_get_key(self);
+ key = NIL_P(val) ? NULL : ossl_pkey_get_EVP_PKEY(val);
+ if(cert && key){
+ if(!SSL_CTX_use_certificate(p->ctx,cert))
+ rb_raise(eSSLError,"SSL_CTX_use_certificate:%s",ossl_error());
+ if(!SSL_CTX_use_PrivateKey(p->ctx,key))
+ rb_raise(eSSLError,"SSL_CTX_use_PrivateKey:%s",ossl_error());
+ if(!SSL_CTX_check_private_key(p->ctx))
+ rb_raise(eSSLError,"SSL_CTX_check_private_key:%s",ossl_error());
+ }
+
+ val = ssl_get_ca(self);
+ ca = NIL_P(val) ? NULL : ossl_x509_get_X509(val);
+ val = ssl_get_ca_file(self);
+ ca_file = NIL_P(val) ? NULL : RSTRING(val)->ptr;
+ val = ssl_get_ca_path(self);
+ ca_path = NIL_P(val) ? NULL : RSTRING(val)->ptr;
+ if (ca)
+ if(!SSL_CTX_add_client_CA(p->ctx, ca))
+ rb_raise(eSSLError, "%s", ossl_error());
+ if (!SSL_CTX_load_verify_locations(p->ctx, ca_file, ca_path) ||
+ !SSL_CTX_set_default_verify_paths(p->ctx) &&
+ ruby_verbose) {
+ rb_warning("can't set verify locations:%s", ossl_error());
+ }
+
+ val = ssl_get_verify_mode(self);
+ verify_mode = NIL_P(val) ? SSL_VERIFY_NONE : NUM2INT(val);
+ SSL_CTX_set_verify(p->ctx, verify_mode, ssl_verify_callback);
+
+ val = ssl_get_timeout(self);
+ if(!NIL_P(val)) SSL_CTX_set_timeout(p->ctx, NUM2LONG(val));
+
+ val = ssl_get_verify_dep(self);
+ if(!NIL_P(val)) SSL_CTX_set_verify_depth(p->ctx, NUM2LONG(val));
+}
+
+static void ssl_setup(VALUE self)
+{
+ ssl_st *p;
+ VALUE io;
+ OpenFile *fptr;
+
+ Data_Get_Struct(self, ssl_st, p);
+ if(!p->ssl){
+ io = ssl_get_io(self);
+ GetOpenFile(io, fptr);
+ rb_io_check_readable(fptr);
+ rb_io_check_writable(fptr);
+ if((p->ssl = SSL_new(p->ctx)) == NULL)
+ rb_raise(eSSLError, "SSL_new:%s", ossl_error());
+ SSL_set_fd(p->ssl, fileno(fptr->f));
+ }
+}
+
+static VALUE
+ssl_s_new(argc, argv, klass)
+ int argc;
+ VALUE *argv;
+ VALUE klass;
+{
+ VALUE obj;
+ ssl_st *p;
+
+ obj = Data_Make_Struct(klass, ssl_st, 0, ssl_free, p);
+ memset(p, 0, sizeof(ssl_st));
+ if((p->ctx = SSL_CTX_new(SSLv23_method())) == NULL)
+ rb_raise(eSSLError, "SSL_CTX_new:%s", ossl_error());
+ SSL_CTX_set_options(p->ctx, SSL_OP_ALL);
+
+ rb_obj_call_init(obj, argc, argv);
+
+ return obj;
+}
+
+static VALUE ssl_initialize(int argc, VALUE *argv, VALUE self)
+{
+ VALUE io, key, cert;
+
+ switch (rb_scan_args(argc, argv, "12", &io, &cert, &key)) {
+ case 3:
+ if (!NIL_P(key))
+ OSSL_Check_Type(key, cPKey);
+ ssl_set_key(self, key);
+ case 2:
+ if (!NIL_P(cert))
+ OSSL_Check_Type(cert, cX509Certificate);
+ ssl_set_cert(self, cert);
+ case 1:
+ if(!NIL_P(io))
+ Check_Type(io, T_FILE);
+ ssl_set_io(self, io);
+ }
+
+ return self;
+}
+
+static VALUE ssl_connect(VALUE self)
+{
+ ssl_st *p;
+
+ Data_Get_Struct(self, ssl_st, p);
+ ssl_ctx_setup(self);
+ ssl_setup(self);
+ rb_thread_critical = 1;
+ ssl_verify_callback_proc = ssl_get_verify_cb(self);
+ if(SSL_connect(p->ssl) <= 0){
+ rb_thread_critical = 0;
+ rb_raise(eSSLError, "SSL_connect:%s", ossl_error());
+ }
+ rb_thread_critical = 0;
+
+ return self;
+}
+
+static VALUE
+ssl_accept(self)
+ VALUE self;
+{
+ ssl_st *p;
+
+ Data_Get_Struct(self, ssl_st, p);
+ ssl_ctx_setup(self);
+ ssl_setup(self);
+ rb_thread_critical = 1;
+ ssl_verify_callback_proc = ssl_get_verify_cb(self);
+ if(SSL_accept(p->ssl) <= 0){
+ rb_thread_critical = 0;
+ rb_raise(eSSLError, "SSL_accept:%s", ossl_error());
+ }
+ rb_thread_critical = 0;
+
+ return self;
+}
+
+static VALUE
+ssl_read(self, len)
+ VALUE self, len;
+{
+ ssl_st *p;
+ size_t ilen, nread = 0;
+ char *buf;
+ VALUE str;
+ int iostate = 1;
+ OpenFile *fptr;
+
+ Data_Get_Struct(self, ssl_st, p);
+ ilen = NUM2INT(len);
+ str = rb_str_new(0, ilen);
+
+ if(p->ssl){
+ nread = SSL_read(p->ssl, RSTRING(str)->ptr, RSTRING(str)->len);
+ if(nread < 0) rb_raise(eSSLError, "SSL_read:%s", ossl_error());
+ }
+ else{
+ if(ruby_verbose) rb_warning("SSL session is not started yet.");
+ GetOpenFile(ssl_get_io(self), fptr);
+ rb_io_check_readable(fptr);
+ TRAP_BEG;
+ nread = read(fileno(fptr->f), RSTRING(str)->ptr, RSTRING(str)->len);
+ TRAP_END;
+ if(nread < 0) rb_raise(eSSLError, "read:%s", strerror(errno));
+ }
+ if(nread == 0) rb_raise(rb_eEOFError, "End of file reached");
+
+ RSTRING(str)->len = nread;
+ RSTRING(str)->ptr[nread] = 0;
+ OBJ_TAINT(str);
+
+ return str;
+}
+
+static VALUE
+ssl_write(self, str)
+ VALUE self, str;
+{
+ ssl_st *p;
+ size_t nwrite = 0;
+ OpenFile *fptr;
+ FILE *fp;
+
+ Data_Get_Struct(self, ssl_st, p);
+ if(TYPE(str) != T_STRING)
+ str = rb_obj_as_string(str);
+
+ if(p->ssl){
+ nwrite = SSL_write(p->ssl, RSTRING(str)->ptr, RSTRING(str)->len);
+ if(nwrite < 0) rb_raise(eSSLError, "SSL_write:%s", ossl_error());
+ }
+ else{
+ if(ruby_verbose) rb_warning("SSL session is not started yet.");
+ GetOpenFile(ssl_get_io(self), fptr);
+ rb_io_check_writable(fptr);
+ fp = GetWriteFile(fptr);
+ nwrite = write(fileno(fp), RSTRING(str)->ptr, RSTRING(str)->len);
+ if(nwrite < 0) rb_raise(eSSLError, "write:%s", strerror(errno));
+ }
+
+ return INT2NUM(nwrite);
+}
+
+static VALUE
+ssl_close(self)
+ VALUE self;
+{
+ ssl_st *p;
+
+ Data_Get_Struct(self, ssl_st, p);
+ ssl_shutdown(p);
+ return Qnil;
+}
+
+static VALUE
+ssl_get_certificate(self)
+ VALUE self;
+{
+ ssl_st *p;
+ X509 *cert = NULL;
+
+ Data_Get_Struct(self, ssl_st, p);
+ if(!p->ssl){
+ if(ruby_verbose) rb_warning("SSL session is not started yet.");
+ return Qnil;
+ }
+
+ if((cert = SSL_get_certificate(p->ssl)) == NULL) return Qnil;
+
+ return ossl_x509_new2(cert);
+}
+
+static VALUE
+ssl_get_peer_certificate(self)
+ VALUE self;
+{
+ ssl_st *p;
+ X509 *cert = NULL;
+
+ Data_Get_Struct(self, ssl_st, p);
+ if(!p->ssl){
+ if(ruby_verbose) rb_warning("SSL session is not started yet.");
+ return Qnil;
+ }
+
+ if((cert = SSL_get_peer_certificate(p->ssl)) == NULL) return Qnil;
+
+ return ossl_x509_new2(cert);
+}
+
+static VALUE
+ssl_cipher_to_ary(cipher)
+ SSL_CIPHER *cipher;
+{
+ VALUE ary;
+ int bits, alg_bits;
+
+ ary = rb_ary_new2(4);
+ rb_ary_push(ary, rb_str_new2(SSL_CIPHER_get_name(cipher)));
+ rb_ary_push(ary, rb_str_new2(SSL_CIPHER_get_version(cipher)));
+ bits = SSL_CIPHER_get_bits(cipher, &alg_bits);
+ rb_ary_push(ary, INT2FIX(bits));
+ rb_ary_push(ary, INT2FIX(alg_bits));
+
+ return ary;
+}
+
+static VALUE
+ssl_get_cipher(self)
+ VALUE self;
+{
+ ssl_st *p;
+ SSL_CIPHER *cipher;
+
+ Data_Get_Struct(self, ssl_st, p);
+ if(!p->ssl){
+ if(ruby_verbose) rb_warning("SSL session is not started yet.");
+ return Qnil;
+ }
+ cipher = SSL_get_current_cipher(p->ssl);
+
+ return ssl_cipher_to_ary(cipher);
+}
+
+static VALUE
+ssl_get_ciphers(self)
+ VALUE self;
+{
+ ssl_st *p;
+ STACK_OF(SSL_CIPHER) *ciphers;
+ SSL_CIPHER *cipher;
+ VALUE ary;
+ int i;
+
+ Data_Get_Struct(self, ssl_st, p);
+ if(!p->ctx){
+ if(ruby_verbose) rb_warning("SSL_CTX is not initialized.");
+ return Qnil;
+ }
+ ciphers = p->ctx->cipher_list;
+ ary = rb_ary_new();
+ if(ciphers){
+ for(i = 0; i < sk_num((STACK*)ciphers); i++){
+ cipher = (SSL_CIPHER*)sk_value((STACK*)ciphers, i);
+ rb_ary_push(ary, ssl_cipher_to_ary(cipher));
+ }
+ }
+ return ary;
+}
+
+static VALUE
+ssl_set_ciphers(self, v)
+ VALUE self, v;
+{
+ ssl_st *p;
+ VALUE str, s, elem;
+ int i;
+
+ Data_Get_Struct(self, ssl_st, p);
+ if(!p->ctx){
+ rb_raise(eSSLError, "SSL_CTX is not initialized.");
+ return Qnil;
+ }
+
+ if(TYPE(v) == T_STRING) str = v;
+ else if(TYPE(v) == T_ARRAY){
+ str = rb_str_new2("");
+ for(i = 0; i < RARRAY(v)->len; i++){
+ elem = rb_ary_entry(v, i);
+ if(TYPE(elem) == T_ARRAY) elem = rb_ary_entry(elem, 0);
+ elem = rb_obj_as_string(elem);
+ rb_str_append(str, elem);
+ if(i < RARRAY(v)->len-1) rb_str_cat2(str, ":");
+ }
+ }
+ else str = rb_obj_as_string(v);
+
+ if(!SSL_CTX_set_cipher_list(p->ctx, RSTRING(str)->ptr))
+ rb_raise(eSSLError, "SSL_CTX_set_ciphers:%s", ossl_error());
+
+ return Qnil;
+}
+
+static VALUE
+ssl_get_state(self)
+ VALUE self;
+{
+ ssl_st *p;
+ VALUE ret;
+
+ Data_Get_Struct(self, ssl_st, p);
+ if(!p->ssl){
+ if(ruby_verbose) rb_warning("SSL session is not started yet.");
+ return Qnil;
+ }
+ ret = rb_str_new2(SSL_state_string(p->ssl));
+ if(ruby_verbose){
+ rb_str_cat2(ret, ": ");
+ rb_str_cat2(ret, SSL_state_string_long(p->ssl));
+ }
+ return ret;
+}
+
+void Init_ssl(VALUE mSSL)
+{
+ int i;
+
+ /* module SSL */
+ rb_define_const(mSSL, "VERSION", rb_str_new2("0.3.3"));
+ rb_define_const(mSSL, "OPENSSL_VERSION", rb_str_new2(OPENSSL_VERSION_TEXT));
+
+ /* class SSLError */
+ eSSLError = rb_define_class_under(mSSL, "Error", rb_eStandardError);
+
+ /* class SSLSocket */
+ cSSLSocket = rb_define_class_under(mSSL, "SSLSocket", rb_cObject);
+ rb_define_singleton_method(cSSLSocket, "new", ssl_s_new, -1);
+ rb_define_method(cSSLSocket, "initialize", ssl_initialize, -1);
+ rb_define_method(cSSLSocket, "connect", ssl_connect, 0);
+ rb_define_method(cSSLSocket, "accept", ssl_accept, 0);
+ rb_define_method(cSSLSocket, "sysread", ssl_read, 1);
+ rb_define_method(cSSLSocket, "syswrite", ssl_write, 1);
+ rb_define_method(cSSLSocket, "sysclose", ssl_close, 0);
+ rb_define_method(cSSLSocket, "cert", ssl_get_certificate, 0);
+ rb_define_method(cSSLSocket, "peer_cert", ssl_get_peer_certificate, 0);
+ rb_define_method(cSSLSocket, "cipher", ssl_get_cipher, 0);
+ rb_define_method(cSSLSocket, "ciphers", ssl_get_ciphers, 0);
+ rb_define_method(cSSLSocket, "ciphers=", ssl_set_ciphers, 1);
+ rb_define_method(cSSLSocket, "state", ssl_get_state, 0);
+ for(i = 0; i < numberof(ssl_attrs); i++)
+ rb_attr(cSSLSocket, rb_intern(ssl_attrs[i]), 1, 1, Qfalse);
+ rb_attr(cSSLSocket, rb_intern("io"), 1, 0, Qfalse);
+ rb_define_alias(cSSLSocket, "to_io", "io");
+
+ ssl_def_const(VERIFY_NONE);
+ ssl_def_const(VERIFY_PEER);
+ ssl_def_const(VERIFY_FAIL_IF_NO_PEER_CERT);
+ ssl_def_const(VERIFY_CLIENT_ONCE);
+}
+