diff options
97 files changed, 30662 insertions, 7 deletions
diff --git a/ext/openssl/Makefile b/ext/openssl/Makefile new file mode 100644 index 00000000..55cfcb08 --- /dev/null +++ b/ext/openssl/Makefile @@ -0,0 +1,324 @@ + +SHELL = /bin/sh + +# V=0 quiet, V=1 verbose. other values don't work. +V = 0 +Q1 = $(V:1=) +Q = $(Q1:0=@) +ECHO1 = $(V:1=@:) +ECHO = $(ECHO1:0=@echo) +NULLCMD = : + +#### Start of system configuration section. #### +top_srcdir = $(topdir)/. +srcdir = $(top_srcdir)/ext/openssl +topdir = ../.. +hdrdir = $(top_srcdir)/include +arch_hdrdir = $(extout)/include/$(arch) +PATH_SEPARATOR = : +VPATH = $(srcdir):$(arch_hdrdir)/ruby:$(hdrdir)/ruby +RUBYLIB = +RUBYOPT = - +prefix = $(DESTDIR)/usr/local +rubysitearchprefix = $(rubylibprefix)/$(sitearch) +rubyarchprefix = $(rubylibprefix)/$(arch) +rubylibprefix = $(libdir)/$(RUBY_BASE_NAME) +exec_prefix = $(prefix) +vendorarchhdrdir = $(vendorhdrdir)/$(sitearch) +sitearchhdrdir = $(sitehdrdir)/$(sitearch) +rubyarchhdrdir = $(rubyhdrdir)/$(arch) +vendorhdrdir = $(rubyhdrdir)/vendor_ruby +sitehdrdir = $(rubyhdrdir)/site_ruby +rubyhdrdir = $(includedir)/$(RUBY_VERSION_NAME) +vendorarchdir = $(vendorlibdir)/$(sitearch) +vendorlibdir = $(vendordir)/$(ruby_version) +vendordir = $(rubylibprefix)/vendor_ruby +sitearchdir = $(sitelibdir)/$(sitearch) +sitelibdir = $(sitedir)/$(ruby_version) +sitedir = $(rubylibprefix)/site_ruby +rubyarchdir = $(rubylibdir)/$(arch) +rubylibdir = $(rubylibprefix)/$(ruby_version) +sitearchincludedir = $(includedir)/$(sitearch) +archincludedir = $(includedir)/$(arch) +sitearchlibdir = $(libdir)/$(sitearch) +archlibdir = $(libdir)/$(arch) +ridir = $(datarootdir)/$(RI_BASE_NAME) +mandir = $(datarootdir)/man +localedir = $(datarootdir)/locale +libdir = $(exec_prefix)/lib +psdir = $(docdir) +pdfdir = $(docdir) +dvidir = $(docdir) +htmldir = $(docdir) +infodir = $(datarootdir)/info +docdir = $(datarootdir)/doc/$(PACKAGE) +oldincludedir = $(DESTDIR)/usr/include +includedir = $(prefix)/include +localstatedir = $(prefix)/var +sharedstatedir = $(prefix)/com +sysconfdir = $(prefix)/etc +datadir = $(datarootdir) +datarootdir = $(prefix)/share +libexecdir = $(exec_prefix)/libexec +sbindir = $(exec_prefix)/sbin +bindir = $(exec_prefix)/bin +archdir = $(rubyarchdir) + + +CC = clang +CXX = g++ +LIBRUBY = $(LIBRUBY_A) +LIBRUBY_A = lib$(RUBY_SO_NAME)-static.a +LIBRUBYARG_SHARED = +LIBRUBYARG_STATIC = -l$(RUBY_SO_NAME)-static -framework CoreFoundation +empty = +OUTFLAG = -o $(empty) +COUTFLAG = -o $(empty) + +RUBY_EXTCONF_H = extconf.h +cflags = $(optflags) $(debugflags) $(warnflags) +optflags = -O3 -fno-fast-math +debugflags = -ggdb3 +warnflags = -Wall -Wextra -Wno-unused-parameter -Wno-parentheses -Wno-long-long -Wno-missing-field-initializers -Wunused-variable -Wpointer-arith -Wwrite-strings -Wdeclaration-after-statement -Wshorten-64-to-32 -Wimplicit-function-declaration -Wdivision-by-zero -Wdeprecated-declarations -Wextra-tokens +CCDLFLAGS = -fno-common +CFLAGS = $(CCDLFLAGS) $(cflags) -pipe $(ARCH_FLAG) +INCFLAGS = -I. -I$(arch_hdrdir) -I$(hdrdir) -I$(srcdir) -I/usr/local/Cellar/openssl/1.0.1j/include +DEFS = +CPPFLAGS = -DRUBY_EXTCONF_H=\"$(RUBY_EXTCONF_H)\" -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT $(DEFS) $(cppflags) +CXXFLAGS = $(CCDLFLAGS) $(cxxflags) $(ARCH_FLAG) +ldflags = -L. -fstack-protector -L/usr/local/lib -L/usr/local/Cellar/openssl/1.0.1j/lib +dldflags = -Wl,-undefined,dynamic_lookup -Wl,-multiply_defined,suppress +ARCH_FLAG = +DLDFLAGS = $(ldflags) $(dldflags) $(ARCH_FLAG) +LDSHARED = $(CC) -dynamic -bundle +LDSHAREDXX = $(CXX) -dynamic -bundle +AR = ar +EXEEXT = + +RUBY_INSTALL_NAME = $(RUBY_BASE_NAME) +RUBY_SO_NAME = ruby +RUBYW_INSTALL_NAME = +RUBY_VERSION_NAME = $(RUBY_BASE_NAME)-$(ruby_version) +RUBYW_BASE_NAME = rubyw +RUBY_BASE_NAME = ruby + +arch = x86_64-darwin13 +sitearch = $(arch) +ruby_version = 2.2.0 +ruby = $(topdir)/miniruby -I'$(topdir)' -I'$(top_srcdir)/lib' -I'$(extout)/$(arch)' -I'$(extout)/common' +RUBY = $(ruby) +ruby_headers = $(hdrdir)/ruby.h $(hdrdir)/ruby/ruby.h $(hdrdir)/ruby/defines.h $(hdrdir)/ruby/missing.h $(hdrdir)/ruby/intern.h $(hdrdir)/ruby/st.h $(hdrdir)/ruby/subst.h $(arch_hdrdir)/ruby/config.h $(RUBY_EXTCONF_H) + +RM = rm -f +RM_RF = $(RUBY) -run -e rm -- -rf +RMDIRS = rmdir --ignore-fail-on-non-empty -p +MAKEDIRS = /usr/local/opt/coreutils/libexec/gnubin/mkdir -p +INSTALL = /usr/local/opt/coreutils/libexec/gnubin/install -c +INSTALL_PROG = $(INSTALL) -m 0755 +INSTALL_DATA = $(INSTALL) -m 644 +COPY = cp +TOUCH = exit > + +#### End of system configuration section. #### + +preload = +THREAD_MODEL = pthread + +libpath = . $(topdir) +LIBPATH = -L. -L$(topdir) +DEFFILE = + +CLEANFILES = mkmf.log +DISTCLEANFILES = +DISTCLEANDIRS = + +extout = $(topdir)/.ext +extout_prefix = $(extout)$(target_prefix)/ +target_prefix = +LOCAL_LIBS = +LIBS = -lssl -lcrypto -lpthread -lgmp -ldl -lobjc +ORIG_SRCS = openssl_missing.c ossl.c ossl_asn1.c ossl_bio.c ossl_bn.c ossl_cipher.c ossl_config.c ossl_digest.c ossl_engine.c ossl_hmac.c ossl_ns_spki.c ossl_ocsp.c ossl_pkcs12.c ossl_pkcs5.c ossl_pkcs7.c ossl_pkey.c ossl_pkey_dh.c ossl_pkey_dsa.c ossl_pkey_ec.c ossl_pkey_rsa.c ossl_rand.c ossl_ssl.c ossl_ssl_session.c ossl_x509.c ossl_x509attr.c ossl_x509cert.c ossl_x509crl.c ossl_x509ext.c ossl_x509name.c ossl_x509req.c ossl_x509revoked.c ossl_x509store.c +SRCS = $(ORIG_SRCS) +OBJS = openssl_missing.o ossl.o ossl_asn1.o ossl_bio.o ossl_bn.o ossl_cipher.o ossl_config.o ossl_digest.o ossl_engine.o ossl_hmac.o ossl_ns_spki.o ossl_ocsp.o ossl_pkcs12.o ossl_pkcs5.o ossl_pkcs7.o ossl_pkey.o ossl_pkey_dh.o ossl_pkey_dsa.o ossl_pkey_ec.o ossl_pkey_rsa.o ossl_rand.o ossl_ssl.o ossl_ssl_session.o ossl_x509.o ossl_x509attr.o ossl_x509cert.o ossl_x509crl.o ossl_x509ext.o ossl_x509name.o ossl_x509req.o ossl_x509revoked.o ossl_x509store.o +HDRS = $(srcdir)/extconf.h $(srcdir)/openssl_missing.h $(srcdir)/ossl.h $(srcdir)/ossl_asn1.h $(srcdir)/ossl_bio.h $(srcdir)/ossl_bn.h $(srcdir)/ossl_cipher.h $(srcdir)/ossl_config.h $(srcdir)/ossl_digest.h $(srcdir)/ossl_engine.h $(srcdir)/ossl_hmac.h $(srcdir)/ossl_ns_spki.h $(srcdir)/ossl_ocsp.h $(srcdir)/ossl_pkcs12.h $(srcdir)/ossl_pkcs5.h $(srcdir)/ossl_pkcs7.h $(srcdir)/ossl_pkey.h $(srcdir)/ossl_rand.h $(srcdir)/ossl_ssl.h $(srcdir)/ossl_version.h $(srcdir)/ossl_x509.h $(srcdir)/ruby_missing.h +TARGET = openssl +TARGET_NAME = openssl +TARGET_ENTRY = Init_$(TARGET_NAME) +DLLIB = $(TARGET).bundle +EXTSTATIC = +STATIC_LIB = + +TIMESTAMP_DIR = $(extout)/.timestamp +BINDIR = $(extout)/bin +RUBYCOMMONDIR = $(extout)/common +RUBYLIBDIR = $(RUBYCOMMONDIR)$(target_prefix) +RUBYARCHDIR = $(extout)/$(arch)$(target_prefix) +HDRDIR = $(extout)/include/ruby$(target_prefix) +ARCHHDRDIR = $(extout)/include/$(arch)/ruby$(target_prefix) + +TARGET_SO = $(RUBYARCHDIR)/$(DLLIB) +CLEANLIBS = $(RUBYARCHDIR)/$(TARGET).bundle +CLEANOBJS = *.o *.bak + +all: install +static: all +.PHONY: all install static install-so install-rb +.PHONY: clean clean-so clean-static clean-rb + +clean-static:: +clean-rb-default:: +clean-rb:: +clean-so:: +clean: clean-so clean-static clean-rb-default clean-rb + -$(Q)$(RM) $(CLEANLIBS) $(CLEANOBJS) $(CLEANFILES) .*.time + +distclean-rb-default:: +distclean-rb:: +distclean-so:: +distclean-static:: +distclean: clean distclean-so distclean-static distclean-rb-default distclean-rb + -$(Q)$(RM) Makefile $(RUBY_EXTCONF_H) conftest.* mkmf.log + -$(Q)$(RM) core ruby$(EXEEXT) *~ $(DISTCLEANFILES) + -$(Q)$(RMDIRS) $(DISTCLEANDIRS) 2> /dev/null || true + +realclean: distclean +install: install-so install-rb + +install-so: $(RUBYARCHDIR)/$(DLLIB) +clean-so:: + -$(Q)$(RM) $(RUBYARCHDIR)/$(DLLIB) + -$(Q)$(RMDIRS) $(RUBYARCHDIR) 2> /dev/null || true +clean-static:: + -$(Q)$(RM) $(STATIC_LIB) +install-rb: pre-install-rb install-rb-default +install-rb-default: pre-install-rb-default +pre-install-rb: Makefile +pre-install-rb-default: Makefile +pre-install-rb-default: $(TIMESTAMP_DIR)/.RUBYLIBDIR.-.openssl.time +install-rb-default: $(RUBYLIBDIR)/openssl/bn.rb +$(RUBYLIBDIR)/openssl/bn.rb: $(srcdir)/lib/openssl/bn.rb $(TIMESTAMP_DIR)/.RUBYLIBDIR.-.openssl.time + $(Q) $(COPY) $(srcdir)/lib/openssl/bn.rb $(@D) +clean-rb-default:: + -$(Q)$(RM) $(RUBYLIBDIR)/openssl/bn.rb +install-rb-default: $(RUBYLIBDIR)/openssl/buffering.rb +$(RUBYLIBDIR)/openssl/buffering.rb: $(srcdir)/lib/openssl/buffering.rb $(TIMESTAMP_DIR)/.RUBYLIBDIR.-.openssl.time + $(Q) $(COPY) $(srcdir)/lib/openssl/buffering.rb $(@D) +clean-rb-default:: + -$(Q)$(RM) $(RUBYLIBDIR)/openssl/buffering.rb +install-rb-default: $(RUBYLIBDIR)/openssl/cipher.rb +$(RUBYLIBDIR)/openssl/cipher.rb: $(srcdir)/lib/openssl/cipher.rb $(TIMESTAMP_DIR)/.RUBYLIBDIR.-.openssl.time + $(Q) $(COPY) $(srcdir)/lib/openssl/cipher.rb $(@D) +clean-rb-default:: + -$(Q)$(RM) $(RUBYLIBDIR)/openssl/cipher.rb +install-rb-default: $(RUBYLIBDIR)/openssl/config.rb +$(RUBYLIBDIR)/openssl/config.rb: $(srcdir)/lib/openssl/config.rb $(TIMESTAMP_DIR)/.RUBYLIBDIR.-.openssl.time + $(Q) $(COPY) $(srcdir)/lib/openssl/config.rb $(@D) +clean-rb-default:: + -$(Q)$(RM) $(RUBYLIBDIR)/openssl/config.rb +install-rb-default: $(RUBYLIBDIR)/openssl/digest.rb +$(RUBYLIBDIR)/openssl/digest.rb: $(srcdir)/lib/openssl/digest.rb $(TIMESTAMP_DIR)/.RUBYLIBDIR.-.openssl.time + $(Q) $(COPY) $(srcdir)/lib/openssl/digest.rb $(@D) +clean-rb-default:: + -$(Q)$(RM) $(RUBYLIBDIR)/openssl/digest.rb +install-rb-default: $(RUBYLIBDIR)/openssl/ssl.rb +$(RUBYLIBDIR)/openssl/ssl.rb: $(srcdir)/lib/openssl/ssl.rb $(TIMESTAMP_DIR)/.RUBYLIBDIR.-.openssl.time + $(Q) $(COPY) $(srcdir)/lib/openssl/ssl.rb $(@D) +clean-rb-default:: + -$(Q)$(RM) $(RUBYLIBDIR)/openssl/ssl.rb +install-rb-default: $(RUBYLIBDIR)/openssl/x509.rb +$(RUBYLIBDIR)/openssl/x509.rb: $(srcdir)/lib/openssl/x509.rb $(TIMESTAMP_DIR)/.RUBYLIBDIR.-.openssl.time + $(Q) $(COPY) $(srcdir)/lib/openssl/x509.rb $(@D) +clean-rb-default:: + -$(Q)$(RM) $(RUBYLIBDIR)/openssl/x509.rb +pre-install-rb-default: $(TIMESTAMP_DIR)/.RUBYLIBDIR.time +install-rb-default: $(RUBYLIBDIR)/openssl.rb +$(RUBYLIBDIR)/openssl.rb: $(srcdir)/lib/openssl.rb $(TIMESTAMP_DIR)/.RUBYLIBDIR.time + $(Q) $(COPY) $(srcdir)/lib/openssl.rb $(@D) +clean-rb-default:: + -$(Q)$(RM) $(RUBYLIBDIR)/openssl.rb +pre-install-rb-default: + $(ECHO) installing default openssl libraries +clean-rb-default:: + -$(Q)$(RMDIRS) $(RUBYLIBDIR)/openssl 2> /dev/null || true + -$(Q)$(RMDIRS) $(RUBYLIBDIR) 2> /dev/null || true +$(TIMESTAMP_DIR)/.RUBYARCHDIR.time: + $(Q) $(MAKEDIRS) $(@D) $(RUBYARCHDIR) + $(Q) $(TOUCH) $@ +$(TIMESTAMP_DIR)/.RUBYLIBDIR.-.openssl.time: + $(Q) $(MAKEDIRS) $(@D) $(RUBYLIBDIR)/openssl + $(Q) $(TOUCH) $@ +$(TIMESTAMP_DIR)/.RUBYLIBDIR.time: + $(Q) $(MAKEDIRS) $(@D) $(RUBYLIBDIR) + $(Q) $(TOUCH) $@ + +site-install: site-install-so site-install-rb +site-install-so: install-so +site-install-rb: install-rb + +.SUFFIXES: .c .m .cc .mm .cxx .cpp .o .S + +.cc.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $< + +.cc.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $< + +.mm.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $< + +.mm.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $< + +.cxx.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $< + +.cxx.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $< + +.cpp.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $< + +.cpp.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $< + +.c.o: + $(ECHO) compiling $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $< + +.c.S: + $(ECHO) translating $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $< + +.m.o: + $(ECHO) compiling $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $< + +.m.S: + $(ECHO) translating $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $< + +$(RUBYARCHDIR)/$(DLLIB): $(OBJS) Makefile $(TIMESTAMP_DIR)/.RUBYARCHDIR.time + $(ECHO) linking shared-object $(DLLIB) + -$(Q)$(RM) $(@) + $(Q) $(LDSHARED) -o $@ $(OBJS) $(LIBPATH) $(DLDFLAGS) $(LOCAL_LIBS) $(LIBS) + $(Q) $(POSTLINK) + + + +### +$(OBJS): $(RUBY_EXTCONF_H) + +$(OBJS): $(HDRS) $(ruby_headers) \ + $(hdrdir)/ruby/io.h \ + $(hdrdir)/ruby/encoding.h \ + $(hdrdir)/ruby/oniguruma.h \ + $(hdrdir)/ruby/thread.h +ossl.o: $(hdrdir)/ruby/thread_native.h diff --git a/ext/openssl/depend b/ext/openssl/depend new file mode 100644 index 00000000..23bebd8c --- /dev/null +++ b/ext/openssl/depend @@ -0,0 +1,6 @@ +$(OBJS): $(HDRS) $(ruby_headers) \ + $(hdrdir)/ruby/io.h \ + $(hdrdir)/ruby/encoding.h \ + $(hdrdir)/ruby/oniguruma.h \ + $(hdrdir)/ruby/thread.h +ossl.o: $(hdrdir)/ruby/thread_native.h diff --git a/ext/openssl/deprecation.rb b/ext/openssl/deprecation.rb new file mode 100644 index 00000000..39ebfa0d --- /dev/null +++ b/ext/openssl/deprecation.rb @@ -0,0 +1,21 @@ +module OpenSSL + def self.deprecated_warning_flag + unless flag = (@deprecated_warning_flag ||= nil) + if try_compile("", flag = "-Werror=deprecated-declarations") + if with_config("broken-apple-openssl") + flag = "-Wno-deprecated-declarations" + end + $warnflags << " #{flag}" + else + flag = "" + end + @deprecated_warning_flag = flag + end + flag + end + + def self.check_func(func, header) + have_func(func, header, deprecated_warning_flag) and + have_header(header, nil, deprecated_warning_flag) + end +end diff --git a/ext/openssl/extconf.h b/ext/openssl/extconf.h new file mode 100644 index 00000000..03ec92bb --- /dev/null +++ b/ext/openssl/extconf.h @@ -0,0 +1,71 @@ +#ifndef EXTCONF_H +#define EXTCONF_H +#define HAVE_ASSERT_H 1 +#define HAVE_OPENSSL_SSL_H 1 +#define HAVE_OPENSSL_CONF_API_H 1 +#define HAVE_SSL_LIBRARY_INIT 1 +#define HAVE_OPENSSL_SSL_H 1 +#define HAVE_ERR_PEEK_LAST_ERROR 1 +#define HAVE_ASN1_PUT_EOC 1 +#define HAVE_BN_MOD_ADD 1 +#define HAVE_BN_MOD_SQR 1 +#define HAVE_BN_MOD_SUB 1 +#define HAVE_BN_PSEUDO_RAND_RANGE 1 +#define HAVE_BN_RAND_RANGE 1 +#define HAVE_CONF_GET1_DEFAULT_CONFIG_FILE 1 +#define HAVE_EVP_CIPHER_CTX_COPY 1 +#define HAVE_EVP_CIPHER_CTX_SET_PADDING 1 +#define HAVE_EVP_CIPHERFINAL_EX 1 +#define HAVE_EVP_CIPHERINIT_EX 1 +#define HAVE_EVP_DIGESTFINAL_EX 1 +#define HAVE_EVP_DIGESTINIT_EX 1 +#define HAVE_EVP_MD_CTX_CLEANUP 1 +#define HAVE_EVP_MD_CTX_CREATE 1 +#define HAVE_EVP_MD_CTX_DESTROY 1 +#define HAVE_EVP_MD_CTX_INIT 1 +#define HAVE_HMAC_CTX_CLEANUP 1 +#define HAVE_HMAC_CTX_COPY 1 +#define HAVE_HMAC_CTX_INIT 1 +#define HAVE_PEM_DEF_CALLBACK 1 +#define HAVE_PKCS5_PBKDF2_HMAC 1 +#define HAVE_PKCS5_PBKDF2_HMAC_SHA1 1 +#define HAVE_X509V3_SET_NCONF 1 +#define HAVE_X509V3_EXT_NCONF_NID 1 +#define HAVE_X509_CRL_ADD0_REVOKED 1 +#define HAVE_X509_CRL_SET_ISSUER_NAME 1 +#define HAVE_X509_CRL_SET_VERSION 1 +#define HAVE_X509_CRL_SORT 1 +#define HAVE_X509_NAME_HASH_OLD 1 +#define HAVE_OBJ_NAME_DO_ALL_SORTED 1 +#define HAVE_SSL_SESSION_GET_ID 1 +#define HAVE_OPENSSL_CLEANSE 1 +#define HAVE_TLSV1_1_METHOD 1 +#define HAVE_TLSV1_1_SERVER_METHOD 1 +#define HAVE_TLSV1_1_CLIENT_METHOD 1 +#define HAVE_TLSV1_2_METHOD 1 +#define HAVE_TLSV1_2_SERVER_METHOD 1 +#define HAVE_TLSV1_2_CLIENT_METHOD 1 +#define HAVE_OPENSSL_NPN_NEGOTIATED 1 +#define HAVE_SSL_SET_TLSEXT_HOST_NAME 1 +#define HAVE_OPENSSL_ENGINE_H 1 +#define HAVE_ENGINE_ADD 1 +#define HAVE_ENGINE_LOAD_BUILTIN_ENGINES 1 +#define HAVE_ENGINE_GET_DIGEST 1 +#define HAVE_ENGINE_GET_CIPHER 1 +#define HAVE_ENGINE_CLEANUP 1 +#define HAVE_ENGINE_LOAD_DYNAMIC 1 +#define HAVE_ENGINE_LOAD_CRYPTODEV 1 +#define HAVE_DH_GENERATE_PARAMETERS_EX 1 +#define HAVE_DSA_GENERATE_PARAMETERS_EX 1 +#define HAVE_RSA_GENERATE_KEY_EX 1 +#define HAVE_OPENSSL_OCSP_H 1 +#define HAVE_CRYPTO_THREADID_PTR 1 +#define HAVE_ST_PTR 1 +#define HAVE_EVP_CIPHER_CTX_FLAGS 1 +#define HAVE_ST_FLAGS 1 +#define HAVE_EVP_CIPHER_CTX_ENGINE 1 +#define HAVE_ST_ENGINE 1 +#define HAVE_X509_ATTRIBUTE_SINGLE 1 +#define HAVE_ST_SINGLE 1 +#define HAVE_AUTHENTICATED_ENCRYPTION 1 +#endif diff --git a/ext/openssl/extconf.rb b/ext/openssl/extconf.rb new file mode 100644 index 00000000..e272cba0 --- /dev/null +++ b/ext/openssl/extconf.rb @@ -0,0 +1,160 @@ +# -*- coding: us-ascii -*- +=begin += $RCSfile$ -- Generator for Makefile + += Info + 'OpenSSL for Ruby 2' project + Copyright (C) 2002 Michal Rokos <m.rokos@sh.cvut.cz> + All rights reserved. + += Licence + This program is licenced under the same licence as Ruby. + (See the file 'LICENCE'.) + += Version + $Id$ +=end + +require "mkmf" +require File.expand_path('../deprecation', __FILE__) + +dir_config("openssl") +dir_config("kerberos") + +Logging::message "=== OpenSSL for Ruby configurator ===\n" + +## +# Adds -DOSSL_DEBUG for compilation and some more targets when GCC is used +# To turn it on, use: --with-debug or --enable-debug +# +if with_config("debug") or enable_config("debug") + $defs.push("-DOSSL_DEBUG") unless $defs.include? "-DOSSL_DEBUG" +end + +Logging::message "=== Checking for system dependent stuff... ===\n" +have_library("nsl", "t_open") +have_library("socket", "socket") +have_header("assert.h") + +Logging::message "=== Checking for required stuff... ===\n" +if $mingw + have_library("wsock32") + have_library("gdi32") +end + +result = pkg_config("openssl") && have_header("openssl/ssl.h") + +unless result + result = have_header("openssl/ssl.h") + result &&= %w[crypto libeay32].any? {|lib| have_library(lib, "OpenSSL_add_all_digests")} + result &&= %w[ssl ssleay32].any? {|lib| have_library(lib, "SSL_library_init")} + unless result + Logging::message "=== Checking for required stuff failed. ===\n" + Logging::message "Makefile wasn't created. Fix the errors above.\n" + exit 1 + end +end + +unless have_header("openssl/conf_api.h") + raise "OpenSSL 0.9.6 or later required." +end +unless OpenSSL.check_func("SSL_library_init()", "openssl/ssl.h") + raise "Ignore OpenSSL broken by Apple.\nPlease use another openssl. (e.g. using `configure --with-openssl-dir=/path/to/openssl')" +end + +Logging::message "=== Checking for OpenSSL features... ===\n" +have_func("ERR_peek_last_error") +have_func("ASN1_put_eoc") +have_func("BN_mod_add") +have_func("BN_mod_sqr") +have_func("BN_mod_sub") +have_func("BN_pseudo_rand_range") +have_func("BN_rand_range") +have_func("CONF_get1_default_config_file") +have_func("EVP_CIPHER_CTX_copy") +have_func("EVP_CIPHER_CTX_set_padding") +have_func("EVP_CipherFinal_ex") +have_func("EVP_CipherInit_ex") +have_func("EVP_DigestFinal_ex") +have_func("EVP_DigestInit_ex") +have_func("EVP_MD_CTX_cleanup") +have_func("EVP_MD_CTX_create") +have_func("EVP_MD_CTX_destroy") +have_func("EVP_MD_CTX_init") +have_func("HMAC_CTX_cleanup") +have_func("HMAC_CTX_copy") +have_func("HMAC_CTX_init") +have_func("PEM_def_callback") +have_func("PKCS5_PBKDF2_HMAC") +have_func("PKCS5_PBKDF2_HMAC_SHA1") +have_func("X509V3_set_nconf") +have_func("X509V3_EXT_nconf_nid") +have_func("X509_CRL_add0_revoked") +have_func("X509_CRL_set_issuer_name") +have_func("X509_CRL_set_version") +have_func("X509_CRL_sort") +have_func("X509_NAME_hash_old") +have_func("X509_STORE_get_ex_data") +have_func("X509_STORE_set_ex_data") +have_func("OBJ_NAME_do_all_sorted") +have_func("SSL_SESSION_get_id") +have_func("SSL_SESSION_cmp") +have_func("OPENSSL_cleanse") +have_func("SSLv2_method") +have_func("SSLv2_server_method") +have_func("SSLv2_client_method") +have_func("TLSv1_1_method") +have_func("TLSv1_1_server_method") +have_func("TLSv1_1_client_method") +have_func("TLSv1_2_method") +have_func("TLSv1_2_server_method") +have_func("TLSv1_2_client_method") +have_macro("OPENSSL_NPN_NEGOTIATED", ['openssl/ssl.h']) && $defs.push("-DHAVE_OPENSSL_NPN_NEGOTIATED") +unless have_func("SSL_set_tlsext_host_name", ['openssl/ssl.h']) + have_macro("SSL_set_tlsext_host_name", ['openssl/ssl.h']) && $defs.push("-DHAVE_SSL_SET_TLSEXT_HOST_NAME") +end +if have_header("openssl/engine.h") + have_func("ENGINE_add") + have_func("ENGINE_load_builtin_engines") + have_func("ENGINE_load_openbsd_dev_crypto") + have_func("ENGINE_get_digest") + have_func("ENGINE_get_cipher") + have_func("ENGINE_cleanup") + have_func("ENGINE_load_dynamic") + have_func("ENGINE_load_4758cca") + have_func("ENGINE_load_aep") + have_func("ENGINE_load_atalla") + have_func("ENGINE_load_chil") + have_func("ENGINE_load_cswift") + have_func("ENGINE_load_nuron") + have_func("ENGINE_load_sureware") + have_func("ENGINE_load_ubsec") + have_func("ENGINE_load_padlock") + have_func("ENGINE_load_capi") + have_func("ENGINE_load_gmp") + have_func("ENGINE_load_gost") + have_func("ENGINE_load_cryptodev") + have_func("ENGINE_load_aesni") +end +have_func("DH_generate_parameters_ex") +have_func("DSA_generate_parameters_ex") +have_func("RSA_generate_key_ex") +if checking_for('OpenSSL version is 0.9.7 or later') { + try_static_assert('OPENSSL_VERSION_NUMBER >= 0x00907000L', 'openssl/opensslv.h') + } + have_header("openssl/ocsp.h") +end +have_struct_member("CRYPTO_THREADID", "ptr", "openssl/crypto.h") +have_struct_member("EVP_CIPHER_CTX", "flags", "openssl/evp.h") +have_struct_member("EVP_CIPHER_CTX", "engine", "openssl/evp.h") +have_struct_member("X509_ATTRIBUTE", "single", "openssl/x509.h") +have_macro("OPENSSL_FIPS", ['openssl/opensslconf.h']) && $defs.push("-DHAVE_OPENSSL_FIPS") +have_macro("EVP_CTRL_GCM_GET_TAG", ['openssl/evp.h']) && $defs.push("-DHAVE_AUTHENTICATED_ENCRYPTION") + +Logging::message "=== Checking done. ===\n" + +create_header +create_makefile("openssl") {|conf| + conf << "THREAD_MODEL = #{CONFIG["THREAD_MODEL"]}\n" +} +Logging::message "Done.\n" diff --git a/ext/openssl/openssl_missing.c b/ext/openssl/openssl_missing.c new file mode 100644 index 00000000..b5efaaf1 --- /dev/null +++ b/ext/openssl/openssl_missing.c @@ -0,0 +1,356 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2001-2002 Michal Rokos <m.rokos@sh.cvut.cz> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#include RUBY_EXTCONF_H + +#if defined(HAVE_OPENSSL_ENGINE_H) && defined(HAVE_EVP_CIPHER_CTX_ENGINE) +# include <openssl/engine.h> +#endif +#include <openssl/x509_vfy.h> + +#if !defined(OPENSSL_NO_HMAC) +#include <string.h> /* memcpy() */ +#include <openssl/hmac.h> + +#include "openssl_missing.h" + +#if !defined(HAVE_HMAC_CTX_COPY) +void +HMAC_CTX_copy(HMAC_CTX *out, HMAC_CTX *in) +{ + if (!out || !in) return; + memcpy(out, in, sizeof(HMAC_CTX)); + + EVP_MD_CTX_copy(&out->md_ctx, &in->md_ctx); + EVP_MD_CTX_copy(&out->i_ctx, &in->i_ctx); + EVP_MD_CTX_copy(&out->o_ctx, &in->o_ctx); +} +#endif /* HAVE_HMAC_CTX_COPY */ +#endif /* NO_HMAC */ + +#if !defined(HAVE_X509_STORE_SET_EX_DATA) +int X509_STORE_set_ex_data(X509_STORE *str, int idx, void *data) +{ + return CRYPTO_set_ex_data(&str->ex_data, idx, data); +} +#endif + +#if !defined(HAVE_X509_STORE_GET_EX_DATA) +void *X509_STORE_get_ex_data(X509_STORE *str, int idx) +{ + return CRYPTO_get_ex_data(&str->ex_data, idx); +} +#endif + +#if !defined(HAVE_EVP_MD_CTX_CREATE) +EVP_MD_CTX * +EVP_MD_CTX_create(void) +{ + EVP_MD_CTX *ctx = OPENSSL_malloc(sizeof(EVP_MD_CTX)); + if (!ctx) return NULL; + + memset(ctx, 0, sizeof(EVP_MD_CTX)); + + return ctx; +} +#endif + +#if !defined(HAVE_EVP_MD_CTX_CLEANUP) +int +EVP_MD_CTX_cleanup(EVP_MD_CTX *ctx) +{ + /* FIXME!!! */ + memset(ctx, 0, sizeof(EVP_MD_CTX)); + + return 1; +} +#endif + +#if !defined(HAVE_EVP_MD_CTX_DESTROY) +void +EVP_MD_CTX_destroy(EVP_MD_CTX *ctx) +{ + EVP_MD_CTX_cleanup(ctx); + OPENSSL_free(ctx); +} +#endif + +#if !defined(HAVE_EVP_MD_CTX_INIT) +void +EVP_MD_CTX_init(EVP_MD_CTX *ctx) +{ + memset(ctx, 0, sizeof(EVP_MD_CTX)); +} +#endif + +#if !defined(HAVE_HMAC_CTX_INIT) +void +HMAC_CTX_init(HMAC_CTX *ctx) +{ + EVP_MD_CTX_init(&ctx->i_ctx); + EVP_MD_CTX_init(&ctx->o_ctx); + EVP_MD_CTX_init(&ctx->md_ctx); +} +#endif + +#if !defined(HAVE_HMAC_CTX_CLEANUP) +void +HMAC_CTX_cleanup(HMAC_CTX *ctx) +{ + EVP_MD_CTX_cleanup(&ctx->i_ctx); + EVP_MD_CTX_cleanup(&ctx->o_ctx); + EVP_MD_CTX_cleanup(&ctx->md_ctx); + memset(ctx, 0, sizeof(HMAC_CTX)); +} +#endif + +#if !defined(HAVE_EVP_CIPHER_CTX_COPY) +/* + * this function does not exist in OpenSSL yet... or ever?. + * a future version may break this function. + * tested on 0.9.7d. + */ +int +EVP_CIPHER_CTX_copy(EVP_CIPHER_CTX *out, EVP_CIPHER_CTX *in) +{ + memcpy(out, in, sizeof(EVP_CIPHER_CTX)); + +#if defined(HAVE_ENGINE_ADD) && defined(HAVE_EVP_CIPHER_CTX_ENGINE) + if (in->engine) ENGINE_add(out->engine); + if (in->cipher_data) { + out->cipher_data = OPENSSL_malloc(in->cipher->ctx_size); + memcpy(out->cipher_data, in->cipher_data, in->cipher->ctx_size); + } +#endif + + return 1; +} +#endif + +#if !defined(HAVE_X509_CRL_SET_VERSION) +int +X509_CRL_set_version(X509_CRL *x, long version) +{ + if (x == NULL || x->crl == NULL) return 0; + if (x->crl->version == NULL) { + x->crl->version = M_ASN1_INTEGER_new(); + if (x->crl->version == NULL) return 0; + } + return ASN1_INTEGER_set(x->crl->version, version); +} +#endif + +#if !defined(HAVE_X509_CRL_SET_ISSUER_NAME) +int +X509_CRL_set_issuer_name(X509_CRL *x, X509_NAME *name) +{ + if (x == NULL || x->crl == NULL) return 0; + return X509_NAME_set(&x->crl->issuer, name); +} +#endif + +#if !defined(HAVE_X509_CRL_SORT) +int +X509_CRL_sort(X509_CRL *c) +{ + int i; + X509_REVOKED *r; + /* sort the data so it will be written in serial + * number order */ + sk_X509_REVOKED_sort(c->crl->revoked); + for (i=0; i<sk_X509_REVOKED_num(c->crl->revoked); i++) { + r=sk_X509_REVOKED_value(c->crl->revoked, i); + r->sequence=i; + } + return 1; +} +#endif + +#if !defined(HAVE_X509_CRL_ADD0_REVOKED) +static int +OSSL_X509_REVOKED_cmp(const X509_REVOKED * const *a, const X509_REVOKED * const *b) +{ + return(ASN1_STRING_cmp( + (ASN1_STRING *)(*a)->serialNumber, + (ASN1_STRING *)(*b)->serialNumber)); +} + +int +X509_CRL_add0_revoked(X509_CRL *crl, X509_REVOKED *rev) +{ + X509_CRL_INFO *inf; + + inf = crl->crl; + if (!inf->revoked) + inf->revoked = sk_X509_REVOKED_new(OSSL_X509_REVOKED_cmp); + if (!inf->revoked || !sk_X509_REVOKED_push(inf->revoked, rev)) + return 0; + return 1; +} +#endif + +#if !defined(HAVE_BN_MOD_SQR) +int +BN_mod_sqr(BIGNUM *r, const BIGNUM *a, const BIGNUM *m, BN_CTX *ctx) +{ + if (!BN_sqr(r, (BIGNUM*)a, ctx)) return 0; + return BN_mod(r, r, m, ctx); +} +#endif + +#if !defined(HAVE_BN_MOD_ADD) || !defined(HAVE_BN_MOD_SUB) +int BN_nnmod(BIGNUM *r, const BIGNUM *m, const BIGNUM *d, BN_CTX *ctx) +{ + if (!BN_mod(r,m,d,ctx)) return 0; + if (!r->neg) return 1; + return (d->neg ? BN_sub : BN_add)(r, r, d); +} +#endif + +#if !defined(HAVE_BN_MOD_ADD) +int +BN_mod_add(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, const BIGNUM *m, BN_CTX *ctx) +{ + if (!BN_add(r, a, b)) return 0; + return BN_nnmod(r, r, m, ctx); +} +#endif + +#if !defined(HAVE_BN_MOD_SUB) +int +BN_mod_sub(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, const BIGNUM *m, BN_CTX *ctx) +{ + if (!BN_sub(r, a, b)) return 0; + return BN_nnmod(r, r, m, ctx); +} +#endif + +#if !defined(HAVE_BN_RAND_RANGE) || !defined(HAVE_BN_PSEUDO_RAND_RANGE) +static int +bn_rand_range(int pseudo, BIGNUM *r, BIGNUM *range) +{ + int (*bn_rand)(BIGNUM *, int, int, int) = pseudo ? BN_pseudo_rand : BN_rand; + int n; + + if (range->neg || BN_is_zero(range)) return 0; + + n = BN_num_bits(range); + + if (n == 1) { + if (!BN_zero(r)) return 0; + } else if (!BN_is_bit_set(range, n - 2) && !BN_is_bit_set(range, n - 3)) { + do { + if (!bn_rand(r, n + 1, -1, 0)) return 0; + if (BN_cmp(r ,range) >= 0) { + if (!BN_sub(r, r, range)) return 0; + if (BN_cmp(r, range) >= 0) + if (!BN_sub(r, r, range)) return 0; + } + } while (BN_cmp(r, range) >= 0); + } else { + do { + if (!bn_rand(r, n, -1, 0)) return 0; + } while (BN_cmp(r, range) >= 0); + } + + return 1; +} +#endif + +#if !defined(HAVE_BN_RAND_RANGE) +int +BN_rand_range(BIGNUM *r, BIGNUM *range) +{ + return bn_rand_range(0, r, range); +} +#endif + +#if !defined(HAVE_BN_PSEUDO_RAND_RANGE) +int +BN_pseudo_rand_range(BIGNUM *r, BIGNUM *range) +{ + return bn_rand_range(1, r, range); +} +#endif + +#if !defined(HAVE_CONF_GET1_DEFAULT_CONFIG_FILE) +#define OPENSSL_CONF "openssl.cnf" +char * +CONF_get1_default_config_file(void) +{ + char *file; + int len; + + file = getenv("OPENSSL_CONF"); + if (file) return BUF_strdup(file); + len = strlen(X509_get_default_cert_area()); +#ifndef OPENSSL_SYS_VMS + len++; +#endif + len += strlen(OPENSSL_CONF); + file = OPENSSL_malloc(len + 1); + if (!file) return NULL; + strcpy(file,X509_get_default_cert_area()); +#ifndef OPENSSL_SYS_VMS + strcat(file,"/"); +#endif + strcat(file,OPENSSL_CONF); + + return file; +} +#endif + +#if !defined(HAVE_PEM_DEF_CALLBACK) +#define OSSL_PASS_MIN_LENGTH 4 +int +PEM_def_callback(char *buf, int num, int w, void *key) +{ + int i,j; + const char *prompt; + + if (key) { + i = strlen(key); + i = (i > num) ? num : i; + memcpy(buf, key, i); + return i; + } + + prompt = EVP_get_pw_prompt(); + if (prompt == NULL) prompt = "Enter PEM pass phrase:"; + for (;;) { + i = EVP_read_pw_string(buf, num, prompt, w); + if (i != 0) { + memset(buf, 0, (unsigned int)num); + return(-1); + } + j = strlen(buf); + if (j < OSSL_PASS_MIN_LENGTH) { + fprintf(stderr, + "phrase is too short, needs to be at least %d chars\n", + OSSL_PASS_MIN_LENGTH); + } + else break; + } + return j; +} +#endif + +#if !defined(HAVE_ASN1_PUT_EOC) +int +ASN1_put_eoc(unsigned char **pp) +{ + unsigned char *p = *pp; + *p++ = 0; + *p++ = 0; + *pp = p; + return 2; +} +#endif + diff --git a/ext/openssl/openssl_missing.h b/ext/openssl/openssl_missing.h new file mode 100644 index 00000000..3635f88b --- /dev/null +++ b/ext/openssl/openssl_missing.h @@ -0,0 +1,198 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2001-2002 Michal Rokos <m.rokos@sh.cvut.cz> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#if !defined(_OSSL_OPENSSL_MISSING_H_) +#define _OSSL_OPENSSL_MISSING_H_ + +#if defined(__cplusplus) +extern "C" { +#endif + +#ifndef TYPEDEF_D2I_OF +typedef char *d2i_of_void(); +#endif +#ifndef TYPEDEF_I2D_OF +typedef int i2d_of_void(); +#endif + +/* + * These functions are not included in headers of OPENSSL <= 0.9.6b + */ + +#if !defined(PEM_read_bio_DSAPublicKey) +# define PEM_read_bio_DSAPublicKey(bp,x,cb,u) (DSA *)PEM_ASN1_read_bio( \ + (d2i_of_void *)d2i_DSAPublicKey,PEM_STRING_DSA_PUBLIC,(bp),(void **)(x),(cb),(u)) +#endif + +#if !defined(PEM_write_bio_DSAPublicKey) +# define PEM_write_bio_DSAPublicKey(bp,x) \ + PEM_ASN1_write_bio((i2d_of_void *)i2d_DSAPublicKey,\ + PEM_STRING_DSA_PUBLIC,\ + (bp),(char *)(x), NULL, NULL, 0, NULL, NULL) +#endif + +#if !defined(DSAPrivateKey_dup) +# define DSAPrivateKey_dup(dsa) (DSA *)ASN1_dup((i2d_of_void *)i2d_DSAPrivateKey, \ + (d2i_of_void *)d2i_DSAPrivateKey,(char *)(dsa)) +#endif + +#if !defined(DSAPublicKey_dup) +# define DSAPublicKey_dup(dsa) (DSA *)ASN1_dup((i2d_of_void *)i2d_DSAPublicKey, \ + (d2i_of_void *)d2i_DSAPublicKey,(char *)(dsa)) +#endif + +#if !defined(X509_REVOKED_dup) +# define X509_REVOKED_dup(rev) (X509_REVOKED *)ASN1_dup((i2d_of_void *)i2d_X509_REVOKED, \ + (d2i_of_void *)d2i_X509_REVOKED, (char *)(rev)) +#endif + +#if !defined(PKCS7_SIGNER_INFO_dup) +# define PKCS7_SIGNER_INFO_dup(si) (PKCS7_SIGNER_INFO *)ASN1_dup((i2d_of_void *)i2d_PKCS7_SIGNER_INFO, \ + (d2i_of_void *)d2i_PKCS7_SIGNER_INFO, (char *)(si)) +#endif + +#if !defined(PKCS7_RECIP_INFO_dup) +# define PKCS7_RECIP_INFO_dup(ri) (PKCS7_RECIP_INFO *)ASN1_dup((i2d_of_void *)i2d_PKCS7_RECIP_INFO, \ + (d2i_of_void *)d2i_PKCS7_RECIP_INFO, (char *)(ri)) +#endif + +#if !defined(HAVE_HMAC_CTX_INIT) +void HMAC_CTX_init(HMAC_CTX *ctx); +#endif + +#if !defined(HAVE_HMAC_CTX_COPY) +void HMAC_CTX_copy(HMAC_CTX *out, HMAC_CTX *in); +#endif + +#if !defined(HAVE_HMAC_CTX_CLEANUP) +void HMAC_CTX_cleanup(HMAC_CTX *ctx); +#endif + +#if !defined(HAVE_EVP_MD_CTX_CREATE) +EVP_MD_CTX *EVP_MD_CTX_create(void); +#endif + +#if !defined(HAVE_EVP_MD_CTX_INIT) +void EVP_MD_CTX_init(EVP_MD_CTX *ctx); +#endif + +#if !defined(HAVE_EVP_MD_CTX_CLEANUP) +int EVP_MD_CTX_cleanup(EVP_MD_CTX *ctx); +#endif + +#if !defined(HAVE_EVP_MD_CTX_DESTROY) +void EVP_MD_CTX_destroy(EVP_MD_CTX *ctx); +#endif + +#if !defined(HAVE_EVP_CIPHER_CTX_COPY) +int EVP_CIPHER_CTX_copy(EVP_CIPHER_CTX *out, EVP_CIPHER_CTX *in); +#endif + +#if !defined(HAVE_EVP_DIGESTINIT_EX) +# define EVP_DigestInit_ex(ctx, md, engine) EVP_DigestInit((ctx), (md)) +#endif +#if !defined(HAVE_EVP_DIGESTFINAL_EX) +# define EVP_DigestFinal_ex(ctx, buf, len) EVP_DigestFinal((ctx), (buf), (len)) +#endif + +#if !defined(HAVE_EVP_CIPHERINIT_EX) +# define EVP_CipherInit_ex(ctx, type, impl, key, iv, enc) EVP_CipherInit((ctx), (type), (key), (iv), (enc)) +#endif +#if !defined(HAVE_EVP_CIPHERFINAL_EX) +# define EVP_CipherFinal_ex(ctx, outm, outl) EVP_CipherFinal((ctx), (outm), (outl)) +#endif + +#if !defined(EVP_CIPHER_name) +# define EVP_CIPHER_name(e) OBJ_nid2sn(EVP_CIPHER_nid(e)) +#endif + +#if !defined(EVP_MD_name) +# define EVP_MD_name(e) OBJ_nid2sn(EVP_MD_type(e)) +#endif + +#if !defined(HAVE_EVP_HMAC_INIT_EX) +# define HMAC_Init_ex(ctx, key, len, digest, engine) HMAC_Init((ctx), (key), (len), (digest)) +#endif + +#if !defined(PKCS7_is_detached) +# define PKCS7_is_detached(p7) (PKCS7_type_is_signed(p7) && PKCS7_get_detached(p7)) +#endif + +#if !defined(PKCS7_type_is_encrypted) +# define PKCS7_type_is_encrypted(a) (OBJ_obj2nid((a)->type) == NID_pkcs7_encrypted) +#endif + +#if !defined(HAVE_OPENSSL_CLEANSE) +#define OPENSSL_cleanse(p, l) memset((p), 0, (l)) +#endif + +#if !defined(HAVE_X509_STORE_GET_EX_DATA) +void *X509_STORE_get_ex_data(X509_STORE *str, int idx); +#endif + +#if !defined(HAVE_X509_STORE_SET_EX_DATA) +int X509_STORE_set_ex_data(X509_STORE *str, int idx, void *data); +#endif + +#if !defined(HAVE_X509_CRL_SET_VERSION) +int X509_CRL_set_version(X509_CRL *x, long version); +#endif + +#if !defined(HAVE_X509_CRL_SET_ISSUER_NAME) +int X509_CRL_set_issuer_name(X509_CRL *x, X509_NAME *name); +#endif + +#if !defined(HAVE_X509_CRL_SORT) +int X509_CRL_sort(X509_CRL *c); +#endif + +#if !defined(HAVE_X509_CRL_ADD0_REVOKED) +int X509_CRL_add0_revoked(X509_CRL *crl, X509_REVOKED *rev); +#endif + +#if !defined(HAVE_BN_MOD_SQR) +int BN_mod_sqr(BIGNUM *r, const BIGNUM *a, const BIGNUM *m, BN_CTX *ctx); +#endif + +#if !defined(HAVE_BN_MOD_ADD) +int BN_mod_add(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, const BIGNUM *m, BN_CTX *ctx); +#endif + +#if !defined(HAVE_BN_MOD_SUB) +int BN_mod_sub(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, const BIGNUM *m, BN_CTX *ctx); +#endif + +#if !defined(HAVE_BN_RAND_RANGE) +int BN_rand_range(BIGNUM *r, BIGNUM *range); +#endif + +#if !defined(HAVE_BN_PSEUDO_RAND_RANGE) +int BN_pseudo_rand_range(BIGNUM *r, BIGNUM *range); +#endif + +#if !defined(HAVE_CONF_GET1_DEFAULT_CONFIG_FILE) +char *CONF_get1_default_config_file(void); +#endif + +#if !defined(HAVE_PEM_DEF_CALLBACK) +int PEM_def_callback(char *buf, int num, int w, void *key); +#endif + +#if !defined(HAVE_ASN1_PUT_EOC) +int ASN1_put_eoc(unsigned char **pp); +#endif + +#if defined(__cplusplus) +} +#endif + + +#endif /* _OSSL_OPENSSL_MISSING_H_ */ + diff --git a/ext/openssl/ossl.c b/ext/openssl/ossl.c new file mode 100644 index 00000000..5fbfb335 --- /dev/null +++ b/ext/openssl/ossl.c @@ -0,0 +1,1168 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2001-2002 Michal Rokos <m.rokos@sh.cvut.cz> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#include "ossl.h" +#include <stdarg.h> /* for ossl_raise */ + +/* + * String to HEXString conversion + */ +int +string2hex(const unsigned char *buf, int buf_len, char **hexbuf, int *hexbuf_len) +{ + static const char hex[]="0123456789abcdef"; + int i, len; + + if (buf_len < 0 || buf_len > INT_MAX / 2) { /* PARANOIA? */ + return -1; + } + len = 2 * buf_len; + if (!hexbuf) { /* if no buf, return calculated len */ + if (hexbuf_len) { + *hexbuf_len = len; + } + return len; + } + if (!(*hexbuf = OPENSSL_malloc(len + 1))) { + return -1; + } + for (i = 0; i < buf_len; i++) { + (*hexbuf)[2 * i] = hex[((unsigned char)buf[i]) >> 4]; + (*hexbuf)[2 * i + 1] = hex[buf[i] & 0x0f]; + } + (*hexbuf)[2 * i] = '\0'; + + if (hexbuf_len) { + *hexbuf_len = len; + } + return len; +} + +/* + * Data Conversion + */ +#define OSSL_IMPL_ARY2SK(name, type, expected_class, dup) \ +STACK_OF(type) * \ +ossl_##name##_ary2sk0(VALUE ary) \ +{ \ + STACK_OF(type) *sk; \ + VALUE val; \ + type *x; \ + int i; \ + \ + Check_Type(ary, T_ARRAY); \ + sk = sk_##type##_new_null(); \ + if (!sk) ossl_raise(eOSSLError, NULL); \ + \ + for (i = 0; i < RARRAY_LEN(ary); i++) { \ + val = rb_ary_entry(ary, i); \ + if (!rb_obj_is_kind_of(val, expected_class)) { \ + sk_##type##_pop_free(sk, type##_free); \ + ossl_raise(eOSSLError, "object in array not" \ + " of class ##type##"); \ + } \ + x = dup(val); /* NEED TO DUP */ \ + sk_##type##_push(sk, x); \ + } \ + return sk; \ +} \ + \ +STACK_OF(type) * \ +ossl_protect_##name##_ary2sk(VALUE ary, int *status) \ +{ \ + return (STACK_OF(type)*)rb_protect( \ + (VALUE(*)_((VALUE)))ossl_##name##_ary2sk0, \ + ary, \ + status); \ +} \ + \ +STACK_OF(type) * \ +ossl_##name##_ary2sk(VALUE ary) \ +{ \ + STACK_OF(type) *sk; \ + int status = 0; \ + \ + sk = ossl_protect_##name##_ary2sk(ary, &status); \ + if (status) rb_jump_tag(status); \ + \ + return sk; \ +} +OSSL_IMPL_ARY2SK(x509, X509, cX509Cert, DupX509CertPtr) + +#define OSSL_IMPL_SK2ARY(name, type) \ +VALUE \ +ossl_##name##_sk2ary(STACK_OF(type) *sk) \ +{ \ + type *t; \ + int i, num; \ + VALUE ary; \ + \ + if (!sk) { \ + OSSL_Debug("empty sk!"); \ + return Qnil; \ + } \ + num = sk_##type##_num(sk); \ + if (num < 0) { \ + OSSL_Debug("items in sk < -1???"); \ + return rb_ary_new(); \ + } \ + ary = rb_ary_new2(num); \ + \ + for (i=0; i<num; i++) { \ + t = sk_##type##_value(sk, i); \ + rb_ary_push(ary, ossl_##name##_new(t)); \ + } \ + return ary; \ +} +OSSL_IMPL_SK2ARY(x509, X509) +OSSL_IMPL_SK2ARY(x509crl, X509_CRL) +OSSL_IMPL_SK2ARY(x509name, X509_NAME) + +static VALUE +ossl_str_new(int size) +{ + return rb_str_new(0, size); +} + +VALUE +ossl_buf2str(char *buf, int len) +{ + VALUE str; + int status = 0; + + str = rb_protect((VALUE(*)_((VALUE)))ossl_str_new, len, &status); + if(!NIL_P(str)) memcpy(RSTRING_PTR(str), buf, len); + OPENSSL_free(buf); + if(status) rb_jump_tag(status); + + return str; +} + +/* + * our default PEM callback + */ +static VALUE +ossl_pem_passwd_cb0(VALUE flag) +{ + VALUE pass; + + pass = rb_yield(flag); + SafeStringValue(pass); + + return pass; +} + +int +ossl_pem_passwd_cb(char *buf, int max_len, int flag, void *pwd) +{ + int len, status = 0; + VALUE rflag, pass; + + if (pwd || !rb_block_given_p()) + return PEM_def_callback(buf, max_len, flag, pwd); + + while (1) { + /* + * when the flag is nonzero, this passphrase + * will be used to perform encryption; otherwise it will + * be used to perform decryption. + */ + rflag = flag ? Qtrue : Qfalse; + pass = rb_protect(ossl_pem_passwd_cb0, rflag, &status); + if (status) { + /* ignore an exception raised. */ + rb_set_errinfo(Qnil); + return -1; + } + len = RSTRING_LENINT(pass); + if (len < 4) { /* 4 is OpenSSL hardcoded limit */ + rb_warning("password must be longer than 4 bytes"); + continue; + } + if (len > max_len) { + rb_warning("password must be shorter then %d bytes", max_len-1); + continue; + } + memcpy(buf, RSTRING_PTR(pass), len); + break; + } + return len; +} + +/* + * Verify callback + */ +int ossl_verify_cb_idx; + +VALUE +ossl_call_verify_cb_proc(struct ossl_verify_cb_args *args) +{ + return rb_funcall(args->proc, rb_intern("call"), 2, + args->preverify_ok, args->store_ctx); +} + +int +ossl_verify_cb(int ok, X509_STORE_CTX *ctx) +{ + VALUE proc, rctx, ret; + struct ossl_verify_cb_args args; + int state = 0; + + proc = (VALUE)X509_STORE_CTX_get_ex_data(ctx, ossl_verify_cb_idx); + if ((void*)proc == 0) + proc = (VALUE)X509_STORE_get_ex_data(ctx->ctx, ossl_verify_cb_idx); + if ((void*)proc == 0) + return ok; + if (!NIL_P(proc)) { + ret = Qfalse; + rctx = rb_protect((VALUE(*)(VALUE))ossl_x509stctx_new, + (VALUE)ctx, &state); + if (state) { + rb_set_errinfo(Qnil); + rb_warn("StoreContext initialization failure"); + } + else { + args.proc = proc; + args.preverify_ok = ok ? Qtrue : Qfalse; + args.store_ctx = rctx; + ret = rb_protect((VALUE(*)(VALUE))ossl_call_verify_cb_proc, (VALUE)&args, &state); + if (state) { + rb_set_errinfo(Qnil); + rb_warn("exception in verify_callback is ignored"); + } + ossl_x509stctx_clear_ptr(rctx); + } + if (ret == Qtrue) { + X509_STORE_CTX_set_error(ctx, X509_V_OK); + ok = 1; + } + else{ + if (X509_STORE_CTX_get_error(ctx) == X509_V_OK) { + X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_REJECTED); + } + ok = 0; + } + } + + return ok; +} + +/* + * main module + */ +VALUE mOSSL; + +/* + * OpenSSLError < StandardError + */ +VALUE eOSSLError; + +/* + * Convert to DER string + */ +ID ossl_s_to_der; + +VALUE +ossl_to_der(VALUE obj) +{ + VALUE tmp; + + tmp = rb_funcall(obj, ossl_s_to_der, 0); + StringValue(tmp); + + return tmp; +} + +VALUE +ossl_to_der_if_possible(VALUE obj) +{ + if(rb_respond_to(obj, ossl_s_to_der)) + return ossl_to_der(obj); + return obj; +} + +/* + * Errors + */ +static VALUE +ossl_make_error(VALUE exc, const char *fmt, va_list args) +{ + VALUE str = Qnil; + const char *msg; + long e; + +#ifdef HAVE_ERR_PEEK_LAST_ERROR + e = ERR_peek_last_error(); +#else + e = ERR_peek_error(); +#endif + if (fmt) { + str = rb_vsprintf(fmt, args); + } + if (e) { + if (dOSSL == Qtrue) /* FULL INFO */ + msg = ERR_error_string(e, NULL); + else + msg = ERR_reason_error_string(e); + if (NIL_P(str)) { + if (msg) str = rb_str_new_cstr(msg); + } + else { + if (RSTRING_LEN(str)) rb_str_cat2(str, ": "); + rb_str_cat2(str, msg ? msg : "(null)"); + } + } + if (dOSSL == Qtrue){ /* show all errors on the stack */ + while ((e = ERR_get_error()) != 0){ + rb_warn("error on stack: %s", ERR_error_string(e, NULL)); + } + } + ERR_clear_error(); + + if (NIL_P(str)) str = rb_str_new(0, 0); + return rb_exc_new3(exc, str); +} + +void +ossl_raise(VALUE exc, const char *fmt, ...) +{ + va_list args; + VALUE err; + va_start(args, fmt); + err = ossl_make_error(exc, fmt, args); + va_end(args); + rb_exc_raise(err); +} + +VALUE +ossl_exc_new(VALUE exc, const char *fmt, ...) +{ + va_list args; + VALUE err; + va_start(args, fmt); + err = ossl_make_error(exc, fmt, args); + va_end(args); + return err; +} + +/* + * call-seq: + * OpenSSL.errors -> [String...] + * + * See any remaining errors held in queue. + * + * Any errors you see here are probably due to a bug in ruby's OpenSSL implementation. + */ +VALUE +ossl_get_errors(void) +{ + VALUE ary; + long e; + + ary = rb_ary_new(); + while ((e = ERR_get_error()) != 0){ + rb_ary_push(ary, rb_str_new2(ERR_error_string(e, NULL))); + } + + return ary; +} + +/* + * Debug + */ +VALUE dOSSL; + +#if !defined(HAVE_VA_ARGS_MACRO) +void +ossl_debug(const char *fmt, ...) +{ + va_list args; + + if (dOSSL == Qtrue) { + fprintf(stderr, "OSSL_DEBUG: "); + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + fprintf(stderr, " [CONTEXT N/A]\n"); + } +} +#endif + +/* + * call-seq: + * OpenSSL.debug -> true | false + */ +static VALUE +ossl_debug_get(VALUE self) +{ + return dOSSL; +} + +/* + * call-seq: + * OpenSSL.debug = boolean -> boolean + * + * Turns on or off CRYPTO_MEM_CHECK. + * Also shows some debugging message on stderr. + */ +static VALUE +ossl_debug_set(VALUE self, VALUE val) +{ + VALUE old = dOSSL; + dOSSL = val; + + if (old != dOSSL) { + if (dOSSL == Qtrue) { + CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON); + fprintf(stderr, "OSSL_DEBUG: IS NOW ON!\n"); + } else if (old == Qtrue) { + CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_OFF); + fprintf(stderr, "OSSL_DEBUG: IS NOW OFF!\n"); + } + } + return val; +} + +/* + * call-seq: + * OpenSSL.fips_mode = boolean -> boolean + * + * Turns FIPS mode on or off. Turning on FIPS mode will obviously only have an + * effect for FIPS-capable installations of the OpenSSL library. Trying to do + * so otherwise will result in an error. + * + * === Examples + * + * OpenSSL.fips_mode = true # turn FIPS mode on + * OpenSSL.fips_mode = false # and off again + */ +static VALUE +ossl_fips_mode_set(VALUE self, VALUE enabled) +{ + +#ifdef HAVE_OPENSSL_FIPS + if (RTEST(enabled)) { + int mode = FIPS_mode(); + if(!mode && !FIPS_mode_set(1)) /* turning on twice leads to an error */ + ossl_raise(eOSSLError, "Turning on FIPS mode failed"); + } else { + if(!FIPS_mode_set(0)) /* turning off twice is OK */ + ossl_raise(eOSSLError, "Turning off FIPS mode failed"); + } + return enabled; +#else + if (RTEST(enabled)) + ossl_raise(eOSSLError, "This version of OpenSSL does not support FIPS mode"); + return enabled; +#endif +} + +/** + * Stores locks needed for OpenSSL thread safety + */ +#include "ruby/thread_native.h" +static rb_nativethread_lock_t *ossl_locks; + +static void +ossl_lock_unlock(int mode, rb_nativethread_lock_t *lock) +{ + if (mode & CRYPTO_LOCK) { + rb_nativethread_lock_lock(lock); + } else { + rb_nativethread_lock_unlock(lock); + } +} + +static void +ossl_lock_callback(int mode, int type, const char *file, int line) +{ + ossl_lock_unlock(mode, &ossl_locks[type]); +} + +struct CRYPTO_dynlock_value { + rb_nativethread_lock_t lock; +}; + +static struct CRYPTO_dynlock_value * +ossl_dyn_create_callback(const char *file, int line) +{ + struct CRYPTO_dynlock_value *dynlock = (struct CRYPTO_dynlock_value *)OPENSSL_malloc((int)sizeof(struct CRYPTO_dynlock_value)); + rb_nativethread_lock_initialize(&dynlock->lock); + return dynlock; +} + +static void +ossl_dyn_lock_callback(int mode, struct CRYPTO_dynlock_value *l, const char *file, int line) +{ + ossl_lock_unlock(mode, &l->lock); +} + +static void +ossl_dyn_destroy_callback(struct CRYPTO_dynlock_value *l, const char *file, int line) +{ + rb_nativethread_lock_destroy(&l->lock); + OPENSSL_free(l); +} + +#ifdef HAVE_CRYPTO_THREADID_PTR +static void ossl_threadid_func(CRYPTO_THREADID *id) +{ + /* register native thread id */ + CRYPTO_THREADID_set_pointer(id, (void *)rb_nativethread_self()); +} +#else +static unsigned long ossl_thread_id(void) +{ + /* before OpenSSL 1.0, this is 'unsigned long' */ + return (unsigned long)rb_nativethread_self(); +} +#endif + +static void Init_ossl_locks(void) +{ + int i; + int num_locks = CRYPTO_num_locks(); + + if ((unsigned)num_locks >= INT_MAX / (int)sizeof(VALUE)) { + rb_raise(rb_eRuntimeError, "CRYPTO_num_locks() is too big: %d", num_locks); + } + ossl_locks = (rb_nativethread_lock_t *) OPENSSL_malloc(num_locks * (int)sizeof(rb_nativethread_lock_t)); + if (!ossl_locks) { + rb_raise(rb_eNoMemError, "CRYPTO_num_locks() is too big: %d", num_locks); + } + for (i = 0; i < num_locks; i++) { + rb_nativethread_lock_initialize(&ossl_locks[i]); + } + +#ifdef HAVE_CRYPTO_THREADID_PTR + CRYPTO_THREADID_set_callback(ossl_threadid_func); +#else + CRYPTO_set_id_callback(ossl_thread_id); +#endif + CRYPTO_set_locking_callback(ossl_lock_callback); + CRYPTO_set_dynlock_create_callback(ossl_dyn_create_callback); + CRYPTO_set_dynlock_lock_callback(ossl_dyn_lock_callback); + CRYPTO_set_dynlock_destroy_callback(ossl_dyn_destroy_callback); +} + +/* + * OpenSSL provides SSL, TLS and general purpose cryptography. It wraps the + * OpenSSL[http://www.openssl.org/] library. + * + * = Examples + * + * All examples assume you have loaded OpenSSL with: + * + * require 'openssl' + * + * These examples build atop each other. For example the key created in the + * next is used in throughout these examples. + * + * == Keys + * + * === Creating a Key + * + * This example creates a 2048 bit RSA keypair and writes it to the current + * directory. + * + * key = OpenSSL::PKey::RSA.new 2048 + * + * open 'private_key.pem', 'w' do |io| io.write key.to_pem end + * open 'public_key.pem', 'w' do |io| io.write key.public_key.to_pem end + * + * === Exporting a Key + * + * Keys saved to disk without encryption are not secure as anyone who gets + * ahold of the key may use it unless it is encrypted. In order to securely + * export a key you may export it with a pass phrase. + * + * cipher = OpenSSL::Cipher.new 'AES-128-CBC' + * pass_phrase = 'my secure pass phrase goes here' + * + * key_secure = key.export cipher, pass_phrase + * + * open 'private.secure.pem', 'w' do |io| + * io.write key_secure + * end + * + * OpenSSL::Cipher.ciphers returns a list of available ciphers. + * + * === Loading a Key + * + * A key can also be loaded from a file. + * + * key2 = OpenSSL::PKey::RSA.new File.read 'private_key.pem' + * key2.public? # => true + * + * or + * + * key3 = OpenSSL::PKey::RSA.new File.read 'public_key.pem' + * key3.private? # => false + * + * === Loading an Encrypted Key + * + * OpenSSL will prompt you for your pass phrase when loading an encrypted key. + * If you will not be able to type in the pass phrase you may provide it when + * loading the key: + * + * key4_pem = File.read 'private.secure.pem' + * key4 = OpenSSL::PKey::RSA.new key4_pem, pass_phrase + * + * == RSA Encryption + * + * RSA provides encryption and decryption using the public and private keys. + * You can use a variety of padding methods depending upon the intended use of + * encrypted data. + * + * === Encryption & Decryption + * + * Asymmetric public/private key encryption is slow and victim to attack in + * cases where it is used without padding or directly to encrypt larger chunks + * of data. Typical use cases for RSA encryption involve "wrapping" a symmetric + * key with the public key of the recipient who would "unwrap" that symmetric + * key again using their private key. + * The following illustrates a simplified example of such a key transport + * scheme. It shouldn't be used in practice, though, standardized protocols + * should always be preferred. + * + * wrapped_key = key.public_encrypt key + * + * A symmetric key encrypted with the public key can only be decrypted with + * the corresponding private key of the recipient. + * + * original_key = key.private_decrypt wrapped_key + * + * By default PKCS#1 padding will be used, but it is also possible to use + * other forms of padding, see PKey::RSA for further details. + * + * === Signatures + * + * Using "private_encrypt" to encrypt some data with the private key is + * equivalent to applying a digital signature to the data. A verifying + * party may validate the signature by comparing the result of decrypting + * the signature with "public_decrypt" to the original data. However, + * OpenSSL::PKey already has methods "sign" and "verify" that handle + * digital signatures in a standardized way - "private_encrypt" and + * "public_decrypt" shouldn't be used in practice. + * + * To sign a document, a cryptographically secure hash of the document is + * computed first, which is then signed using the private key. + * + * digest = OpenSSL::Digest::SHA256.new + * signature = key.sign digest, document + * + * To validate the signature, again a hash of the document is computed and + * the signature is decrypted using the public key. The result is then + * compared to the hash just computed, if they are equal the signature was + * valid. + * + * digest = OpenSSL::Digest::SHA256.new + * if key.verify digest, signature, document + * puts 'Valid' + * else + * puts 'Invalid' + * end + * + * == PBKDF2 Password-based Encryption + * + * If supported by the underlying OpenSSL version used, Password-based + * Encryption should use the features of PKCS5. If not supported or if + * required by legacy applications, the older, less secure methods specified + * in RFC 2898 are also supported (see below). + * + * PKCS5 supports PBKDF2 as it was specified in PKCS#5 + * v2.0[http://www.rsa.com/rsalabs/node.asp?id=2127]. It still uses a + * password, a salt, and additionally a number of iterations that will + * slow the key derivation process down. The slower this is, the more work + * it requires being able to brute-force the resulting key. + * + * === Encryption + * + * The strategy is to first instantiate a Cipher for encryption, and + * then to generate a random IV plus a key derived from the password + * using PBKDF2. PKCS #5 v2.0 recommends at least 8 bytes for the salt, + * the number of iterations largely depends on the hardware being used. + * + * cipher = OpenSSL::Cipher.new 'AES-128-CBC' + * cipher.encrypt + * iv = cipher.random_iv + * + * pwd = 'some hopefully not to easily guessable password' + * salt = OpenSSL::Random.random_bytes 16 + * iter = 20000 + * key_len = cipher.key_len + * digest = OpenSSL::Digest::SHA256.new + * + * key = OpenSSL::PKCS5.pbkdf2_hmac(pwd, salt, iter, key_len, digest) + * cipher.key = key + * + * Now encrypt the data: + * + * encrypted = cipher.update document + * encrypted << cipher.final + * + * === Decryption + * + * Use the same steps as before to derive the symmetric AES key, this time + * setting the Cipher up for decryption. + * + * cipher = OpenSSL::Cipher.new 'AES-128-CBC' + * cipher.decrypt + * cipher.iv = iv # the one generated with #random_iv + * + * pwd = 'some hopefully not to easily guessable password' + * salt = ... # the one generated above + * iter = 20000 + * key_len = cipher.key_len + * digest = OpenSSL::Digest::SHA256.new + * + * key = OpenSSL::PKCS5.pbkdf2_hmac(pwd, salt, iter, key_len, digest) + * cipher.key = key + * + * Now decrypt the data: + * + * decrypted = cipher.update encrypted + * decrypted << cipher.final + * + * == PKCS #5 Password-based Encryption + * + * PKCS #5 is a password-based encryption standard documented at + * RFC2898[http://www.ietf.org/rfc/rfc2898.txt]. It allows a short password or + * passphrase to be used to create a secure encryption key. If possible, PBKDF2 + * as described above should be used if the circumstances allow it. + * + * PKCS #5 uses a Cipher, a pass phrase and a salt to generate an encryption + * key. + * + * pass_phrase = 'my secure pass phrase goes here' + * salt = '8 octets' + * + * === Encryption + * + * First set up the cipher for encryption + * + * encryptor = OpenSSL::Cipher.new 'AES-128-CBC' + * encryptor.encrypt + * encryptor.pkcs5_keyivgen pass_phrase, salt + * + * Then pass the data you want to encrypt through + * + * encrypted = encryptor.update 'top secret document' + * encrypted << encryptor.final + * + * === Decryption + * + * Use a new Cipher instance set up for decryption + * + * decryptor = OpenSSL::Cipher.new 'AES-128-CBC' + * decryptor.decrypt + * decryptor.pkcs5_keyivgen pass_phrase, salt + * + * Then pass the data you want to decrypt through + * + * plain = decryptor.update encrypted + * plain << decryptor.final + * + * == X509 Certificates + * + * === Creating a Certificate + * + * This example creates a self-signed certificate using an RSA key and a SHA1 + * signature. + * + * name = OpenSSL::X509::Name.parse 'CN=nobody/DC=example' + * + * cert = OpenSSL::X509::Certificate.new + * cert.version = 2 + * cert.serial = 0 + * cert.not_before = Time.now + * cert.not_after = Time.now + 3600 + * + * cert.public_key = key.public_key + * cert.subject = name + * + * === Certificate Extensions + * + * You can add extensions to the certificate with + * OpenSSL::SSL::ExtensionFactory to indicate the purpose of the certificate. + * + * extension_factory = OpenSSL::X509::ExtensionFactory.new nil, cert + * + * cert.add_extension \ + * extension_factory.create_extension('basicConstraints', 'CA:FALSE', true) + * + * cert.add_extension \ + * extension_factory.create_extension( + * 'keyUsage', 'keyEncipherment,dataEncipherment,digitalSignature') + * + * cert.add_extension \ + * extension_factory.create_extension('subjectKeyIdentifier', 'hash') + * + * The list of supported extensions (and in some cases their possible values) + * can be derived from the "objects.h" file in the OpenSSL source code. + * + * === Signing a Certificate + * + * To sign a certificate set the issuer and use OpenSSL::X509::Certificate#sign + * with a digest algorithm. This creates a self-signed cert because we're using + * the same name and key to sign the certificate as was used to create the + * certificate. + * + * cert.issuer = name + * cert.sign key, OpenSSL::Digest::SHA1.new + * + * open 'certificate.pem', 'w' do |io| io.write cert.to_pem end + * + * === Loading a Certificate + * + * Like a key, a cert can also be loaded from a file. + * + * cert2 = OpenSSL::X509::Certificate.new File.read 'certificate.pem' + * + * === Verifying a Certificate + * + * Certificate#verify will return true when a certificate was signed with the + * given public key. + * + * raise 'certificate can not be verified' unless cert2.verify key + * + * == Certificate Authority + * + * A certificate authority (CA) is a trusted third party that allows you to + * verify the ownership of unknown certificates. The CA issues key signatures + * that indicate it trusts the user of that key. A user encountering the key + * can verify the signature by using the CA's public key. + * + * === CA Key + * + * CA keys are valuable, so we encrypt and save it to disk and make sure it is + * not readable by other users. + * + * ca_key = OpenSSL::PKey::RSA.new 2048 + * + * cipher = OpenSSL::Cipher::Cipher.new 'AES-128-CBC' + * + * open 'ca_key.pem', 'w', 0400 do |io| + * io.write ca_key.export(cipher, pass_phrase) + * end + * + * === CA Certificate + * + * A CA certificate is created the same way we created a certificate above, but + * with different extensions. + * + * ca_name = OpenSSL::X509::Name.parse 'CN=ca/DC=example' + * + * ca_cert = OpenSSL::X509::Certificate.new + * ca_cert.serial = 0 + * ca_cert.version = 2 + * ca_cert.not_before = Time.now + * ca_cert.not_after = Time.now + 86400 + * + * ca_cert.public_key = ca_key.public_key + * ca_cert.subject = ca_name + * ca_cert.issuer = ca_name + * + * extension_factory = OpenSSL::X509::ExtensionFactory.new + * extension_factory.subject_certificate = ca_cert + * extension_factory.issuer_certificate = ca_cert + * + * ca_cert.add_extension \ + * extension_factory.create_extension('subjectKeyIdentifier', 'hash') + * + * This extension indicates the CA's key may be used as a CA. + * + * ca_cert.add_extension \ + * extension_factory.create_extension('basicConstraints', 'CA:TRUE', true) + * + * This extension indicates the CA's key may be used to verify signatures on + * both certificates and certificate revocations. + * + * ca_cert.add_extension \ + * extension_factory.create_extension( + * 'keyUsage', 'cRLSign,keyCertSign', true) + * + * Root CA certificates are self-signed. + * + * ca_cert.sign ca_key, OpenSSL::Digest::SHA1.new + * + * The CA certificate is saved to disk so it may be distributed to all the + * users of the keys this CA will sign. + * + * open 'ca_cert.pem', 'w' do |io| + * io.write ca_cert.to_pem + * end + * + * === Certificate Signing Request + * + * The CA signs keys through a Certificate Signing Request (CSR). The CSR + * contains the information necessary to identify the key. + * + * csr = OpenSSL::X509::Request.new + * csr.version = 0 + * csr.subject = name + * csr.public_key = key.public_key + * csr.sign key, OpenSSL::Digest::SHA1.new + * + * A CSR is saved to disk and sent to the CA for signing. + * + * open 'csr.pem', 'w' do |io| + * io.write csr.to_pem + * end + * + * === Creating a Certificate from a CSR + * + * Upon receiving a CSR the CA will verify it before signing it. A minimal + * verification would be to check the CSR's signature. + * + * csr = OpenSSL::X509::Request.new File.read 'csr.pem' + * + * raise 'CSR can not be verified' unless csr.verify csr.public_key + * + * After verification a certificate is created, marked for various usages, + * signed with the CA key and returned to the requester. + * + * csr_cert = OpenSSL::X509::Certificate.new + * csr_cert.serial = 0 + * csr_cert.version = 2 + * csr_cert.not_before = Time.now + * csr_cert.not_after = Time.now + 600 + * + * csr_cert.subject = csr.subject + * csr_cert.public_key = csr.public_key + * csr_cert.issuer = ca_cert.subject + * + * extension_factory = OpenSSL::X509::ExtensionFactory.new + * extension_factory.subject_certificate = csr_cert + * extension_factory.issuer_certificate = ca_cert + * + * csr_cert.add_extension \ + * extension_factory.create_extension('basicConstraints', 'CA:FALSE') + * + * csr_cert.add_extension \ + * extension_factory.create_extension( + * 'keyUsage', 'keyEncipherment,dataEncipherment,digitalSignature') + * + * csr_cert.add_extension \ + * extension_factory.create_extension('subjectKeyIdentifier', 'hash') + * + * csr_cert.sign ca_key, OpenSSL::Digest::SHA1.new + * + * open 'csr_cert.pem', 'w' do |io| + * io.write csr_cert.to_pem + * end + * + * == SSL and TLS Connections + * + * Using our created key and certificate we can create an SSL or TLS connection. + * An SSLContext is used to set up an SSL session. + * + * context = OpenSSL::SSL::SSLContext.new + * + * === SSL Server + * + * An SSL server requires the certificate and private key to communicate + * securely with its clients: + * + * context.cert = cert + * context.key = key + * + * Then create an SSLServer with a TCP server socket and the context. Use the + * SSLServer like an ordinary TCP server. + * + * require 'socket' + * + * tcp_server = TCPServer.new 5000 + * ssl_server = OpenSSL::SSL::SSLServer.new tcp_server, context + * + * loop do + * ssl_connection = ssl_server.accept + * + * data = connection.gets + * + * response = "I got #{data.dump}" + * puts response + * + * connection.puts "I got #{data.dump}" + * connection.close + * end + * + * === SSL client + * + * An SSL client is created with a TCP socket and the context. + * SSLSocket#connect must be called to initiate the SSL handshake and start + * encryption. A key and certificate are not required for the client socket. + * + * require 'socket' + * + * tcp_client = TCPSocket.new 'localhost', 5000 + * ssl_client = OpenSSL::SSL::SSLSocket.new client_socket, context + * ssl_client.connect + * + * ssl_client.puts "hello server!" + * puts ssl_client.gets + * + * === Peer Verification + * + * An unverified SSL connection does not provide much security. For enhanced + * security the client or server can verify the certificate of its peer. + * + * The client can be modified to verify the server's certificate against the + * certificate authority's certificate: + * + * context.ca_file = 'ca_cert.pem' + * context.verify_mode = OpenSSL::SSL::VERIFY_PEER + * + * require 'socket' + * + * tcp_client = TCPSocket.new 'localhost', 5000 + * ssl_client = OpenSSL::SSL::SSLSocket.new client_socket, context + * ssl_client.connect + * + * ssl_client.puts "hello server!" + * puts ssl_client.gets + * + * If the server certificate is invalid or <tt>context.ca_file</tt> is not set + * when verifying peers an OpenSSL::SSL::SSLError will be raised. + * + */ +void +Init_openssl(void) +{ + /* + * Init timezone info + */ +#if 0 + tzset(); +#endif + + /* + * Init all digests, ciphers + */ + /* CRYPTO_malloc_init(); */ + /* ENGINE_load_builtin_engines(); */ + OpenSSL_add_ssl_algorithms(); + OpenSSL_add_all_algorithms(); + ERR_load_crypto_strings(); + SSL_load_error_strings(); + + /* + * FIXME: + * On unload do: + */ +#if 0 + CONF_modules_unload(1); + destroy_ui_method(); + EVP_cleanup(); + ENGINE_cleanup(); + CRYPTO_cleanup_all_ex_data(); + ERR_remove_state(0); + ERR_free_strings(); +#endif + + /* + * Init main module + */ + mOSSL = rb_define_module("OpenSSL"); + rb_global_variable(&mOSSL); + + /* + * OpenSSL ruby extension version + */ + rb_define_const(mOSSL, "VERSION", rb_str_new2(OSSL_VERSION)); + + /* + * Version of OpenSSL the ruby OpenSSL extension was built with + */ + rb_define_const(mOSSL, "OPENSSL_VERSION", rb_str_new2(OPENSSL_VERSION_TEXT)); + + /* + * Version of OpenSSL the ruby OpenSSL extension is running with + */ + rb_define_const(mOSSL, "OPENSSL_LIBRARY_VERSION", rb_str_new2(SSLeay_version(SSLEAY_VERSION))); + + /* + * Version number of OpenSSL the ruby OpenSSL extension was built with + * (base 16) + */ + rb_define_const(mOSSL, "OPENSSL_VERSION_NUMBER", INT2NUM(OPENSSL_VERSION_NUMBER)); + + /* + * Boolean indicating whether OpenSSL is FIPS-enabled or not + */ +#ifdef HAVE_OPENSSL_FIPS + rb_define_const(mOSSL, "OPENSSL_FIPS", Qtrue); +#else + rb_define_const(mOSSL, "OPENSSL_FIPS", Qfalse); +#endif + rb_define_module_function(mOSSL, "fips_mode=", ossl_fips_mode_set, 1); + + /* + * Generic error, + * common for all classes under OpenSSL module + */ + eOSSLError = rb_define_class_under(mOSSL,"OpenSSLError",rb_eStandardError); + rb_global_variable(&eOSSLError); + + /* + * Verify callback Proc index for ext-data + */ + if ((ossl_verify_cb_idx = X509_STORE_CTX_get_ex_new_index(0, (void *)"ossl_verify_cb_idx", 0, 0, 0)) < 0) + ossl_raise(eOSSLError, "X509_STORE_CTX_get_ex_new_index"); + + /* + * Init debug core + */ + dOSSL = Qfalse; + rb_global_variable(&dOSSL); + + rb_define_module_function(mOSSL, "debug", ossl_debug_get, 0); + rb_define_module_function(mOSSL, "debug=", ossl_debug_set, 1); + rb_define_module_function(mOSSL, "errors", ossl_get_errors, 0); + + /* + * Get ID of to_der + */ + ossl_s_to_der = rb_intern("to_der"); + + Init_ossl_locks(); + + /* + * Init components + */ + Init_ossl_bn(); + Init_ossl_cipher(); + Init_ossl_config(); + Init_ossl_digest(); + Init_ossl_hmac(); + Init_ossl_ns_spki(); + Init_ossl_pkcs12(); + Init_ossl_pkcs7(); + Init_ossl_pkcs5(); + Init_ossl_pkey(); + Init_ossl_rand(); + Init_ossl_ssl(); + Init_ossl_x509(); + Init_ossl_ocsp(); + Init_ossl_engine(); + Init_ossl_asn1(); +} + +#if defined(OSSL_DEBUG) +/* + * Check if all symbols are OK with 'make LDSHARED=gcc all' + */ +int +main(int argc, char *argv[]) +{ + return 0; +} +#endif /* OSSL_DEBUG */ + diff --git a/ext/openssl/ossl.h b/ext/openssl/ossl.h new file mode 100644 index 00000000..c843c06f --- /dev/null +++ b/ext/openssl/ossl.h @@ -0,0 +1,247 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2001-2002 Michal Rokos <m.rokos@sh.cvut.cz> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#if !defined(_OSSL_H_) +#define _OSSL_H_ + +#include RUBY_EXTCONF_H + +#if defined(__cplusplus) +extern "C" { +#endif + +#if 0 + mOSSL = rb_define_module("OpenSSL"); + mX509 = rb_define_module_under(mOSSL, "X509"); +#endif + +/* +* OpenSSL has defined RFILE and Ruby has defined RFILE - so undef it! +*/ +#if defined(RFILE) /*&& !defined(OSSL_DEBUG)*/ +# undef RFILE +#endif +#include <ruby.h> +#include <ruby/io.h> +#include <ruby/thread.h> + +/* + * Check the OpenSSL version + * The only supported are: + * OpenSSL >= 0.9.7 + */ +#include <openssl/opensslv.h> + +#ifdef HAVE_ASSERT_H +# include <assert.h> +#else +# define assert(condition) +#endif + +#if defined(_WIN32) +# include <openssl/e_os2.h> +# define OSSL_NO_CONF_API 1 +# if !defined(OPENSSL_SYS_WIN32) +# define OPENSSL_SYS_WIN32 1 +# endif +# include <winsock2.h> +#endif +#include <errno.h> +#include <openssl/err.h> +#include <openssl/asn1_mac.h> +#include <openssl/x509v3.h> +#include <openssl/ssl.h> +#include <openssl/pkcs12.h> +#include <openssl/pkcs7.h> +#include <openssl/hmac.h> +#include <openssl/rand.h> +#include <openssl/conf.h> +#include <openssl/conf_api.h> +#undef X509_NAME +#undef PKCS7_SIGNER_INFO +#if defined(HAVE_OPENSSL_ENGINE_H) && defined(HAVE_EVP_CIPHER_CTX_ENGINE) +# define OSSL_ENGINE_ENABLED +# include <openssl/engine.h> +#endif +#if defined(HAVE_OPENSSL_OCSP_H) +# define OSSL_OCSP_ENABLED +# include <openssl/ocsp.h> +#endif + +/* OpenSSL requires passwords for PEM-encoded files to be at least four + * characters long + */ +#define OSSL_MIN_PWD_LEN 4 + +/* + * Common Module + */ +extern VALUE mOSSL; + +/* + * Common Error Class + */ +extern VALUE eOSSLError; + +/* + * CheckTypes + */ +#define OSSL_Check_Kind(obj, klass) do {\ + if (!rb_obj_is_kind_of((obj), (klass))) {\ + ossl_raise(rb_eTypeError, "wrong argument (%"PRIsVALUE")! (Expected kind of %"PRIsVALUE")",\ + rb_obj_class(obj), (klass));\ + }\ +} while (0) + +#define OSSL_Check_Instance(obj, klass) do {\ + if (!rb_obj_is_instance_of((obj), (klass))) {\ + ossl_raise(rb_eTypeError, "wrong argument (%"PRIsVALUE")! (Expected instance of %"PRIsVALUE")",\ + rb_obj_class(obj), (klass));\ + }\ +} while (0) + +#define OSSL_Check_Same_Class(obj1, obj2) do {\ + if (!rb_obj_is_instance_of((obj1), rb_obj_class(obj2))) {\ + ossl_raise(rb_eTypeError, "wrong argument type");\ + }\ +} while (0) + +/* + * Compatibility + */ +#if OPENSSL_VERSION_NUMBER >= 0x10000000L +#define STACK _STACK +#endif + +/* + * String to HEXString conversion + */ +int string2hex(const unsigned char *, int, char **, int *); + +/* + * Data Conversion + */ +STACK_OF(X509) *ossl_x509_ary2sk0(VALUE); +STACK_OF(X509) *ossl_x509_ary2sk(VALUE); +STACK_OF(X509) *ossl_protect_x509_ary2sk(VALUE,int*); +VALUE ossl_x509_sk2ary(STACK_OF(X509) *certs); +VALUE ossl_x509crl_sk2ary(STACK_OF(X509_CRL) *crl); +VALUE ossl_x509name_sk2ary(STACK_OF(X509_NAME) *names); +VALUE ossl_buf2str(char *buf, int len); +#define ossl_str_adjust(str, p) \ +do{\ + long len = RSTRING_LEN(str);\ + long newlen = (long)((p) - (unsigned char*)RSTRING_PTR(str));\ + assert(newlen <= len);\ + rb_str_set_len((str), newlen);\ +}while(0) + +/* + * our default PEM callback + */ +int ossl_pem_passwd_cb(char *, int, int, void *); + +/* + * Clear BIO* with this in PEM/DER fallback scenarios to avoid decoding + * errors piling up in OpenSSL::Errors + */ +#define OSSL_BIO_reset(bio) (void)BIO_reset((bio)); \ + ERR_clear_error(); + +/* + * ERRor messages + */ +#define OSSL_ErrMsg() ERR_reason_error_string(ERR_get_error()) +NORETURN(void ossl_raise(VALUE, const char *, ...)); +VALUE ossl_exc_new(VALUE, const char *, ...); + +/* + * Verify callback + */ +extern int ossl_verify_cb_idx; + +struct ossl_verify_cb_args { + VALUE proc; + VALUE preverify_ok; + VALUE store_ctx; +}; + +VALUE ossl_call_verify_cb_proc(struct ossl_verify_cb_args *); +int ossl_verify_cb(int, X509_STORE_CTX *); + +/* + * String to DER String + */ +extern ID ossl_s_to_der; +VALUE ossl_to_der(VALUE); +VALUE ossl_to_der_if_possible(VALUE); + +/* + * Debug + */ +extern VALUE dOSSL; + +#if defined(HAVE_VA_ARGS_MACRO) +#define OSSL_Debug(...) do { \ + if (dOSSL == Qtrue) { \ + fprintf(stderr, "OSSL_DEBUG: "); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, " [%s:%d]\n", __FILE__, __LINE__); \ + } \ +} while (0) + +#define OSSL_Warning(fmt, ...) do { \ + OSSL_Debug((fmt), ##__VA_ARGS__); \ + rb_warning((fmt), ##__VA_ARGS__); \ +} while (0) + +#define OSSL_Warn(fmt, ...) do { \ + OSSL_Debug((fmt), ##__VA_ARGS__); \ + rb_warn((fmt), ##__VA_ARGS__); \ +} while (0) +#else +void ossl_debug(const char *, ...); +#define OSSL_Debug ossl_debug +#define OSSL_Warning rb_warning +#define OSSL_Warn rb_warn +#endif + +/* + * Include all parts + */ +#include "openssl_missing.h" +#include "ruby_missing.h" +#include "ossl_asn1.h" +#include "ossl_bio.h" +#include "ossl_bn.h" +#include "ossl_cipher.h" +#include "ossl_config.h" +#include "ossl_digest.h" +#include "ossl_hmac.h" +#include "ossl_ns_spki.h" +#include "ossl_ocsp.h" +#include "ossl_pkcs12.h" +#include "ossl_pkcs7.h" +#include "ossl_pkcs5.h" +#include "ossl_pkey.h" +#include "ossl_rand.h" +#include "ossl_ssl.h" +#include "ossl_version.h" +#include "ossl_x509.h" +#include "ossl_engine.h" + +void Init_openssl(void); + +#if defined(__cplusplus) +} +#endif + +#endif /* _OSSL_H_ */ + diff --git a/ext/openssl/ossl_asn1.c b/ext/openssl/ossl_asn1.c new file mode 100644 index 00000000..3a24d17d --- /dev/null +++ b/ext/openssl/ossl_asn1.c @@ -0,0 +1,1997 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' team members + * Copyright (C) 2003 + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#include "ossl.h" + +#if defined(HAVE_SYS_TIME_H) +# include <sys/time.h> +#elif !defined(NT) && !defined(_WIN32) +struct timeval { + long tv_sec; /* seconds */ + long tv_usec; /* and microseconds */ +}; +#endif + +static VALUE join_der(VALUE enumerable); +static VALUE ossl_asn1_decode0(unsigned char **pp, long length, long *offset, + int depth, int yield, long *num_read); +static VALUE ossl_asn1_initialize(int argc, VALUE *argv, VALUE self); +static VALUE ossl_asn1eoc_initialize(VALUE self); + +/* + * DATE conversion + */ +VALUE +asn1time_to_time(ASN1_TIME *time) +{ + struct tm tm; + VALUE argv[6]; + int count; + + if (!time || !time->data) return Qnil; + memset(&tm, 0, sizeof(struct tm)); + + switch (time->type) { + case V_ASN1_UTCTIME: + count = sscanf((const char *)time->data, "%2d%2d%2d%2d%2d%2dZ", + &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, + &tm.tm_sec); + + if (count == 5) { + tm.tm_sec = 0; + } else if (count != 6) { + ossl_raise(rb_eTypeError, "bad UTCTIME format: \"%s\"", + time->data); + } + if (tm.tm_year < 69) { + tm.tm_year += 2000; + } else { + tm.tm_year += 1900; + } + break; + case V_ASN1_GENERALIZEDTIME: + if (sscanf((const char *)time->data, "%4d%2d%2d%2d%2d%2dZ", &tm.tm_year, &tm.tm_mon, + &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) { + ossl_raise(rb_eTypeError, "bad GENERALIZEDTIME format" ); + } + break; + default: + rb_warning("unknown time format"); + return Qnil; + } + argv[0] = INT2NUM(tm.tm_year); + argv[1] = INT2NUM(tm.tm_mon); + argv[2] = INT2NUM(tm.tm_mday); + argv[3] = INT2NUM(tm.tm_hour); + argv[4] = INT2NUM(tm.tm_min); + argv[5] = INT2NUM(tm.tm_sec); + + return rb_funcall2(rb_cTime, rb_intern("utc"), 6, argv); +} + +/* + * This function is not exported in Ruby's *.h + */ +extern struct timeval rb_time_timeval(VALUE); + +time_t +time_to_time_t(VALUE time) +{ + return (time_t)NUM2LONG(rb_Integer(time)); +} + +/* + * STRING conversion + */ +VALUE +asn1str_to_str(ASN1_STRING *str) +{ + return rb_str_new((const char *)str->data, str->length); +} + +/* + * ASN1_INTEGER conversions + * TODO: Make a decision what's the right way to do this. + */ +#define DO_IT_VIA_RUBY 0 +VALUE +asn1integer_to_num(ASN1_INTEGER *ai) +{ + BIGNUM *bn; +#if DO_IT_VIA_RUBY + char *txt; +#endif + VALUE num; + + if (!ai) { + ossl_raise(rb_eTypeError, "ASN1_INTEGER is NULL!"); + } + if (!(bn = ASN1_INTEGER_to_BN(ai, NULL))) { + ossl_raise(eOSSLError, NULL); + } +#if DO_IT_VIA_RUBY + if (!(txt = BN_bn2dec(bn))) { + BN_free(bn); + ossl_raise(eOSSLError, NULL); + } + num = rb_cstr_to_inum(txt, 10, Qtrue); + OPENSSL_free(txt); +#else + num = ossl_bn_new(bn); +#endif + BN_free(bn); + + return num; +} + +#if DO_IT_VIA_RUBY +ASN1_INTEGER * +num_to_asn1integer(VALUE obj, ASN1_INTEGER *ai) +{ + BIGNUM *bn = NULL; + + if (RTEST(rb_obj_is_kind_of(obj, cBN))) { + bn = GetBNPtr(obj); + } else { + obj = rb_String(obj); + if (!BN_dec2bn(&bn, StringValuePtr(obj))) { + ossl_raise(eOSSLError, NULL); + } + } + if (!(ai = BN_to_ASN1_INTEGER(bn, ai))) { + BN_free(bn); + ossl_raise(eOSSLError, NULL); + } + BN_free(bn); + return ai; +} +#else +ASN1_INTEGER * +num_to_asn1integer(VALUE obj, ASN1_INTEGER *ai) +{ + BIGNUM *bn; + + if (NIL_P(obj)) + ossl_raise(rb_eTypeError, "Can't convert nil into Integer"); + + bn = GetBNPtr(obj); + + if (!(ai = BN_to_ASN1_INTEGER(bn, ai))) + ossl_raise(eOSSLError, NULL); + + return ai; +} +#endif + +/********/ +/* + * ASN1 module + */ +#define ossl_asn1_get_value(o) rb_attr_get((o),sivVALUE) +#define ossl_asn1_get_tag(o) rb_attr_get((o),sivTAG) +#define ossl_asn1_get_tagging(o) rb_attr_get((o),sivTAGGING) +#define ossl_asn1_get_tag_class(o) rb_attr_get((o),sivTAG_CLASS) +#define ossl_asn1_get_infinite_length(o) rb_attr_get((o),sivINFINITE_LENGTH) + +#define ossl_asn1_set_value(o,v) rb_ivar_set((o),sivVALUE,(v)) +#define ossl_asn1_set_tag(o,v) rb_ivar_set((o),sivTAG,(v)) +#define ossl_asn1_set_tagging(o,v) rb_ivar_set((o),sivTAGGING,(v)) +#define ossl_asn1_set_tag_class(o,v) rb_ivar_set((o),sivTAG_CLASS,(v)) +#define ossl_asn1_set_infinite_length(o,v) rb_ivar_set((o),sivINFINITE_LENGTH,(v)) + +VALUE mASN1; +VALUE eASN1Error; + +VALUE cASN1Data; +VALUE cASN1Primitive; +VALUE cASN1Constructive; + +VALUE cASN1EndOfContent; +VALUE cASN1Boolean; /* BOOLEAN */ +VALUE cASN1Integer, cASN1Enumerated; /* INTEGER */ +VALUE cASN1BitString; /* BIT STRING */ +VALUE cASN1OctetString, cASN1UTF8String; /* STRINGs */ +VALUE cASN1NumericString, cASN1PrintableString; +VALUE cASN1T61String, cASN1VideotexString; +VALUE cASN1IA5String, cASN1GraphicString; +VALUE cASN1ISO64String, cASN1GeneralString; +VALUE cASN1UniversalString, cASN1BMPString; +VALUE cASN1Null; /* NULL */ +VALUE cASN1ObjectId; /* OBJECT IDENTIFIER */ +VALUE cASN1UTCTime, cASN1GeneralizedTime; /* TIME */ +VALUE cASN1Sequence, cASN1Set; /* CONSTRUCTIVE */ + +static ID sIMPLICIT, sEXPLICIT; +static ID sUNIVERSAL, sAPPLICATION, sCONTEXT_SPECIFIC, sPRIVATE; +static ID sivVALUE, sivTAG, sivTAG_CLASS, sivTAGGING, sivINFINITE_LENGTH, sivUNUSED_BITS; + +/* + * We need to implement these for backward compatibility + * reasons, behavior of ASN1_put_object and ASN1_object_size + * for infinite length values is different in OpenSSL <= 0.9.7 + */ +#if OPENSSL_VERSION_NUMBER < 0x00908000L +#define ossl_asn1_object_size(cons, len, tag) (cons) == 2 ? (len) + ASN1_object_size((cons), 0, (tag)) : ASN1_object_size((cons), (len), (tag)) +#define ossl_asn1_put_object(pp, cons, len, tag, xc) (cons) == 2 ? ASN1_put_object((pp), (cons), 0, (tag), (xc)) : ASN1_put_object((pp), (cons), (len), (tag), (xc)) +#else +#define ossl_asn1_object_size(cons, len, tag) ASN1_object_size((cons), (len), (tag)) +#define ossl_asn1_put_object(pp, cons, len, tag, xc) ASN1_put_object((pp), (cons), (len), (tag), (xc)) +#endif + +/* + * Ruby to ASN1 converters + */ +static ASN1_BOOLEAN +obj_to_asn1bool(VALUE obj) +{ + if (NIL_P(obj)) + ossl_raise(rb_eTypeError, "Can't convert nil into Boolean"); + +#if OPENSSL_VERSION_NUMBER < 0x00907000L + return RTEST(obj) ? 0xff : 0x100; +#else + return RTEST(obj) ? 0xff : 0x0; +#endif +} + +static ASN1_INTEGER* +obj_to_asn1int(VALUE obj) +{ + return num_to_asn1integer(obj, NULL); +} + +static ASN1_BIT_STRING* +obj_to_asn1bstr(VALUE obj, long unused_bits) +{ + ASN1_BIT_STRING *bstr; + + if(unused_bits < 0) unused_bits = 0; + StringValue(obj); + if(!(bstr = ASN1_BIT_STRING_new())) + ossl_raise(eASN1Error, NULL); + ASN1_BIT_STRING_set(bstr, (unsigned char *)RSTRING_PTR(obj), RSTRING_LENINT(obj)); + bstr->flags &= ~(ASN1_STRING_FLAG_BITS_LEFT|0x07); /* clear */ + bstr->flags |= ASN1_STRING_FLAG_BITS_LEFT|(unused_bits&0x07); + + return bstr; +} + +static ASN1_STRING* +obj_to_asn1str(VALUE obj) +{ + ASN1_STRING *str; + + StringValue(obj); + if(!(str = ASN1_STRING_new())) + ossl_raise(eASN1Error, NULL); + ASN1_STRING_set(str, RSTRING_PTR(obj), RSTRING_LENINT(obj)); + + return str; +} + +static ASN1_NULL* +obj_to_asn1null(VALUE obj) +{ + ASN1_NULL *null; + + if(!NIL_P(obj)) + ossl_raise(eASN1Error, "nil expected"); + if(!(null = ASN1_NULL_new())) + ossl_raise(eASN1Error, NULL); + + return null; +} + +static ASN1_OBJECT* +obj_to_asn1obj(VALUE obj) +{ + ASN1_OBJECT *a1obj; + + StringValue(obj); + a1obj = OBJ_txt2obj(RSTRING_PTR(obj), 0); + if(!a1obj) a1obj = OBJ_txt2obj(RSTRING_PTR(obj), 1); + if(!a1obj) ossl_raise(eASN1Error, "invalid OBJECT ID"); + + return a1obj; +} + +static ASN1_UTCTIME* +obj_to_asn1utime(VALUE time) +{ + time_t sec; + ASN1_UTCTIME *t; + + sec = time_to_time_t(time); + if(!(t = ASN1_UTCTIME_set(NULL, sec))) + ossl_raise(eASN1Error, NULL); + + return t; +} + +static ASN1_GENERALIZEDTIME* +obj_to_asn1gtime(VALUE time) +{ + time_t sec; + ASN1_GENERALIZEDTIME *t; + + sec = time_to_time_t(time); + if(!(t =ASN1_GENERALIZEDTIME_set(NULL, sec))) + ossl_raise(eASN1Error, NULL); + + return t; +} + +static ASN1_STRING* +obj_to_asn1derstr(VALUE obj) +{ + ASN1_STRING *a1str; + VALUE str; + + str = ossl_to_der(obj); + if(!(a1str = ASN1_STRING_new())) + ossl_raise(eASN1Error, NULL); + ASN1_STRING_set(a1str, RSTRING_PTR(str), RSTRING_LENINT(str)); + + return a1str; +} + +/* + * DER to Ruby converters + */ +static VALUE +decode_bool(unsigned char* der, long length) +{ + int val; + const unsigned char *p; + + p = der; + if((val = d2i_ASN1_BOOLEAN(NULL, &p, length)) < 0) + ossl_raise(eASN1Error, NULL); + + return val ? Qtrue : Qfalse; +} + +static VALUE +decode_int(unsigned char* der, long length) +{ + ASN1_INTEGER *ai; + const unsigned char *p; + VALUE ret; + int status = 0; + + p = der; + if(!(ai = d2i_ASN1_INTEGER(NULL, &p, length))) + ossl_raise(eASN1Error, NULL); + ret = rb_protect((VALUE(*)_((VALUE)))asn1integer_to_num, + (VALUE)ai, &status); + ASN1_INTEGER_free(ai); + if(status) rb_jump_tag(status); + + return ret; +} + +static VALUE +decode_bstr(unsigned char* der, long length, long *unused_bits) +{ + ASN1_BIT_STRING *bstr; + const unsigned char *p; + long len; + VALUE ret; + + p = der; + if(!(bstr = d2i_ASN1_BIT_STRING(NULL, &p, length))) + ossl_raise(eASN1Error, NULL); + len = bstr->length; + *unused_bits = 0; + if(bstr->flags & ASN1_STRING_FLAG_BITS_LEFT) + *unused_bits = bstr->flags & 0x07; + ret = rb_str_new((const char *)bstr->data, len); + ASN1_BIT_STRING_free(bstr); + + return ret; +} + +static VALUE +decode_enum(unsigned char* der, long length) +{ + ASN1_ENUMERATED *ai; + const unsigned char *p; + VALUE ret; + int status = 0; + + p = der; + if(!(ai = d2i_ASN1_ENUMERATED(NULL, &p, length))) + ossl_raise(eASN1Error, NULL); + ret = rb_protect((VALUE(*)_((VALUE)))asn1integer_to_num, + (VALUE)ai, &status); + ASN1_ENUMERATED_free(ai); + if(status) rb_jump_tag(status); + + return ret; +} + +static VALUE +decode_null(unsigned char* der, long length) +{ + ASN1_NULL *null; + const unsigned char *p; + + p = der; + if(!(null = d2i_ASN1_NULL(NULL, &p, length))) + ossl_raise(eASN1Error, NULL); + ASN1_NULL_free(null); + + return Qnil; +} + +static VALUE +decode_obj(unsigned char* der, long length) +{ + ASN1_OBJECT *obj; + const unsigned char *p; + VALUE ret; + int nid; + BIO *bio; + + p = der; + if(!(obj = d2i_ASN1_OBJECT(NULL, &p, length))) + ossl_raise(eASN1Error, NULL); + if((nid = OBJ_obj2nid(obj)) != NID_undef){ + ASN1_OBJECT_free(obj); + ret = rb_str_new2(OBJ_nid2sn(nid)); + } + else{ + if(!(bio = BIO_new(BIO_s_mem()))){ + ASN1_OBJECT_free(obj); + ossl_raise(eASN1Error, NULL); + } + i2a_ASN1_OBJECT(bio, obj); + ASN1_OBJECT_free(obj); + ret = ossl_membio2str(bio); + } + + return ret; +} + +static VALUE +decode_time(unsigned char* der, long length) +{ + ASN1_TIME *time; + const unsigned char *p; + VALUE ret; + int status = 0; + + p = der; + if(!(time = d2i_ASN1_TIME(NULL, &p, length))) + ossl_raise(eASN1Error, NULL); + ret = rb_protect((VALUE(*)_((VALUE)))asn1time_to_time, + (VALUE)time, &status); + ASN1_TIME_free(time); + if(status) rb_jump_tag(status); + + return ret; +} + +static VALUE +decode_eoc(unsigned char *der, long length) +{ + if (length != 2 || !(der[0] == 0x00 && der[1] == 0x00)) + ossl_raise(eASN1Error, NULL); + + return rb_str_new("", 0); +} + +/********/ + +typedef struct { + const char *name; + VALUE *klass; +} ossl_asn1_info_t; + +static const ossl_asn1_info_t ossl_asn1_info[] = { + { "EOC", &cASN1EndOfContent, }, /* 0 */ + { "BOOLEAN", &cASN1Boolean, }, /* 1 */ + { "INTEGER", &cASN1Integer, }, /* 2 */ + { "BIT_STRING", &cASN1BitString, }, /* 3 */ + { "OCTET_STRING", &cASN1OctetString, }, /* 4 */ + { "NULL", &cASN1Null, }, /* 5 */ + { "OBJECT", &cASN1ObjectId, }, /* 6 */ + { "OBJECT_DESCRIPTOR", NULL, }, /* 7 */ + { "EXTERNAL", NULL, }, /* 8 */ + { "REAL", NULL, }, /* 9 */ + { "ENUMERATED", &cASN1Enumerated, }, /* 10 */ + { "EMBEDDED_PDV", NULL, }, /* 11 */ + { "UTF8STRING", &cASN1UTF8String, }, /* 12 */ + { "RELATIVE_OID", NULL, }, /* 13 */ + { "[UNIVERSAL 14]", NULL, }, /* 14 */ + { "[UNIVERSAL 15]", NULL, }, /* 15 */ + { "SEQUENCE", &cASN1Sequence, }, /* 16 */ + { "SET", &cASN1Set, }, /* 17 */ + { "NUMERICSTRING", &cASN1NumericString, }, /* 18 */ + { "PRINTABLESTRING", &cASN1PrintableString, }, /* 19 */ + { "T61STRING", &cASN1T61String, }, /* 20 */ + { "VIDEOTEXSTRING", &cASN1VideotexString, }, /* 21 */ + { "IA5STRING", &cASN1IA5String, }, /* 22 */ + { "UTCTIME", &cASN1UTCTime, }, /* 23 */ + { "GENERALIZEDTIME", &cASN1GeneralizedTime, }, /* 24 */ + { "GRAPHICSTRING", &cASN1GraphicString, }, /* 25 */ + { "ISO64STRING", &cASN1ISO64String, }, /* 26 */ + { "GENERALSTRING", &cASN1GeneralString, }, /* 27 */ + { "UNIVERSALSTRING", &cASN1UniversalString, }, /* 28 */ + { "CHARACTER_STRING", NULL, }, /* 29 */ + { "BMPSTRING", &cASN1BMPString, }, /* 30 */ +}; + +enum {ossl_asn1_info_size = (sizeof(ossl_asn1_info)/sizeof(ossl_asn1_info[0]))}; + +static VALUE class_tag_map; + +static int ossl_asn1_default_tag(VALUE obj); + +ASN1_TYPE* +ossl_asn1_get_asn1type(VALUE obj) +{ + ASN1_TYPE *ret; + VALUE value, rflag; + void *ptr; + void (*free_func)(); + int tag, flag; + + tag = ossl_asn1_default_tag(obj); + value = ossl_asn1_get_value(obj); + switch(tag){ + case V_ASN1_BOOLEAN: + ptr = (void*)(VALUE)obj_to_asn1bool(value); + free_func = NULL; + break; + case V_ASN1_INTEGER: /* FALLTHROUGH */ + case V_ASN1_ENUMERATED: + ptr = obj_to_asn1int(value); + free_func = ASN1_INTEGER_free; + break; + case V_ASN1_BIT_STRING: + rflag = rb_attr_get(obj, sivUNUSED_BITS); + flag = NIL_P(rflag) ? -1 : NUM2INT(rflag); + ptr = obj_to_asn1bstr(value, flag); + free_func = ASN1_BIT_STRING_free; + break; + case V_ASN1_NULL: + ptr = obj_to_asn1null(value); + free_func = ASN1_NULL_free; + break; + case V_ASN1_OCTET_STRING: /* FALLTHROUGH */ + case V_ASN1_UTF8STRING: /* FALLTHROUGH */ + case V_ASN1_NUMERICSTRING: /* FALLTHROUGH */ + case V_ASN1_PRINTABLESTRING: /* FALLTHROUGH */ + case V_ASN1_T61STRING: /* FALLTHROUGH */ + case V_ASN1_VIDEOTEXSTRING: /* FALLTHROUGH */ + case V_ASN1_IA5STRING: /* FALLTHROUGH */ + case V_ASN1_GRAPHICSTRING: /* FALLTHROUGH */ + case V_ASN1_ISO64STRING: /* FALLTHROUGH */ + case V_ASN1_GENERALSTRING: /* FALLTHROUGH */ + case V_ASN1_UNIVERSALSTRING: /* FALLTHROUGH */ + case V_ASN1_BMPSTRING: + ptr = obj_to_asn1str(value); + free_func = ASN1_STRING_free; + break; + case V_ASN1_OBJECT: + ptr = obj_to_asn1obj(value); + free_func = ASN1_OBJECT_free; + break; + case V_ASN1_UTCTIME: + ptr = obj_to_asn1utime(value); + free_func = ASN1_TIME_free; + break; + case V_ASN1_GENERALIZEDTIME: + ptr = obj_to_asn1gtime(value); + free_func = ASN1_TIME_free; + break; + case V_ASN1_SET: /* FALLTHROUGH */ + case V_ASN1_SEQUENCE: + ptr = obj_to_asn1derstr(obj); + free_func = ASN1_STRING_free; + break; + default: + ossl_raise(eASN1Error, "unsupported ASN.1 type"); + } + if(!(ret = OPENSSL_malloc(sizeof(ASN1_TYPE)))){ + if(free_func) free_func(ptr); + ossl_raise(eASN1Error, "ASN1_TYPE alloc failure"); + } + memset(ret, 0, sizeof(ASN1_TYPE)); + ASN1_TYPE_set(ret, tag, ptr); + + return ret; +} + +static int +ossl_asn1_default_tag(VALUE obj) +{ + VALUE tmp_class, tag; + + tmp_class = CLASS_OF(obj); + while (tmp_class) { + tag = rb_hash_lookup(class_tag_map, tmp_class); + if (tag != Qnil) { + return NUM2INT(tag); + } + tmp_class = rb_class_superclass(tmp_class); + } + ossl_raise(eASN1Error, "universal tag for %"PRIsVALUE" not found", + rb_obj_class(obj)); + + return -1; /* dummy */ +} + +static int +ossl_asn1_tag(VALUE obj) +{ + VALUE tag; + + tag = ossl_asn1_get_tag(obj); + if(NIL_P(tag)) + ossl_raise(eASN1Error, "tag number not specified"); + + return NUM2INT(tag); +} + +static int +ossl_asn1_is_explicit(VALUE obj) +{ + VALUE s; + int ret = -1; + + s = ossl_asn1_get_tagging(obj); + if(NIL_P(s)) return 0; + else if(SYMBOL_P(s)){ + if (SYM2ID(s) == sIMPLICIT) + ret = 0; + else if (SYM2ID(s) == sEXPLICIT) + ret = 1; + } + if(ret < 0){ + ossl_raise(eASN1Error, "invalid tag default"); + } + + return ret; +} + +static int +ossl_asn1_tag_class(VALUE obj) +{ + VALUE s; + int ret = -1; + + s = ossl_asn1_get_tag_class(obj); + if(NIL_P(s)) ret = V_ASN1_UNIVERSAL; + else if(SYMBOL_P(s)){ + if (SYM2ID(s) == sUNIVERSAL) + ret = V_ASN1_UNIVERSAL; + else if (SYM2ID(s) == sAPPLICATION) + ret = V_ASN1_APPLICATION; + else if (SYM2ID(s) == sCONTEXT_SPECIFIC) + ret = V_ASN1_CONTEXT_SPECIFIC; + else if (SYM2ID(s) == sPRIVATE) + ret = V_ASN1_PRIVATE; + } + if(ret < 0){ + ossl_raise(eASN1Error, "invalid tag class"); + } + + return ret; +} + +static VALUE +ossl_asn1_class2sym(int tc) +{ + if((tc & V_ASN1_PRIVATE) == V_ASN1_PRIVATE) + return ID2SYM(sPRIVATE); + else if((tc & V_ASN1_CONTEXT_SPECIFIC) == V_ASN1_CONTEXT_SPECIFIC) + return ID2SYM(sCONTEXT_SPECIFIC); + else if((tc & V_ASN1_APPLICATION) == V_ASN1_APPLICATION) + return ID2SYM(sAPPLICATION); + else + return ID2SYM(sUNIVERSAL); +} + +/* + * call-seq: + * OpenSSL::ASN1::ASN1Data.new(value, tag, tag_class) => ASN1Data + * + * +value+: Please have a look at Constructive and Primitive to see how Ruby + * types are mapped to ASN.1 types and vice versa. + * + * +tag+: A +Number+ indicating the tag number. + * + * +tag_class+: A +Symbol+ indicating the tag class. Please cf. ASN1 for + * possible values. + * + * == Example + * asn1_int = OpenSSL::ASN1Data.new(42, 2, :UNIVERSAL) # => Same as OpenSSL::ASN1::Integer.new(42) + * tagged_int = OpenSSL::ASN1Data.new(42, 0, :CONTEXT_SPECIFIC) # implicitly 0-tagged INTEGER + */ +static VALUE +ossl_asn1data_initialize(VALUE self, VALUE value, VALUE tag, VALUE tag_class) +{ + if(!SYMBOL_P(tag_class)) + ossl_raise(eASN1Error, "invalid tag class"); + if((SYM2ID(tag_class) == sUNIVERSAL) && NUM2INT(tag) > 31) + ossl_raise(eASN1Error, "tag number for Universal too large"); + ossl_asn1_set_tag(self, tag); + ossl_asn1_set_value(self, value); + ossl_asn1_set_tag_class(self, tag_class); + ossl_asn1_set_infinite_length(self, Qfalse); + + return self; +} + +static VALUE +join_der_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, str)) +{ + i = ossl_to_der_if_possible(i); + StringValue(i); + rb_str_append(str, i); + return Qnil; +} + +static VALUE +join_der(VALUE enumerable) +{ + VALUE str = rb_str_new(0, 0); + rb_block_call(enumerable, rb_intern("each"), 0, 0, join_der_i, str); + return str; +} + +/* + * call-seq: + * asn1.to_der => DER-encoded String + * + * Encodes this ASN1Data into a DER-encoded String value. The result is + * DER-encoded except for the possibility of infinite length encodings. + * Infinite length encodings are not allowed in strict DER, so strictly + * speaking the result of such an encoding would be a BER-encoding. + */ +static VALUE +ossl_asn1data_to_der(VALUE self) +{ + VALUE value, der, inf_length; + int tag, tag_class, is_cons = 0; + long length; + unsigned char *p; + + value = ossl_asn1_get_value(self); + if(rb_obj_is_kind_of(value, rb_cArray)){ + is_cons = 1; + value = join_der(value); + } + StringValue(value); + + tag = ossl_asn1_tag(self); + tag_class = ossl_asn1_tag_class(self); + inf_length = ossl_asn1_get_infinite_length(self); + if (inf_length == Qtrue) { + is_cons = 2; + } + if((length = ossl_asn1_object_size(is_cons, RSTRING_LENINT(value), tag)) <= 0) + ossl_raise(eASN1Error, NULL); + der = rb_str_new(0, length); + p = (unsigned char *)RSTRING_PTR(der); + ossl_asn1_put_object(&p, is_cons, RSTRING_LENINT(value), tag, tag_class); + memcpy(p, RSTRING_PTR(value), RSTRING_LEN(value)); + p += RSTRING_LEN(value); + ossl_str_adjust(der, p); + + return der; +} + +static VALUE +int_ossl_asn1_decode0_prim(unsigned char **pp, long length, long hlen, int tag, + VALUE tc, long *num_read) +{ + VALUE value, asn1data; + unsigned char *p; + long flag = 0; + + p = *pp; + + if(tc == sUNIVERSAL && tag < ossl_asn1_info_size) { + switch(tag){ + case V_ASN1_EOC: + value = decode_eoc(p, hlen+length); + break; + case V_ASN1_BOOLEAN: + value = decode_bool(p, hlen+length); + break; + case V_ASN1_INTEGER: + value = decode_int(p, hlen+length); + break; + case V_ASN1_BIT_STRING: + value = decode_bstr(p, hlen+length, &flag); + break; + case V_ASN1_NULL: + value = decode_null(p, hlen+length); + break; + case V_ASN1_ENUMERATED: + value = decode_enum(p, hlen+length); + break; + case V_ASN1_OBJECT: + value = decode_obj(p, hlen+length); + break; + case V_ASN1_UTCTIME: /* FALLTHROUGH */ + case V_ASN1_GENERALIZEDTIME: + value = decode_time(p, hlen+length); + break; + default: + /* use original value */ + p += hlen; + value = rb_str_new((const char *)p, length); + break; + } + } + else { + p += hlen; + value = rb_str_new((const char *)p, length); + } + + *pp += hlen + length; + *num_read = hlen + length; + + if (tc == sUNIVERSAL && tag < ossl_asn1_info_size && ossl_asn1_info[tag].klass) { + VALUE klass = *ossl_asn1_info[tag].klass; + VALUE args[4]; + args[0] = value; + args[1] = INT2NUM(tag); + args[2] = Qnil; + args[3] = ID2SYM(tc); + asn1data = rb_obj_alloc(klass); + ossl_asn1_initialize(4, args, asn1data); + if(tag == V_ASN1_BIT_STRING){ + rb_ivar_set(asn1data, sivUNUSED_BITS, LONG2NUM(flag)); + } + } + else { + asn1data = rb_obj_alloc(cASN1Data); + ossl_asn1data_initialize(asn1data, value, INT2NUM(tag), ID2SYM(tc)); + } + + return asn1data; +} + +static VALUE +int_ossl_asn1_decode0_cons(unsigned char **pp, long max_len, long length, + long *offset, int depth, int yield, int j, + int tag, VALUE tc, long *num_read) +{ + VALUE value, asn1data, ary; + int infinite; + long off = *offset; + + infinite = (j == 0x21); + ary = rb_ary_new(); + + while (length > 0 || infinite) { + long inner_read = 0; + value = ossl_asn1_decode0(pp, max_len, &off, depth + 1, yield, &inner_read); + *num_read += inner_read; + max_len -= inner_read; + rb_ary_push(ary, value); + if (length > 0) + length -= inner_read; + + if (infinite && + NUM2INT(ossl_asn1_get_tag(value)) == V_ASN1_EOC && + SYM2ID(ossl_asn1_get_tag_class(value)) == sUNIVERSAL) { + break; + } + } + + if (tc == sUNIVERSAL) { + VALUE args[4]; + int not_sequence_or_set; + + not_sequence_or_set = tag != V_ASN1_SEQUENCE && tag != V_ASN1_SET; + + if (not_sequence_or_set) { + if (infinite) { + asn1data = rb_obj_alloc(cASN1Constructive); + } + else { + ossl_raise(eASN1Error, "invalid non-infinite tag"); + return Qnil; + } + } + else { + VALUE klass = *ossl_asn1_info[tag].klass; + asn1data = rb_obj_alloc(klass); + } + args[0] = ary; + args[1] = INT2NUM(tag); + args[2] = Qnil; + args[3] = ID2SYM(tc); + ossl_asn1_initialize(4, args, asn1data); + } + else { + asn1data = rb_obj_alloc(cASN1Data); + ossl_asn1data_initialize(asn1data, ary, INT2NUM(tag), ID2SYM(tc)); + } + + if (infinite) + ossl_asn1_set_infinite_length(asn1data, Qtrue); + else + ossl_asn1_set_infinite_length(asn1data, Qfalse); + + *offset = off; + return asn1data; +} + +static VALUE +ossl_asn1_decode0(unsigned char **pp, long length, long *offset, int depth, + int yield, long *num_read) +{ + unsigned char *start, *p; + const unsigned char *p0; + long len = 0, inner_read = 0, off = *offset, hlen; + int tag, tc, j; + VALUE asn1data, tag_class; + + p = *pp; + start = p; + p0 = p; + j = ASN1_get_object(&p0, &len, &tag, &tc, length); + p = (unsigned char *)p0; + if(j & 0x80) ossl_raise(eASN1Error, NULL); + if(len > length) ossl_raise(eASN1Error, "value is too short"); + if((tc & V_ASN1_PRIVATE) == V_ASN1_PRIVATE) + tag_class = sPRIVATE; + else if((tc & V_ASN1_CONTEXT_SPECIFIC) == V_ASN1_CONTEXT_SPECIFIC) + tag_class = sCONTEXT_SPECIFIC; + else if((tc & V_ASN1_APPLICATION) == V_ASN1_APPLICATION) + tag_class = sAPPLICATION; + else + tag_class = sUNIVERSAL; + + hlen = p - start; + + if(yield) { + VALUE arg = rb_ary_new(); + rb_ary_push(arg, LONG2NUM(depth)); + rb_ary_push(arg, LONG2NUM(*offset)); + rb_ary_push(arg, LONG2NUM(hlen)); + rb_ary_push(arg, LONG2NUM(len)); + rb_ary_push(arg, (j & V_ASN1_CONSTRUCTED) ? Qtrue : Qfalse); + rb_ary_push(arg, ossl_asn1_class2sym(tc)); + rb_ary_push(arg, INT2NUM(tag)); + rb_yield(arg); + } + + if(j & V_ASN1_CONSTRUCTED) { + *pp += hlen; + off += hlen; + asn1data = int_ossl_asn1_decode0_cons(pp, length, len, &off, depth, yield, j, tag, tag_class, &inner_read); + inner_read += hlen; + } + else { + if ((j & 0x01) && (len == 0)) ossl_raise(eASN1Error, "Infinite length for primitive value"); + asn1data = int_ossl_asn1_decode0_prim(pp, len, hlen, tag, tag_class, &inner_read); + off += hlen + len; + } + if (num_read) + *num_read = inner_read; + if (len != 0 && inner_read != hlen + len) { + ossl_raise(eASN1Error, + "Type mismatch. Bytes read: %ld Bytes available: %ld", + inner_read, hlen + len); + } + + *offset = off; + return asn1data; +} + +static void +int_ossl_decode_sanity_check(long len, long read, long offset) +{ + if (len != 0 && (read != len || offset != len)) { + ossl_raise(eASN1Error, + "Type mismatch. Total bytes read: %ld Bytes available: %ld Offset: %ld", + read, len, offset); + } +} + +/* + * call-seq: + * OpenSSL::ASN1.traverse(asn1) -> nil + * + * If a block is given, it prints out each of the elements encountered. + * Block parameters are (in that order): + * * depth: The recursion depth, plus one with each constructed value being encountered (Number) + * * offset: Current byte offset (Number) + * * header length: Combined length in bytes of the Tag and Length headers. (Number) + * * length: The overall remaining length of the entire data (Number) + * * constructed: Whether this value is constructed or not (Boolean) + * * tag_class: Current tag class (Symbol) + * * tag: The current tag (Number) + * + * == Example + * der = File.binread('asn1data.der') + * OpenSSL::ASN1.traverse(der) do | depth, offset, header_len, length, constructed, tag_class, tag| + * puts "Depth: #{depth} Offset: #{offset} Length: #{length}" + * puts "Header length: #{header_len} Tag: #{tag} Tag class: #{tag_class} Constructed: #{constructed}" + * end + */ +static VALUE +ossl_asn1_traverse(VALUE self, VALUE obj) +{ + unsigned char *p; + volatile VALUE tmp; + long len, read = 0, offset = 0; + + obj = ossl_to_der_if_possible(obj); + tmp = rb_str_new4(StringValue(obj)); + p = (unsigned char *)RSTRING_PTR(tmp); + len = RSTRING_LEN(tmp); + ossl_asn1_decode0(&p, len, &offset, 0, 1, &read); + int_ossl_decode_sanity_check(len, read, offset); + return Qnil; +} + +/* + * call-seq: + * OpenSSL::ASN1.decode(der) -> ASN1Data + * + * Decodes a BER- or DER-encoded value and creates an ASN1Data instance. +der+ + * may be a +String+ or any object that features a +#to_der+ method transforming + * it into a BER-/DER-encoded +String+. + * + * == Example + * der = File.binread('asn1data') + * asn1 = OpenSSL::ASN1.decode(der) + */ +static VALUE +ossl_asn1_decode(VALUE self, VALUE obj) +{ + VALUE ret; + unsigned char *p; + volatile VALUE tmp; + long len, read = 0, offset = 0; + + obj = ossl_to_der_if_possible(obj); + tmp = rb_str_new4(StringValue(obj)); + p = (unsigned char *)RSTRING_PTR(tmp); + len = RSTRING_LEN(tmp); + ret = ossl_asn1_decode0(&p, len, &offset, 0, 0, &read); + int_ossl_decode_sanity_check(len, read, offset); + return ret; +} + +/* + * call-seq: + * OpenSSL::ASN1.decode_all(der) -> Array of ASN1Data + * + * Similar to +decode+ with the difference that +decode+ expects one + * distinct value represented in +der+. +decode_all+ on the contrary + * decodes a sequence of sequential BER/DER values lined up in +der+ + * and returns them as an array. + * + * == Example + * ders = File.binread('asn1data_seq') + * asn1_ary = OpenSSL::ASN1.decode_all(ders) + */ +static VALUE +ossl_asn1_decode_all(VALUE self, VALUE obj) +{ + VALUE ary, val; + unsigned char *p; + long len, tmp_len = 0, read = 0, offset = 0; + volatile VALUE tmp; + + obj = ossl_to_der_if_possible(obj); + tmp = rb_str_new4(StringValue(obj)); + p = (unsigned char *)RSTRING_PTR(tmp); + len = RSTRING_LEN(tmp); + tmp_len = len; + ary = rb_ary_new(); + while (tmp_len > 0) { + long tmp_read = 0; + val = ossl_asn1_decode0(&p, tmp_len, &offset, 0, 0, &tmp_read); + rb_ary_push(ary, val); + read += tmp_read; + tmp_len -= tmp_read; + } + int_ossl_decode_sanity_check(len, read, offset); + return ary; +} + +/* + * call-seq: + * OpenSSL::ASN1::Primitive.new( value [, tag, tagging, tag_class ]) => Primitive + * + * +value+: is mandatory. + * + * +tag+: optional, may be specified for tagged values. If no +tag+ is + * specified, the UNIVERSAL tag corresponding to the Primitive sub-class + * is used by default. + * + * +tagging+: may be used as an encoding hint to encode a value either + * explicitly or implicitly, see ASN1 for possible values. + * + * +tag_class+: if +tag+ and +tagging+ are +nil+ then this is set to + * +:UNIVERSAL+ by default. If either +tag+ or +tagging+ are set then + * +:CONTEXT_SPECIFIC+ is used as the default. For possible values please + * cf. ASN1. + * + * == Example + * int = OpenSSL::ASN1::Integer.new(42) + * zero_tagged_int = OpenSSL::ASN1::Integer.new(42, 0, :IMPLICIT) + * private_explicit_zero_tagged_int = OpenSSL::ASN1::Integer.new(42, 0, :EXPLICIT, :PRIVATE) + */ +static VALUE +ossl_asn1_initialize(int argc, VALUE *argv, VALUE self) +{ + VALUE value, tag, tagging, tag_class; + + rb_scan_args(argc, argv, "13", &value, &tag, &tagging, &tag_class); + if(argc > 1){ + if(NIL_P(tag)) + ossl_raise(eASN1Error, "must specify tag number"); + if(!NIL_P(tagging) && !SYMBOL_P(tagging)) + ossl_raise(eASN1Error, "invalid tagging method"); + if(NIL_P(tag_class)) { + if (NIL_P(tagging)) + tag_class = ID2SYM(sUNIVERSAL); + else + tag_class = ID2SYM(sCONTEXT_SPECIFIC); + } + if(!SYMBOL_P(tag_class)) + ossl_raise(eASN1Error, "invalid tag class"); + if(!NIL_P(tagging) && SYM2ID(tagging) == sIMPLICIT && NUM2INT(tag) > 31) + ossl_raise(eASN1Error, "tag number for Universal too large"); + } + else{ + tag = INT2NUM(ossl_asn1_default_tag(self)); + tagging = Qnil; + tag_class = ID2SYM(sUNIVERSAL); + } + ossl_asn1_set_tag(self, tag); + ossl_asn1_set_value(self, value); + ossl_asn1_set_tagging(self, tagging); + ossl_asn1_set_tag_class(self, tag_class); + ossl_asn1_set_infinite_length(self, Qfalse); + + return self; +} + +static VALUE +ossl_asn1eoc_initialize(VALUE self) { + VALUE tag, tagging, tag_class, value; + tag = INT2NUM(ossl_asn1_default_tag(self)); + tagging = Qnil; + tag_class = ID2SYM(sUNIVERSAL); + value = rb_str_new("", 0); + ossl_asn1_set_tag(self, tag); + ossl_asn1_set_value(self, value); + ossl_asn1_set_tagging(self, tagging); + ossl_asn1_set_tag_class(self, tag_class); + ossl_asn1_set_infinite_length(self, Qfalse); + return self; +} + +static int +ossl_i2d_ASN1_TYPE(ASN1_TYPE *a, unsigned char **pp) +{ +#if OPENSSL_VERSION_NUMBER < 0x00907000L + if(!a) return 0; + if(a->type == V_ASN1_BOOLEAN) + return i2d_ASN1_BOOLEAN(a->value.boolean, pp); +#endif + return i2d_ASN1_TYPE(a, pp); +} + +static void +ossl_ASN1_TYPE_free(ASN1_TYPE *a) +{ +#if OPENSSL_VERSION_NUMBER < 0x00907000L + if(!a) return; + if(a->type == V_ASN1_BOOLEAN){ + OPENSSL_free(a); + return; + } +#endif + ASN1_TYPE_free(a); +} + +/* + * call-seq: + * asn1.to_der => DER-encoded String + * + * See ASN1Data#to_der for details. * + */ +static VALUE +ossl_asn1prim_to_der(VALUE self) +{ + ASN1_TYPE *asn1; + int tn, tc, explicit; + long len, reallen; + unsigned char *buf, *p; + VALUE str; + + tn = NUM2INT(ossl_asn1_get_tag(self)); + tc = ossl_asn1_tag_class(self); + explicit = ossl_asn1_is_explicit(self); + asn1 = ossl_asn1_get_asn1type(self); + + len = ossl_asn1_object_size(1, ossl_i2d_ASN1_TYPE(asn1, NULL), tn); + if(!(buf = OPENSSL_malloc(len))){ + ossl_ASN1_TYPE_free(asn1); + ossl_raise(eASN1Error, "cannot alloc buffer"); + } + p = buf; + if (tc == V_ASN1_UNIVERSAL) { + ossl_i2d_ASN1_TYPE(asn1, &p); + } else if (explicit) { + ossl_asn1_put_object(&p, 1, ossl_i2d_ASN1_TYPE(asn1, NULL), tn, tc); + ossl_i2d_ASN1_TYPE(asn1, &p); + } else { + ossl_i2d_ASN1_TYPE(asn1, &p); + *buf = tc | tn | (*buf & V_ASN1_CONSTRUCTED); + } + ossl_ASN1_TYPE_free(asn1); + reallen = p - buf; + assert(reallen <= len); + str = ossl_buf2str((char *)buf, rb_long2int(reallen)); /* buf will be free in ossl_buf2str */ + + return str; +} + +/* + * call-seq: + * asn1.to_der => DER-encoded String + * + * See ASN1Data#to_der for details. + */ +static VALUE +ossl_asn1cons_to_der(VALUE self) +{ + int tag, tn, tc, explicit, constructed = 1; + int found_prim = 0, seq_len; + long length; + unsigned char *p; + VALUE value, str, inf_length; + + tn = NUM2INT(ossl_asn1_get_tag(self)); + tc = ossl_asn1_tag_class(self); + inf_length = ossl_asn1_get_infinite_length(self); + if (inf_length == Qtrue) { + VALUE ary, example; + constructed = 2; + if (CLASS_OF(self) == cASN1Sequence || + CLASS_OF(self) == cASN1Set) { + tag = ossl_asn1_default_tag(self); + } + else { /* must be a constructive encoding of a primitive value */ + ary = ossl_asn1_get_value(self); + if (!rb_obj_is_kind_of(ary, rb_cArray)) + ossl_raise(eASN1Error, "Constructive value must be an Array"); + /* Recursively descend until a primitive value is found. + The overall value of the entire constructed encoding + is of the type of the first primitive encoding to be + found. */ + while (!found_prim){ + example = rb_ary_entry(ary, 0); + if (rb_obj_is_kind_of(example, cASN1Primitive)){ + found_prim = 1; + } + else { + /* example is another ASN1Constructive */ + if (!rb_obj_is_kind_of(example, cASN1Constructive)){ + ossl_raise(eASN1Error, "invalid constructed encoding"); + return Qnil; /* dummy */ + } + ary = ossl_asn1_get_value(example); + } + } + tag = ossl_asn1_default_tag(example); + } + } + else { + if (CLASS_OF(self) == cASN1Constructive) + ossl_raise(eASN1Error, "Constructive shall only be used with infinite length"); + tag = ossl_asn1_default_tag(self); + } + explicit = ossl_asn1_is_explicit(self); + value = join_der(ossl_asn1_get_value(self)); + + seq_len = ossl_asn1_object_size(constructed, RSTRING_LENINT(value), tag); + length = ossl_asn1_object_size(constructed, seq_len, tn); + str = rb_str_new(0, length); + p = (unsigned char *)RSTRING_PTR(str); + if(tc == V_ASN1_UNIVERSAL) + ossl_asn1_put_object(&p, constructed, RSTRING_LENINT(value), tn, tc); + else{ + if(explicit){ + ossl_asn1_put_object(&p, constructed, seq_len, tn, tc); + ossl_asn1_put_object(&p, constructed, RSTRING_LENINT(value), tag, V_ASN1_UNIVERSAL); + } + else{ + ossl_asn1_put_object(&p, constructed, RSTRING_LENINT(value), tn, tc); + } + } + memcpy(p, RSTRING_PTR(value), RSTRING_LEN(value)); + p += RSTRING_LEN(value); + + /* In this case we need an additional EOC (one for the explicit part and + * one for the Constructive itself. The EOC for the Constructive is + * supplied by the user, but that for the "explicit wrapper" must be + * added here. + */ + if (explicit && inf_length == Qtrue) { + ASN1_put_eoc(&p); + } + ossl_str_adjust(str, p); + + return str; +} + +/* + * call-seq: + * asn1_ary.each { |asn1| block } => asn1_ary + * + * Calls <i>block</i> once for each element in +self+, passing that element + * as parameter +asn1+. If no block is given, an enumerator is returned + * instead. + * + * == Example + * asn1_ary.each do |asn1| + * puts asn1 + * end + */ +static VALUE +ossl_asn1cons_each(VALUE self) +{ + rb_ary_each(ossl_asn1_get_value(self)); + return self; +} + +/* + * call-seq: + * ObjectId.register(object_id, short_name, long_name) + * + * This adds a new ObjectId to the internal tables. Where +object_id+ is the + * numerical form, +short_name+ is the short name, and +long_name+ is the long + * name. + * + * Returns +true+ if successful. Raises an ASN1Error otherwise. + * + */ +static VALUE +ossl_asn1obj_s_register(VALUE self, VALUE oid, VALUE sn, VALUE ln) +{ + StringValue(oid); + StringValue(sn); + StringValue(ln); + + if(!OBJ_create(RSTRING_PTR(oid), RSTRING_PTR(sn), RSTRING_PTR(ln))) + ossl_raise(eASN1Error, NULL); + + return Qtrue; +} + +/* Document-method: OpenSSL::ASN1::ObjectId#sn + * + * The short name of the ObjectId, as defined in +openssl/objects.h+. + */ +/* Document-method: OpenSSL::ASN1::ObjectId#short_name + * + * #short_name is an alias to #sn + */ +static VALUE +ossl_asn1obj_get_sn(VALUE self) +{ + VALUE val, ret = Qnil; + int nid; + + val = ossl_asn1_get_value(self); + if ((nid = OBJ_txt2nid(StringValuePtr(val))) != NID_undef) + ret = rb_str_new2(OBJ_nid2sn(nid)); + + return ret; +} + +/* Document-method: OpenSSL::ASN1::ObjectId#ln + * + * The long name of the ObjectId, as defined in +openssl/objects.h+. + */ +/* Document-method: OpenSSL::ASN1::ObjectId.long_name + * + * #long_name is an alias to #ln + */ +static VALUE +ossl_asn1obj_get_ln(VALUE self) +{ + VALUE val, ret = Qnil; + int nid; + + val = ossl_asn1_get_value(self); + if ((nid = OBJ_txt2nid(StringValuePtr(val))) != NID_undef) + ret = rb_str_new2(OBJ_nid2ln(nid)); + + return ret; +} + +/* Document-method: OpenSSL::ASN1::ObjectId#oid + * + * The object identifier as a String. + */ +static VALUE +ossl_asn1obj_get_oid(VALUE self) +{ + VALUE val; + ASN1_OBJECT *a1obj; + char buf[128]; + + val = ossl_asn1_get_value(self); + a1obj = obj_to_asn1obj(val); + OBJ_obj2txt(buf, sizeof(buf), a1obj, 1); + ASN1_OBJECT_free(a1obj); + + return rb_str_new2(buf); +} + +#define OSSL_ASN1_IMPL_FACTORY_METHOD(klass) \ +static VALUE ossl_asn1_##klass(int argc, VALUE *argv, VALUE self)\ +{ return rb_funcall3(cASN1##klass, rb_intern("new"), argc, argv); } + +OSSL_ASN1_IMPL_FACTORY_METHOD(Boolean) +OSSL_ASN1_IMPL_FACTORY_METHOD(Integer) +OSSL_ASN1_IMPL_FACTORY_METHOD(Enumerated) +OSSL_ASN1_IMPL_FACTORY_METHOD(BitString) +OSSL_ASN1_IMPL_FACTORY_METHOD(OctetString) +OSSL_ASN1_IMPL_FACTORY_METHOD(UTF8String) +OSSL_ASN1_IMPL_FACTORY_METHOD(NumericString) +OSSL_ASN1_IMPL_FACTORY_METHOD(PrintableString) +OSSL_ASN1_IMPL_FACTORY_METHOD(T61String) +OSSL_ASN1_IMPL_FACTORY_METHOD(VideotexString) +OSSL_ASN1_IMPL_FACTORY_METHOD(IA5String) +OSSL_ASN1_IMPL_FACTORY_METHOD(GraphicString) +OSSL_ASN1_IMPL_FACTORY_METHOD(ISO64String) +OSSL_ASN1_IMPL_FACTORY_METHOD(GeneralString) +OSSL_ASN1_IMPL_FACTORY_METHOD(UniversalString) +OSSL_ASN1_IMPL_FACTORY_METHOD(BMPString) +OSSL_ASN1_IMPL_FACTORY_METHOD(Null) +OSSL_ASN1_IMPL_FACTORY_METHOD(ObjectId) +OSSL_ASN1_IMPL_FACTORY_METHOD(UTCTime) +OSSL_ASN1_IMPL_FACTORY_METHOD(GeneralizedTime) +OSSL_ASN1_IMPL_FACTORY_METHOD(Sequence) +OSSL_ASN1_IMPL_FACTORY_METHOD(Set) +OSSL_ASN1_IMPL_FACTORY_METHOD(EndOfContent) + +void +Init_ossl_asn1(void) +{ + VALUE ary; + int i; + +#if 0 + mOSSL = rb_define_module("OpenSSL"); /* let rdoc know about mOSSL */ +#endif + + sUNIVERSAL = rb_intern("UNIVERSAL"); + sCONTEXT_SPECIFIC = rb_intern("CONTEXT_SPECIFIC"); + sAPPLICATION = rb_intern("APPLICATION"); + sPRIVATE = rb_intern("PRIVATE"); + sEXPLICIT = rb_intern("EXPLICIT"); + sIMPLICIT = rb_intern("IMPLICIT"); + + sivVALUE = rb_intern("@value"); + sivTAG = rb_intern("@tag"); + sivTAGGING = rb_intern("@tagging"); + sivTAG_CLASS = rb_intern("@tag_class"); + sivINFINITE_LENGTH = rb_intern("@infinite_length"); + sivUNUSED_BITS = rb_intern("@unused_bits"); + + /* + * Document-module: OpenSSL::ASN1 + * + * Abstract Syntax Notation One (or ASN.1) is a notation syntax to + * describe data structures and is defined in ITU-T X.680. ASN.1 itself + * does not mandate any encoding or parsing rules, but usually ASN.1 data + * structures are encoded using the Distinguished Encoding Rules (DER) or + * less often the Basic Encoding Rules (BER) described in ITU-T X.690. DER + * and BER encodings are binary Tag-Length-Value (TLV) encodings that are + * quite concise compared to other popular data description formats such + * as XML, JSON etc. + * ASN.1 data structures are very common in cryptographic applications, + * e.g. X.509 public key certificates or certificate revocation lists + * (CRLs) are all defined in ASN.1 and DER-encoded. ASN.1, DER and BER are + * the building blocks of applied cryptography. + * The ASN1 module provides the necessary classes that allow generation + * of ASN.1 data structures and the methods to encode them using a DER + * encoding. The decode method allows parsing arbitrary BER-/DER-encoded + * data to a Ruby object that can then be modified and re-encoded at will. + * + * == ASN.1 class hierarchy + * + * The base class representing ASN.1 structures is ASN1Data. ASN1Data offers + * attributes to read and set the +tag+, the +tag_class+ and finally the + * +value+ of a particular ASN.1 item. Upon parsing, any tagged values + * (implicit or explicit) will be represented by ASN1Data instances because + * their "real type" can only be determined using out-of-band information + * from the ASN.1 type declaration. Since this information is normally + * known when encoding a type, all sub-classes of ASN1Data offer an + * additional attribute +tagging+ that allows to encode a value implicitly + * (+:IMPLICIT+) or explicitly (+:EXPLICIT+). + * + * === Constructive + * + * Constructive is, as its name implies, the base class for all + * constructed encodings, i.e. those that consist of several values, + * opposed to "primitive" encodings with just one single value. + * Primitive values that are encoded with "infinite length" are typically + * constructed (their values come in multiple chunks) and are therefore + * represented by instances of Constructive. The value of an Constructive + * is always an Array. + * + * ==== ASN1::Set and ASN1::Sequence + * + * The most common constructive encodings are SETs and SEQUENCEs, which is + * why there are two sub-classes of Constructive representing each of + * them. + * + * === Primitive + * + * This is the super class of all primitive values. Primitive + * itself is not used when parsing ASN.1 data, all values are either + * instances of a corresponding sub-class of Primitive or they are + * instances of ASN1Data if the value was tagged implicitly or explicitly. + * Please cf. Primitive documentation for details on sub-classes and + * their respective mappings of ASN.1 data types to Ruby objects. + * + * == Possible values for +tagging+ + * + * When constructing an ASN1Data object the ASN.1 type definition may + * require certain elements to be either implicitly or explicitly tagged. + * This can be achieved by setting the +tagging+ attribute manually for + * sub-classes of ASN1Data. Use the symbol +:IMPLICIT+ for implicit + * tagging and +:EXPLICIT+ if the element requires explicit tagging. + * + * == Possible values for +tag_class+ + * + * It is possible to create arbitrary ASN1Data objects that also support + * a PRIVATE or APPLICATION tag class. Possible values for the +tag_class+ + * attribute are: + * * +:UNIVERSAL+ (the default for untagged values) + * * +:CONTEXT_SPECIFIC+ (the default for tagged values) + * * +:APPLICATION+ + * * +:PRIVATE+ + * + * == Tag constants + * + * There is a constant defined for each universal tag: + * * OpenSSL::ASN1::EOC (0) + * * OpenSSL::ASN1::BOOLEAN (1) + * * OpenSSL::ASN1::INTEGER (2) + * * OpenSSL::ASN1::BIT_STRING (3) + * * OpenSSL::ASN1::OCTET_STRING (4) + * * OpenSSL::ASN1::NULL (5) + * * OpenSSL::ASN1::OBJECT (6) + * * OpenSSL::ASN1::ENUMERATED (10) + * * OpenSSL::ASN1::UTF8STRING (12) + * * OpenSSL::ASN1::SEQUENCE (16) + * * OpenSSL::ASN1::SET (17) + * * OpenSSL::ASN1::NUMERICSTRING (18) + * * OpenSSL::ASN1::PRINTABLESTRING (19) + * * OpenSSL::ASN1::T61STRING (20) + * * OpenSSL::ASN1::VIDEOTEXSTRING (21) + * * OpenSSL::ASN1::IA5STRING (22) + * * OpenSSL::ASN1::UTCTIME (23) + * * OpenSSL::ASN1::GENERALIZEDTIME (24) + * * OpenSSL::ASN1::GRAPHICSTRING (25) + * * OpenSSL::ASN1::ISO64STRING (26) + * * OpenSSL::ASN1::GENERALSTRING (27) + * * OpenSSL::ASN1::UNIVERSALSTRING (28) + * * OpenSSL::ASN1::BMPSTRING (30) + * + * == UNIVERSAL_TAG_NAME constant + * + * An Array that stores the name of a given tag number. These names are + * the same as the name of the tag constant that is additionally defined, + * e.g. UNIVERSAL_TAG_NAME[2] = "INTEGER" and OpenSSL::ASN1::INTEGER = 2. + * + * == Example usage + * + * === Decoding and viewing a DER-encoded file + * require 'openssl' + * require 'pp' + * der = File.binread('data.der') + * asn1 = OpenSSL::ASN1.decode(der) + * pp der + * + * === Creating an ASN.1 structure and DER-encoding it + * require 'openssl' + * version = OpenSSL::ASN1::Integer.new(1) + * # Explicitly 0-tagged implies context-specific tag class + * serial = OpenSSL::ASN1::Integer.new(12345, 0, :EXPLICIT, :CONTEXT_SPECIFIC) + * name = OpenSSL::ASN1::PrintableString.new('Data 1') + * sequence = OpenSSL::ASN1::Sequence.new( [ version, serial, name ] ) + * der = sequence.to_der + */ + mASN1 = rb_define_module_under(mOSSL, "ASN1"); + + /* Document-class: OpenSSL::ASN1::ASN1Error + * + * Generic error class for all errors raised in ASN1 and any of the + * classes defined in it. + */ + eASN1Error = rb_define_class_under(mASN1, "ASN1Error", eOSSLError); + rb_define_module_function(mASN1, "traverse", ossl_asn1_traverse, 1); + rb_define_module_function(mASN1, "decode", ossl_asn1_decode, 1); + rb_define_module_function(mASN1, "decode_all", ossl_asn1_decode_all, 1); + ary = rb_ary_new(); + + /* + * Array storing tag names at the tag's index. + */ + rb_define_const(mASN1, "UNIVERSAL_TAG_NAME", ary); + for(i = 0; i < ossl_asn1_info_size; i++){ + if(ossl_asn1_info[i].name[0] == '[') continue; + rb_define_const(mASN1, ossl_asn1_info[i].name, INT2NUM(i)); + rb_ary_store(ary, i, rb_str_new2(ossl_asn1_info[i].name)); + } + + /* Document-class: OpenSSL::ASN1::ASN1Data + * + * The top-level class representing any ASN.1 object. When parsed by + * ASN1.decode, tagged values are always represented by an instance + * of ASN1Data. + * + * == The role of ASN1Data for parsing tagged values + * + * When encoding an ASN.1 type it is inherently clear what original + * type (e.g. INTEGER, OCTET STRING etc.) this value has, regardless + * of its tagging. + * But opposed to the time an ASN.1 type is to be encoded, when parsing + * them it is not possible to deduce the "real type" of tagged + * values. This is why tagged values are generally parsed into ASN1Data + * instances, but with a different outcome for implicit and explicit + * tagging. + * + * === Example of a parsed implicitly tagged value + * + * An implicitly 1-tagged INTEGER value will be parsed as an + * ASN1Data with + * * +tag+ equal to 1 + * * +tag_class+ equal to +:CONTEXT_SPECIFIC+ + * * +value+ equal to a +String+ that carries the raw encoding + * of the INTEGER. + * This implies that a subsequent decoding step is required to + * completely decode implicitly tagged values. + * + * === Example of a parsed explicitly tagged value + * + * An explicitly 1-tagged INTEGER value will be parsed as an + * ASN1Data with + * * +tag+ equal to 1 + * * +tag_class+ equal to +:CONTEXT_SPECIFIC+ + * * +value+ equal to an +Array+ with one single element, an + * instance of OpenSSL::ASN1::Integer, i.e. the inner element + * is the non-tagged primitive value, and the tagging is represented + * in the outer ASN1Data + * + * == Example - Decoding an implicitly tagged INTEGER + * int = OpenSSL::ASN1::Integer.new(1, 0, :IMPLICIT) # implicit 0-tagged + * seq = OpenSSL::ASN1::Sequence.new( [int] ) + * der = seq.to_der + * asn1 = OpenSSL::ASN1.decode(der) + * # pp asn1 => #<OpenSSL::ASN1::Sequence:0x87326e0 + * # @infinite_length=false, + * # @tag=16, + * # @tag_class=:UNIVERSAL, + * # @tagging=nil, + * # @value= + * # [#<OpenSSL::ASN1::ASN1Data:0x87326f4 + * # @infinite_length=false, + * # @tag=0, + * # @tag_class=:CONTEXT_SPECIFIC, + * # @value="\x01">]> + * raw_int = asn1.value[0] + * # manually rewrite tag and tag class to make it an UNIVERSAL value + * raw_int.tag = OpenSSL::ASN1::INTEGER + * raw_int.tag_class = :UNIVERSAL + * int2 = OpenSSL::ASN1.decode(raw_int) + * puts int2.value # => 1 + * + * == Example - Decoding an explicitly tagged INTEGER + * int = OpenSSL::ASN1::Integer.new(1, 0, :EXPLICIT) # explicit 0-tagged + * seq = OpenSSL::ASN1::Sequence.new( [int] ) + * der = seq.to_der + * asn1 = OpenSSL::ASN1.decode(der) + * # pp asn1 => #<OpenSSL::ASN1::Sequence:0x87326e0 + * # @infinite_length=false, + * # @tag=16, + * # @tag_class=:UNIVERSAL, + * # @tagging=nil, + * # @value= + * # [#<OpenSSL::ASN1::ASN1Data:0x87326f4 + * # @infinite_length=false, + * # @tag=0, + * # @tag_class=:CONTEXT_SPECIFIC, + * # @value= + * # [#<OpenSSL::ASN1::Integer:0x85bf308 + * # @infinite_length=false, + * # @tag=2, + * # @tag_class=:UNIVERSAL + * # @tagging=nil, + * # @value=1>]>]> + * int2 = asn1.value[0].value[0] + * puts int2.value # => 1 + */ + cASN1Data = rb_define_class_under(mASN1, "ASN1Data", rb_cObject); + /* + * Carries the value of a ASN.1 type. + * Please confer Constructive and Primitive for the mappings between + * ASN.1 data types and Ruby classes. + */ + rb_attr(cASN1Data, rb_intern("value"), 1, 1, 0); + /* + * A +Number+ representing the tag number of this ASN1Data. Never +nil+. + */ + rb_attr(cASN1Data, rb_intern("tag"), 1, 1, 0); + /* + * A +Symbol+ representing the tag class of this ASN1Data. Never +nil+. + * See ASN1Data for possible values. + */ + rb_attr(cASN1Data, rb_intern("tag_class"), 1, 1, 0); + /* + * Never +nil+. A +Boolean+ indicating whether the encoding was infinite + * length (in the case of parsing) or whether an infinite length encoding + * shall be used (in the encoding case). + * In DER, every value has a finite length associated with it. But in + * scenarios where large amounts of data need to be transferred it + * might be desirable to have some kind of streaming support available. + * For example, huge OCTET STRINGs are preferably sent in smaller-sized + * chunks, each at a time. + * This is possible in BER by setting the length bytes of an encoding + * to zero and by this indicating that the following value will be + * sent in chunks. Infinite length encodings are always constructed. + * The end of such a stream of chunks is indicated by sending a EOC + * (End of Content) tag. SETs and SEQUENCEs may use an infinite length + * encoding, but also primitive types such as e.g. OCTET STRINGS or + * BIT STRINGS may leverage this functionality (cf. ITU-T X.690). + */ + rb_attr(cASN1Data, rb_intern("infinite_length"), 1, 1, 0); + rb_define_method(cASN1Data, "initialize", ossl_asn1data_initialize, 3); + rb_define_method(cASN1Data, "to_der", ossl_asn1data_to_der, 0); + + /* Document-class: OpenSSL::ASN1::Primitive + * + * The parent class for all primitive encodings. Attributes are the same as + * for ASN1Data, with the addition of +tagging+. + * Primitive values can never be infinite length encodings, thus it is not + * possible to set the +infinite_length+ attribute for Primitive and its + * sub-classes. + * + * == Primitive sub-classes and their mapping to Ruby classes + * * OpenSSL::ASN1::EndOfContent <=> +value+ is always +nil+ + * * OpenSSL::ASN1::Boolean <=> +value+ is a +Boolean+ + * * OpenSSL::ASN1::Integer <=> +value+ is a +Number+ + * * OpenSSL::ASN1::BitString <=> +value+ is a +String+ + * * OpenSSL::ASN1::OctetString <=> +value+ is a +String+ + * * OpenSSL::ASN1::Null <=> +value+ is always +nil+ + * * OpenSSL::ASN1::Object <=> +value+ is a +String+ + * * OpenSSL::ASN1::Enumerated <=> +value+ is a +Number+ + * * OpenSSL::ASN1::UTF8String <=> +value+ is a +String+ + * * OpenSSL::ASN1::NumericString <=> +value+ is a +String+ + * * OpenSSL::ASN1::PrintableString <=> +value+ is a +String+ + * * OpenSSL::ASN1::T61String <=> +value+ is a +String+ + * * OpenSSL::ASN1::VideotexString <=> +value+ is a +String+ + * * OpenSSL::ASN1::IA5String <=> +value+ is a +String+ + * * OpenSSL::ASN1::UTCTime <=> +value+ is a +Time+ + * * OpenSSL::ASN1::GeneralizedTime <=> +value+ is a +Time+ + * * OpenSSL::ASN1::GraphicString <=> +value+ is a +String+ + * * OpenSSL::ASN1::ISO64String <=> +value+ is a +String+ + * * OpenSSL::ASN1::GeneralString <=> +value+ is a +String+ + * * OpenSSL::ASN1::UniversalString <=> +value+ is a +String+ + * * OpenSSL::ASN1::BMPString <=> +value+ is a +String+ + * + * == OpenSSL::ASN1::BitString + * + * === Additional attributes + * +unused_bits+: if the underlying BIT STRING's + * length is a multiple of 8 then +unused_bits+ is 0. Otherwise + * +unused_bits+ indicates the number of bits that are to be ignored in + * the final octet of the +BitString+'s +value+. + * + * == OpenSSL::ASN1::ObjectId + * + * While OpenSSL::ASN1::ObjectId.new will allocate a new ObjectId, it is + * not typically allocated this way, but rather that are received from + * parsed ASN1 encodings. + * + * === Additional attributes + * * +sn+: the short name as defined in <openssl/objects.h>. + * * +ln+: the long name as defined in <openssl/objects.h>. + * * +oid+: the object identifier as a +String+, e.g. "1.2.3.4.5" + * * +short_name+: alias for +sn+. + * * +long_name+: alias for +ln+. + * + * == Examples + * With the Exception of OpenSSL::ASN1::EndOfContent, each Primitive class + * constructor takes at least one parameter, the +value+. + * + * === Creating EndOfContent + * eoc = OpenSSL::ASN1::EndOfContent.new + * + * === Creating any other Primitive + * prim = <class>.new(value) # <class> being one of the sub-classes except EndOfContent + * prim_zero_tagged_implicit = <class>.new(value, 0, :IMPLICIT) + * prim_zero_tagged_explicit = <class>.new(value, 0, :EXPLICIT) + */ + cASN1Primitive = rb_define_class_under(mASN1, "Primitive", cASN1Data); + /* + * May be used as a hint for encoding a value either implicitly or + * explicitly by setting it either to +:IMPLICIT+ or to +:EXPLICIT+. + * +tagging+ is not set when a ASN.1 structure is parsed using + * OpenSSL::ASN1.decode. + */ + rb_attr(cASN1Primitive, rb_intern("tagging"), 1, 1, Qtrue); + rb_undef_method(cASN1Primitive, "infinite_length="); + rb_define_method(cASN1Primitive, "initialize", ossl_asn1_initialize, -1); + rb_define_method(cASN1Primitive, "to_der", ossl_asn1prim_to_der, 0); + + /* Document-class: OpenSSL::ASN1::Constructive + * + * The parent class for all constructed encodings. The +value+ attribute + * of a Constructive is always an +Array+. Attributes are the same as + * for ASN1Data, with the addition of +tagging+. + * + * == SET and SEQUENCE + * + * Most constructed encodings come in the form of a SET or a SEQUENCE. + * These encodings are represented by one of the two sub-classes of + * Constructive: + * * OpenSSL::ASN1::Set + * * OpenSSL::ASN1::Sequence + * Please note that tagged sequences and sets are still parsed as + * instances of ASN1Data. Find further details on tagged values + * there. + * + * === Example - constructing a SEQUENCE + * int = OpenSSL::ASN1::Integer.new(1) + * str = OpenSSL::ASN1::PrintableString.new('abc') + * sequence = OpenSSL::ASN1::Sequence.new( [ int, str ] ) + * + * === Example - constructing a SET + * int = OpenSSL::ASN1::Integer.new(1) + * str = OpenSSL::ASN1::PrintableString.new('abc') + * set = OpenSSL::ASN1::Set.new( [ int, str ] ) + * + * == Infinite length primitive values + * + * The only case where Constructive is used directly is for infinite + * length encodings of primitive values. These encodings are always + * constructed, with the contents of the +value+ +Array+ being either + * UNIVERSAL non-infinite length partial encodings of the actual value + * or again constructive encodings with infinite length (i.e. infinite + * length primitive encodings may be constructed recursively with another + * infinite length value within an already infinite length value). Each + * partial encoding must be of the same UNIVERSAL type as the overall + * encoding. The value of the overall encoding consists of the + * concatenation of each partial encoding taken in sequence. The +value+ + * array of the outer infinite length value must end with a + * OpenSSL::ASN1::EndOfContent instance. + * + * Please note that it is not possible to encode Constructive without + * the +infinite_length+ attribute being set to +true+, use + * OpenSSL::ASN1::Sequence or OpenSSL::ASN1::Set in these cases instead. + * + * === Example - Infinite length OCTET STRING + * partial1 = OpenSSL::ASN1::OctetString.new("\x01") + * partial2 = OpenSSL::ASN1::OctetString.new("\x02") + * inf_octets = OpenSSL::ASN1::Constructive.new( [ partial1, + * partial2, + * OpenSSL::ASN1::EndOfContent.new ], + * OpenSSL::ASN1::OCTET_STRING, + * nil, + * :UNIVERSAL ) + * # The real value of inf_octets is "\x01\x02", i.e. the concatenation + * # of partial1 and partial2 + * inf_octets.infinite_length = true + * der = inf_octets.to_der + * asn1 = OpenSSL::ASN1.decode(der) + * puts asn1.infinite_length # => true + */ + cASN1Constructive = rb_define_class_under(mASN1,"Constructive", cASN1Data); + rb_include_module(cASN1Constructive, rb_mEnumerable); + /* + * May be used as a hint for encoding a value either implicitly or + * explicitly by setting it either to +:IMPLICIT+ or to +:EXPLICIT+. + * +tagging+ is not set when a ASN.1 structure is parsed using + * OpenSSL::ASN1.decode. + */ + rb_attr(cASN1Constructive, rb_intern("tagging"), 1, 1, Qtrue); + rb_define_method(cASN1Constructive, "initialize", ossl_asn1_initialize, -1); + rb_define_method(cASN1Constructive, "to_der", ossl_asn1cons_to_der, 0); + rb_define_method(cASN1Constructive, "each", ossl_asn1cons_each, 0); + +#define OSSL_ASN1_DEFINE_CLASS(name, super) \ +do{\ + cASN1##name = rb_define_class_under(mASN1, #name, cASN1##super);\ + rb_define_module_function(mASN1, #name, ossl_asn1_##name, -1);\ +}while(0) + + OSSL_ASN1_DEFINE_CLASS(Boolean, Primitive); + OSSL_ASN1_DEFINE_CLASS(Integer, Primitive); + OSSL_ASN1_DEFINE_CLASS(Enumerated, Primitive); + OSSL_ASN1_DEFINE_CLASS(BitString, Primitive); + OSSL_ASN1_DEFINE_CLASS(OctetString, Primitive); + OSSL_ASN1_DEFINE_CLASS(UTF8String, Primitive); + OSSL_ASN1_DEFINE_CLASS(NumericString, Primitive); + OSSL_ASN1_DEFINE_CLASS(PrintableString, Primitive); + OSSL_ASN1_DEFINE_CLASS(T61String, Primitive); + OSSL_ASN1_DEFINE_CLASS(VideotexString, Primitive); + OSSL_ASN1_DEFINE_CLASS(IA5String, Primitive); + OSSL_ASN1_DEFINE_CLASS(GraphicString, Primitive); + OSSL_ASN1_DEFINE_CLASS(ISO64String, Primitive); + OSSL_ASN1_DEFINE_CLASS(GeneralString, Primitive); + OSSL_ASN1_DEFINE_CLASS(UniversalString, Primitive); + OSSL_ASN1_DEFINE_CLASS(BMPString, Primitive); + OSSL_ASN1_DEFINE_CLASS(Null, Primitive); + OSSL_ASN1_DEFINE_CLASS(ObjectId, Primitive); + OSSL_ASN1_DEFINE_CLASS(UTCTime, Primitive); + OSSL_ASN1_DEFINE_CLASS(GeneralizedTime, Primitive); + + OSSL_ASN1_DEFINE_CLASS(Sequence, Constructive); + OSSL_ASN1_DEFINE_CLASS(Set, Constructive); + + OSSL_ASN1_DEFINE_CLASS(EndOfContent, Data); + + + /* Document-class: OpenSSL::ASN1::ObjectId + * + * Represents the primitive object id for OpenSSL::ASN1 + */ +#if 0 + cASN1ObjectId = rb_define_class_under(mASN1, "ObjectId", cASN1Primitive); /* let rdoc know */ +#endif + rb_define_singleton_method(cASN1ObjectId, "register", ossl_asn1obj_s_register, 3); + rb_define_method(cASN1ObjectId, "sn", ossl_asn1obj_get_sn, 0); + rb_define_method(cASN1ObjectId, "ln", ossl_asn1obj_get_ln, 0); + rb_define_method(cASN1ObjectId, "oid", ossl_asn1obj_get_oid, 0); + rb_define_alias(cASN1ObjectId, "short_name", "sn"); + rb_define_alias(cASN1ObjectId, "long_name", "ln"); + rb_attr(cASN1BitString, rb_intern("unused_bits"), 1, 1, 0); + + rb_define_method(cASN1EndOfContent, "initialize", ossl_asn1eoc_initialize, 0); + + class_tag_map = rb_hash_new(); + rb_hash_aset(class_tag_map, cASN1EndOfContent, INT2NUM(V_ASN1_EOC)); + rb_hash_aset(class_tag_map, cASN1Boolean, INT2NUM(V_ASN1_BOOLEAN)); + rb_hash_aset(class_tag_map, cASN1Integer, INT2NUM(V_ASN1_INTEGER)); + rb_hash_aset(class_tag_map, cASN1BitString, INT2NUM(V_ASN1_BIT_STRING)); + rb_hash_aset(class_tag_map, cASN1OctetString, INT2NUM(V_ASN1_OCTET_STRING)); + rb_hash_aset(class_tag_map, cASN1Null, INT2NUM(V_ASN1_NULL)); + rb_hash_aset(class_tag_map, cASN1ObjectId, INT2NUM(V_ASN1_OBJECT)); + rb_hash_aset(class_tag_map, cASN1Enumerated, INT2NUM(V_ASN1_ENUMERATED)); + rb_hash_aset(class_tag_map, cASN1UTF8String, INT2NUM(V_ASN1_UTF8STRING)); + rb_hash_aset(class_tag_map, cASN1Sequence, INT2NUM(V_ASN1_SEQUENCE)); + rb_hash_aset(class_tag_map, cASN1Set, INT2NUM(V_ASN1_SET)); + rb_hash_aset(class_tag_map, cASN1NumericString, INT2NUM(V_ASN1_NUMERICSTRING)); + rb_hash_aset(class_tag_map, cASN1PrintableString, INT2NUM(V_ASN1_PRINTABLESTRING)); + rb_hash_aset(class_tag_map, cASN1T61String, INT2NUM(V_ASN1_T61STRING)); + rb_hash_aset(class_tag_map, cASN1VideotexString, INT2NUM(V_ASN1_VIDEOTEXSTRING)); + rb_hash_aset(class_tag_map, cASN1IA5String, INT2NUM(V_ASN1_IA5STRING)); + rb_hash_aset(class_tag_map, cASN1UTCTime, INT2NUM(V_ASN1_UTCTIME)); + rb_hash_aset(class_tag_map, cASN1GeneralizedTime, INT2NUM(V_ASN1_GENERALIZEDTIME)); + rb_hash_aset(class_tag_map, cASN1GraphicString, INT2NUM(V_ASN1_GRAPHICSTRING)); + rb_hash_aset(class_tag_map, cASN1ISO64String, INT2NUM(V_ASN1_ISO64STRING)); + rb_hash_aset(class_tag_map, cASN1GeneralString, INT2NUM(V_ASN1_GENERALSTRING)); + rb_hash_aset(class_tag_map, cASN1UniversalString, INT2NUM(V_ASN1_UNIVERSALSTRING)); + rb_hash_aset(class_tag_map, cASN1BMPString, INT2NUM(V_ASN1_BMPSTRING)); + rb_global_variable(&class_tag_map); +} diff --git a/ext/openssl/ossl_asn1.h b/ext/openssl/ossl_asn1.h new file mode 100644 index 00000000..718f43f0 --- /dev/null +++ b/ext/openssl/ossl_asn1.h @@ -0,0 +1,59 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' team members + * Copyright (C) 2003 + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#if !defined(_OSSL_ASN1_H_) +#define _OSSL_ASN1_H_ + +/* + * ASN1_DATE conversions + */ +VALUE asn1time_to_time(ASN1_TIME *); +time_t time_to_time_t(VALUE); + +/* + * ASN1_STRING conversions + */ +VALUE asn1str_to_str(ASN1_STRING *); + +/* + * ASN1_INTEGER conversions + */ +VALUE asn1integer_to_num(ASN1_INTEGER *); +ASN1_INTEGER *num_to_asn1integer(VALUE, ASN1_INTEGER *); + +/* + * ASN1 module + */ +extern VALUE mASN1; +extern VALUE eASN1Error; + +extern VALUE cASN1Data; +extern VALUE cASN1Primitive; +extern VALUE cASN1Constructive; + +extern VALUE cASN1Boolean; /* BOOLEAN */ +extern VALUE cASN1Integer, cASN1Enumerated; /* INTEGER */ +extern VALUE cASN1BitString; /* BIT STRING */ +extern VALUE cASN1OctetString, cASN1UTF8String; /* STRINGs */ +extern VALUE cASN1NumericString, cASN1PrintableString; +extern VALUE cASN1T61String, cASN1VideotexString; +extern VALUE cASN1IA5String, cASN1GraphicString; +extern VALUE cASN1ISO64String, cASN1GeneralString; +extern VALUE cASN1UniversalString, cASN1BMPString; +extern VALUE cASN1Null; /* NULL */ +extern VALUE cASN1ObjectId; /* OBJECT IDENTIFIER */ +extern VALUE cASN1UTCTime, cASN1GeneralizedTime; /* TIME */ +extern VALUE cASN1Sequence, cASN1Set; /* CONSTRUCTIVE */ + +ASN1_TYPE *ossl_asn1_get_asn1type(VALUE); + +void Init_ossl_asn1(void); + +#endif diff --git a/ext/openssl/ossl_bio.c b/ext/openssl/ossl_bio.c new file mode 100644 index 00000000..e150de0a --- /dev/null +++ b/ext/openssl/ossl_bio.c @@ -0,0 +1,87 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' team members + * Copyright (C) 2003 + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#include "ossl.h" +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +BIO * +ossl_obj2bio(VALUE obj) +{ + BIO *bio; + + if (RB_TYPE_P(obj, T_FILE)) { + rb_io_t *fptr; + FILE *fp; + int fd; + + GetOpenFile(obj, fptr); + rb_io_check_readable(fptr); + if ((fd = rb_cloexec_dup(FPTR_TO_FD(fptr))) < 0){ + rb_sys_fail(0); + } + rb_update_max_fd(fd); + if (!(fp = fdopen(fd, "r"))){ + close(fd); + rb_sys_fail(0); + } + if (!(bio = BIO_new_fp(fp, BIO_CLOSE))){ + fclose(fp); + ossl_raise(eOSSLError, NULL); + } + } + else { + StringValue(obj); + bio = BIO_new_mem_buf(RSTRING_PTR(obj), RSTRING_LENINT(obj)); + if (!bio) ossl_raise(eOSSLError, NULL); + } + + return bio; +} + +BIO * +ossl_protect_obj2bio(VALUE obj, int *status) +{ + BIO *ret = NULL; + ret = (BIO*)rb_protect((VALUE(*)_((VALUE)))ossl_obj2bio, obj, status); + return ret; +} + +VALUE +ossl_membio2str0(BIO *bio) +{ + VALUE ret; + BUF_MEM *buf; + + BIO_get_mem_ptr(bio, &buf); + ret = rb_str_new(buf->data, buf->length); + + return ret; +} + +VALUE +ossl_protect_membio2str(BIO *bio, int *status) +{ + return rb_protect((VALUE(*)_((VALUE)))ossl_membio2str0, (VALUE)bio, status); +} + +VALUE +ossl_membio2str(BIO *bio) +{ + VALUE ret; + int status = 0; + + ret = ossl_protect_membio2str(bio, &status); + BIO_free(bio); + if(status) rb_jump_tag(status); + + return ret; +} diff --git a/ext/openssl/ossl_bio.h b/ext/openssl/ossl_bio.h new file mode 100644 index 00000000..2d8f675c --- /dev/null +++ b/ext/openssl/ossl_bio.h @@ -0,0 +1,21 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' team members + * Copyright (C) 2003 + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#if !defined(_OSSL_BIO_H_) +#define _OSSL_BIO_H_ + +BIO *ossl_obj2bio(VALUE); +BIO *ossl_protect_obj2bio(VALUE,int*); +VALUE ossl_membio2str0(BIO*); +VALUE ossl_membio2str(BIO*); +VALUE ossl_protect_membio2str(BIO*,int*); + +#endif + diff --git a/ext/openssl/ossl_bn.c b/ext/openssl/ossl_bn.c new file mode 100644 index 00000000..0af7d639 --- /dev/null +++ b/ext/openssl/ossl_bn.c @@ -0,0 +1,915 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2001-2002 Technorama team <oss-ruby@technorama.net> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +/* modified by Michal Rokos <m.rokos@sh.cvut.cz> */ +#include "ossl.h" + +#define WrapBN(klass, obj, bn) do { \ + if (!(bn)) { \ + ossl_raise(rb_eRuntimeError, "BN wasn't initialized!"); \ + } \ + (obj) = TypedData_Wrap_Struct((klass), &ossl_bn_type, (bn)); \ +} while (0) + +#define GetBN(obj, bn) do { \ + TypedData_Get_Struct((obj), BIGNUM, &ossl_bn_type, (bn)); \ + if (!(bn)) { \ + ossl_raise(rb_eRuntimeError, "BN wasn't initialized!"); \ + } \ +} while (0) + +#define SafeGetBN(obj, bn) do { \ + OSSL_Check_Kind((obj), cBN); \ + GetBN((obj), (bn)); \ +} while (0) + +static void +ossl_bn_free(void *ptr) +{ + BN_clear_free(ptr); +} + +static size_t +ossl_bn_size(const void *ptr) +{ + return sizeof(BIGNUM); +} + +static const rb_data_type_t ossl_bn_type = { + "OpenSSL/BN", + {0, ossl_bn_free, ossl_bn_size,}, + NULL, NULL, + RUBY_TYPED_FREE_IMMEDIATELY, +}; + +/* + * Classes + */ +VALUE cBN; +VALUE eBNError; + +/* + * Public + */ +VALUE +ossl_bn_new(const BIGNUM *bn) +{ + BIGNUM *newbn; + VALUE obj; + + newbn = bn ? BN_dup(bn) : BN_new(); + if (!newbn) { + ossl_raise(eBNError, NULL); + } + WrapBN(cBN, obj, newbn); + + return obj; +} + +BIGNUM * +GetBNPtr(VALUE obj) +{ + BIGNUM *bn = NULL; + + if (RTEST(rb_obj_is_kind_of(obj, cBN))) { + GetBN(obj, bn); + } else switch (TYPE(obj)) { + case T_FIXNUM: + case T_BIGNUM: + obj = rb_String(obj); + if (!BN_dec2bn(&bn, StringValuePtr(obj))) { + ossl_raise(eBNError, NULL); + } + WrapBN(cBN, obj, bn); /* Handle potencial mem leaks */ + break; + case T_NIL: + break; + default: + ossl_raise(rb_eTypeError, "Cannot convert into OpenSSL::BN"); + } + return bn; +} + +/* + * Private + */ +/* + * BN_CTX - is used in more difficult math. ops + * (Why just 1? Because Ruby itself isn't thread safe, + * we don't need to care about threads) + */ +BN_CTX *ossl_bn_ctx; + +static VALUE +ossl_bn_alloc(VALUE klass) +{ + BIGNUM *bn; + VALUE obj; + + if (!(bn = BN_new())) { + ossl_raise(eBNError, NULL); + } + WrapBN(klass, obj, bn); + + return obj; +} + +/* + * call-seq: + * BN.new => aBN + * BN.new(bn) => aBN + * BN.new(integer) => aBN + * BN.new(string) => aBN + * BN.new(string, 0 | 2 | 10 | 16) => aBN + */ +static VALUE +ossl_bn_initialize(int argc, VALUE *argv, VALUE self) +{ + BIGNUM *bn; + VALUE str, bs; + int base = 10; + + if (rb_scan_args(argc, argv, "11", &str, &bs) == 2) { + base = NUM2INT(bs); + } + + if (RB_TYPE_P(str, T_FIXNUM)) { + long i; + unsigned char bin[sizeof(long)]; + long n = FIX2LONG(str); + unsigned long un = labs(n); + + for (i = sizeof(long) - 1; 0 <= i; i--) { + bin[i] = un&0xff; + un >>= 8; + } + + GetBN(self, bn); + if (!BN_bin2bn(bin, sizeof(bin), bn)) { + ossl_raise(eBNError, NULL); + } + if (n < 0) BN_set_negative(bn, 1); + return self; + } + else if (RB_TYPE_P(str, T_BIGNUM)) { + size_t len = rb_absint_size(str, NULL); + unsigned char *bin; + VALUE buf; + int sign; + + if (INT_MAX < len) { + rb_raise(eBNError, "bignum too long"); + } + bin = (unsigned char*)ALLOCV_N(unsigned char, buf, len); + sign = rb_integer_pack(str, bin, len, 1, 0, INTEGER_PACK_BIG_ENDIAN); + + GetBN(self, bn); + if (!BN_bin2bn(bin, (int)len, bn)) { + ALLOCV_END(buf); + ossl_raise(eBNError, NULL); + } + ALLOCV_END(buf); + if (sign < 0) BN_set_negative(bn, 1); + return self; + } + if (RTEST(rb_obj_is_kind_of(str, cBN))) { + BIGNUM *other; + + GetBN(self, bn); + GetBN(str, other); /* Safe - we checked kind_of? above */ + if (!BN_copy(bn, other)) { + ossl_raise(eBNError, NULL); + } + return self; + } + + StringValue(str); + GetBN(self, bn); + switch (base) { + case 0: + if (!BN_mpi2bn((unsigned char *)RSTRING_PTR(str), RSTRING_LENINT(str), bn)) { + ossl_raise(eBNError, NULL); + } + break; + case 2: + if (!BN_bin2bn((unsigned char *)RSTRING_PTR(str), RSTRING_LENINT(str), bn)) { + ossl_raise(eBNError, NULL); + } + break; + case 10: + if (!BN_dec2bn(&bn, RSTRING_PTR(str))) { + ossl_raise(eBNError, NULL); + } + break; + case 16: + if (!BN_hex2bn(&bn, RSTRING_PTR(str))) { + ossl_raise(eBNError, NULL); + } + break; + default: + ossl_raise(rb_eArgError, "invalid radix %d", base); + } + return self; +} + +/* + * call-seq: + * bn.to_s => string + * bn.to_s(base) => string + * + * === Parameters + * * +base+ - integer + * * * Valid values: + * * * * 0 - MPI + * * * * 2 - binary + * * * * 10 - the default + * * * * 16 - hex + */ +static VALUE +ossl_bn_to_s(int argc, VALUE *argv, VALUE self) +{ + BIGNUM *bn; + VALUE str, bs; + int base = 10, len; + char *buf; + + if (rb_scan_args(argc, argv, "01", &bs) == 1) { + base = NUM2INT(bs); + } + GetBN(self, bn); + switch (base) { + case 0: + len = BN_bn2mpi(bn, NULL); + str = rb_str_new(0, len); + if (BN_bn2mpi(bn, (unsigned char *)RSTRING_PTR(str)) != len) + ossl_raise(eBNError, NULL); + break; + case 2: + len = BN_num_bytes(bn); + str = rb_str_new(0, len); + if (BN_bn2bin(bn, (unsigned char *)RSTRING_PTR(str)) != len) + ossl_raise(eBNError, NULL); + break; + case 10: + if (!(buf = BN_bn2dec(bn))) ossl_raise(eBNError, NULL); + str = ossl_buf2str(buf, rb_long2int(strlen(buf))); + break; + case 16: + if (!(buf = BN_bn2hex(bn))) ossl_raise(eBNError, NULL); + str = ossl_buf2str(buf, rb_long2int(strlen(buf))); + break; + default: + ossl_raise(rb_eArgError, "invalid radix %d", base); + } + + return str; +} + +/* + * call-seq: + * bn.to_i => integer + */ +static VALUE +ossl_bn_to_i(VALUE self) +{ + BIGNUM *bn; + char *txt; + VALUE num; + + GetBN(self, bn); + + if (!(txt = BN_bn2hex(bn))) { + ossl_raise(eBNError, NULL); + } + num = rb_cstr_to_inum(txt, 16, Qtrue); + OPENSSL_free(txt); + + return num; +} + +static VALUE +ossl_bn_to_bn(VALUE self) +{ + return self; +} + +static VALUE +ossl_bn_coerce(VALUE self, VALUE other) +{ + switch(TYPE(other)) { + case T_STRING: + self = ossl_bn_to_s(0, NULL, self); + break; + case T_FIXNUM: + case T_BIGNUM: + self = ossl_bn_to_i(self); + break; + default: + if (!RTEST(rb_obj_is_kind_of(other, cBN))) { + ossl_raise(rb_eTypeError, "Don't know how to coerce"); + } + } + return rb_assoc_new(other, self); +} + +#define BIGNUM_BOOL1(func) \ + /* \ + * call-seq: \ + * bn.##func -> true | false \ + * \ + */ \ + static VALUE \ + ossl_bn_##func(VALUE self) \ + { \ + BIGNUM *bn; \ + GetBN(self, bn); \ + if (BN_##func(bn)) { \ + return Qtrue; \ + } \ + return Qfalse; \ + } +BIGNUM_BOOL1(is_zero) +BIGNUM_BOOL1(is_one) +BIGNUM_BOOL1(is_odd) + +#define BIGNUM_1c(func) \ + /* \ + * call-seq: \ + * bn.##func -> aBN \ + * \ + */ \ + static VALUE \ + ossl_bn_##func(VALUE self) \ + { \ + BIGNUM *bn, *result; \ + VALUE obj; \ + GetBN(self, bn); \ + if (!(result = BN_new())) { \ + ossl_raise(eBNError, NULL); \ + } \ + if (!BN_##func(result, bn, ossl_bn_ctx)) { \ + BN_free(result); \ + ossl_raise(eBNError, NULL); \ + } \ + WrapBN(CLASS_OF(self), obj, result); \ + return obj; \ + } +BIGNUM_1c(sqr) + +#define BIGNUM_2(func) \ + /* \ + * call-seq: \ + * bn.##func(bn2) -> aBN \ + * \ + */ \ + static VALUE \ + ossl_bn_##func(VALUE self, VALUE other) \ + { \ + BIGNUM *bn1, *bn2 = GetBNPtr(other), *result; \ + VALUE obj; \ + GetBN(self, bn1); \ + if (!(result = BN_new())) { \ + ossl_raise(eBNError, NULL); \ + } \ + if (!BN_##func(result, bn1, bn2)) { \ + BN_free(result); \ + ossl_raise(eBNError, NULL); \ + } \ + WrapBN(CLASS_OF(self), obj, result); \ + return obj; \ + } +BIGNUM_2(add) +BIGNUM_2(sub) + +#define BIGNUM_2c(func) \ + /* \ + * call-seq: \ + * bn.##func(bn2) -> aBN \ + * \ + */ \ + static VALUE \ + ossl_bn_##func(VALUE self, VALUE other) \ + { \ + BIGNUM *bn1, *bn2 = GetBNPtr(other), *result; \ + VALUE obj; \ + GetBN(self, bn1); \ + if (!(result = BN_new())) { \ + ossl_raise(eBNError, NULL); \ + } \ + if (!BN_##func(result, bn1, bn2, ossl_bn_ctx)) { \ + BN_free(result); \ + ossl_raise(eBNError, NULL); \ + } \ + WrapBN(CLASS_OF(self), obj, result); \ + return obj; \ + } +BIGNUM_2c(mul) +BIGNUM_2c(mod) +BIGNUM_2c(exp) +BIGNUM_2c(gcd) +BIGNUM_2c(mod_sqr) +BIGNUM_2c(mod_inverse) + +/* + * call-seq: + * bn1 / bn2 => [result, remainder] + */ +static VALUE +ossl_bn_div(VALUE self, VALUE other) +{ + BIGNUM *bn1, *bn2 = GetBNPtr(other), *r1, *r2; + VALUE obj1, obj2; + + GetBN(self, bn1); + + if (!(r1 = BN_new())) { + ossl_raise(eBNError, NULL); + } + if (!(r2 = BN_new())) { + BN_free(r1); + ossl_raise(eBNError, NULL); + } + if (!BN_div(r1, r2, bn1, bn2, ossl_bn_ctx)) { + BN_free(r1); + BN_free(r2); + ossl_raise(eBNError, NULL); + } + WrapBN(CLASS_OF(self), obj1, r1); + WrapBN(CLASS_OF(self), obj2, r2); + + return rb_ary_new3(2, obj1, obj2); +} + +#define BIGNUM_3c(func) \ + /* \ + * call-seq: \ + * bn.##func(bn1, bn2) -> aBN \ + * \ + */ \ + static VALUE \ + ossl_bn_##func(VALUE self, VALUE other1, VALUE other2) \ + { \ + BIGNUM *bn1, *bn2 = GetBNPtr(other1); \ + BIGNUM *bn3 = GetBNPtr(other2), *result; \ + VALUE obj; \ + GetBN(self, bn1); \ + if (!(result = BN_new())) { \ + ossl_raise(eBNError, NULL); \ + } \ + if (!BN_##func(result, bn1, bn2, bn3, ossl_bn_ctx)) { \ + BN_free(result); \ + ossl_raise(eBNError, NULL); \ + } \ + WrapBN(CLASS_OF(self), obj, result); \ + return obj; \ + } +BIGNUM_3c(mod_add) +BIGNUM_3c(mod_sub) +BIGNUM_3c(mod_mul) +BIGNUM_3c(mod_exp) + +#define BIGNUM_BIT(func) \ + /* \ + * call-seq: \ + * bn.##func(bit) -> self \ + * \ + */ \ + static VALUE \ + ossl_bn_##func(VALUE self, VALUE bit) \ + { \ + BIGNUM *bn; \ + GetBN(self, bn); \ + if (!BN_##func(bn, NUM2INT(bit))) { \ + ossl_raise(eBNError, NULL); \ + } \ + return self; \ + } +BIGNUM_BIT(set_bit) +BIGNUM_BIT(clear_bit) +BIGNUM_BIT(mask_bits) + +/* + * call-seq: + * bn.bit_set?(bit) => true | false + */ +static VALUE +ossl_bn_is_bit_set(VALUE self, VALUE bit) +{ + int b; + BIGNUM *bn; + + b = NUM2INT(bit); + GetBN(self, bn); + if (BN_is_bit_set(bn, b)) { + return Qtrue; + } + return Qfalse; +} + +#define BIGNUM_SHIFT(func) \ + /* \ + * call-seq: \ + * bn.##func(bits) -> aBN \ + * \ + */ \ + static VALUE \ + ossl_bn_##func(VALUE self, VALUE bits) \ + { \ + BIGNUM *bn, *result; \ + int b; \ + VALUE obj; \ + b = NUM2INT(bits); \ + GetBN(self, bn); \ + if (!(result = BN_new())) { \ + ossl_raise(eBNError, NULL); \ + } \ + if (!BN_##func(result, bn, b)) { \ + BN_free(result); \ + ossl_raise(eBNError, NULL); \ + } \ + WrapBN(CLASS_OF(self), obj, result); \ + return obj; \ + } +BIGNUM_SHIFT(lshift) +BIGNUM_SHIFT(rshift) + +#define BIGNUM_SELF_SHIFT(func) \ + /* \ + * call-seq: \ + * bn.##func!(bits) -> self \ + * \ + */ \ + static VALUE \ + ossl_bn_self_##func(VALUE self, VALUE bits) \ + { \ + BIGNUM *bn; \ + int b; \ + b = NUM2INT(bits); \ + GetBN(self, bn); \ + if (!BN_##func(bn, bn, b)) \ + ossl_raise(eBNError, NULL); \ + return self; \ + } +BIGNUM_SELF_SHIFT(lshift) +BIGNUM_SELF_SHIFT(rshift) + +#define BIGNUM_RAND(func) \ + /* \ + * call-seq: \ + * BN.##func(bits [, fill [, odd]]) -> aBN \ + * \ + */ \ + static VALUE \ + ossl_bn_s_##func(int argc, VALUE *argv, VALUE klass) \ + { \ + BIGNUM *result; \ + int bottom = 0, top = 0, b; \ + VALUE bits, fill, odd, obj; \ + \ + switch (rb_scan_args(argc, argv, "12", &bits, &fill, &odd)) { \ + case 3: \ + bottom = (odd == Qtrue) ? 1 : 0; \ + /* FALLTHROUGH */ \ + case 2: \ + top = NUM2INT(fill); \ + } \ + b = NUM2INT(bits); \ + if (!(result = BN_new())) { \ + ossl_raise(eBNError, NULL); \ + } \ + if (!BN_##func(result, b, top, bottom)) { \ + BN_free(result); \ + ossl_raise(eBNError, NULL); \ + } \ + WrapBN(klass, obj, result); \ + return obj; \ + } +BIGNUM_RAND(rand) +BIGNUM_RAND(pseudo_rand) + +#define BIGNUM_RAND_RANGE(func) \ + /* \ + * call-seq: \ + * BN.##func(range) -> aBN \ + * \ + */ \ + static VALUE \ + ossl_bn_s_##func##_range(VALUE klass, VALUE range) \ + { \ + BIGNUM *bn = GetBNPtr(range), *result; \ + VALUE obj; \ + if (!(result = BN_new())) { \ + ossl_raise(eBNError, NULL); \ + } \ + if (!BN_##func##_range(result, bn)) { \ + BN_free(result); \ + ossl_raise(eBNError, NULL); \ + } \ + WrapBN(klass, obj, result); \ + return obj; \ + } +BIGNUM_RAND_RANGE(rand) +BIGNUM_RAND_RANGE(pseudo_rand) + +/* + * call-seq: + * BN.generate_prime(bits, [, safe [, add [, rem]]]) => bn + * + * === Parameters + * * +bits+ - integer + * * +safe+ - boolean + * * +add+ - BN + * * +rem+ - BN + */ +static VALUE +ossl_bn_s_generate_prime(int argc, VALUE *argv, VALUE klass) +{ + BIGNUM *add = NULL, *rem = NULL, *result; + int safe = 1, num; + VALUE vnum, vsafe, vadd, vrem, obj; + + rb_scan_args(argc, argv, "13", &vnum, &vsafe, &vadd, &vrem); + + num = NUM2INT(vnum); + + if (vsafe == Qfalse) { + safe = 0; + } + if (!NIL_P(vadd)) { + add = GetBNPtr(vadd); + rem = NIL_P(vrem) ? NULL : GetBNPtr(vrem); + } + if (!(result = BN_new())) { + ossl_raise(eBNError, NULL); + } + if (!BN_generate_prime(result, num, safe, add, rem, NULL, NULL)) { + BN_free(result); + ossl_raise(eBNError, NULL); + } + WrapBN(klass, obj, result); + + return obj; +} + +#define BIGNUM_NUM(func) \ + /* \ + * call-seq: \ + * bn.##func -> integer \ + * \ + */ \ + static VALUE \ + ossl_bn_##func(VALUE self) \ + { \ + BIGNUM *bn; \ + GetBN(self, bn); \ + return INT2FIX(BN_##func(bn)); \ + } +BIGNUM_NUM(num_bytes) +BIGNUM_NUM(num_bits) + +static VALUE +ossl_bn_copy(VALUE self, VALUE other) +{ + BIGNUM *bn1, *bn2; + + rb_check_frozen(self); + + if (self == other) return self; + + GetBN(self, bn1); + bn2 = GetBNPtr(other); + + if (!BN_copy(bn1, bn2)) { + ossl_raise(eBNError, NULL); + } + return self; +} + +#define BIGNUM_CMP(func) \ + /* \ + * call-seq: \ + * bn.##func(bn2) -> integer \ + * \ + */ \ + static VALUE \ + ossl_bn_##func(VALUE self, VALUE other) \ + { \ + BIGNUM *bn1, *bn2 = GetBNPtr(other); \ + GetBN(self, bn1); \ + return INT2FIX(BN_##func(bn1, bn2)); \ + } +BIGNUM_CMP(cmp) +BIGNUM_CMP(ucmp) + +static VALUE +ossl_bn_eql(VALUE self, VALUE other) +{ + if (ossl_bn_cmp(self, other) == INT2FIX(0)) { + return Qtrue; + } + return Qfalse; +} + +/* + * call-seq: + * bn.prime? => true | false + * bn.prime?(checks) => true | false + * + * === Parameters + * * +checks+ - integer + */ +static VALUE +ossl_bn_is_prime(int argc, VALUE *argv, VALUE self) +{ + BIGNUM *bn; + VALUE vchecks; + int checks = BN_prime_checks; + + if (rb_scan_args(argc, argv, "01", &vchecks) == 1) { + checks = NUM2INT(vchecks); + } + GetBN(self, bn); + switch (BN_is_prime(bn, checks, NULL, ossl_bn_ctx, NULL)) { + case 1: + return Qtrue; + case 0: + return Qfalse; + default: + ossl_raise(eBNError, NULL); + } + /* not reachable */ + return Qnil; +} + +/* + * call-seq: + * bn.prime_fasttest? => true | false + * bn.prime_fasttest?(checks) => true | false + * bn.prime_fasttest?(checks, trial_div) => true | false + * + * === Parameters + * * +checks+ - integer + * * +trial_div+ - boolean + */ +static VALUE +ossl_bn_is_prime_fasttest(int argc, VALUE *argv, VALUE self) +{ + BIGNUM *bn; + VALUE vchecks, vtrivdiv; + int checks = BN_prime_checks, do_trial_division = 1; + + rb_scan_args(argc, argv, "02", &vchecks, &vtrivdiv); + + if (!NIL_P(vchecks)) { + checks = NUM2INT(vchecks); + } + GetBN(self, bn); + /* handle true/false */ + if (vtrivdiv == Qfalse) { + do_trial_division = 0; + } + switch (BN_is_prime_fasttest(bn, checks, NULL, ossl_bn_ctx, NULL, do_trial_division)) { + case 1: + return Qtrue; + case 0: + return Qfalse; + default: + ossl_raise(eBNError, NULL); + } + /* not reachable */ + return Qnil; +} + +/* + * INIT + * (NOTE: ordering of methods is the same as in 'man bn') + */ +void +Init_ossl_bn(void) +{ +#if 0 + mOSSL = rb_define_module("OpenSSL"); /* let rdoc know about mOSSL */ +#endif + + if (!(ossl_bn_ctx = BN_CTX_new())) { + ossl_raise(rb_eRuntimeError, "Cannot init BN_CTX"); + } + + eBNError = rb_define_class_under(mOSSL, "BNError", eOSSLError); + + cBN = rb_define_class_under(mOSSL, "BN", rb_cObject); + + rb_define_alloc_func(cBN, ossl_bn_alloc); + rb_define_method(cBN, "initialize", ossl_bn_initialize, -1); + + rb_define_copy_func(cBN, ossl_bn_copy); + rb_define_method(cBN, "copy", ossl_bn_copy, 1); + + /* swap (=coerce?) */ + + rb_define_method(cBN, "num_bytes", ossl_bn_num_bytes, 0); + rb_define_method(cBN, "num_bits", ossl_bn_num_bits, 0); + /* num_bits_word */ + + rb_define_method(cBN, "+", ossl_bn_add, 1); + rb_define_method(cBN, "-", ossl_bn_sub, 1); + rb_define_method(cBN, "*", ossl_bn_mul, 1); + rb_define_method(cBN, "sqr", ossl_bn_sqr, 0); + rb_define_method(cBN, "/", ossl_bn_div, 1); + rb_define_method(cBN, "%", ossl_bn_mod, 1); + /* nnmod */ + + rb_define_method(cBN, "mod_add", ossl_bn_mod_add, 2); + rb_define_method(cBN, "mod_sub", ossl_bn_mod_sub, 2); + rb_define_method(cBN, "mod_mul", ossl_bn_mod_mul, 2); + rb_define_method(cBN, "mod_sqr", ossl_bn_mod_sqr, 1); + rb_define_method(cBN, "**", ossl_bn_exp, 1); + rb_define_method(cBN, "mod_exp", ossl_bn_mod_exp, 2); + rb_define_method(cBN, "gcd", ossl_bn_gcd, 1); + + /* add_word + * sub_word + * mul_word + * div_word + * mod_word */ + + rb_define_method(cBN, "cmp", ossl_bn_cmp, 1); + rb_define_alias(cBN, "<=>", "cmp"); + rb_define_method(cBN, "ucmp", ossl_bn_ucmp, 1); + rb_define_method(cBN, "eql?", ossl_bn_eql, 1); + rb_define_alias(cBN, "==", "eql?"); + rb_define_alias(cBN, "===", "eql?"); + rb_define_method(cBN, "zero?", ossl_bn_is_zero, 0); + rb_define_method(cBN, "one?", ossl_bn_is_one, 0); + /* is_word */ + rb_define_method(cBN, "odd?", ossl_bn_is_odd, 0); + + /* zero + * one + * value_one - DON'T IMPL. + * set_word + * get_word */ + + rb_define_singleton_method(cBN, "rand", ossl_bn_s_rand, -1); + rb_define_singleton_method(cBN, "pseudo_rand", ossl_bn_s_pseudo_rand, -1); + rb_define_singleton_method(cBN, "rand_range", ossl_bn_s_rand_range, 1); + rb_define_singleton_method(cBN, "pseudo_rand_range", ossl_bn_s_pseudo_rand_range, 1); + + rb_define_singleton_method(cBN, "generate_prime", ossl_bn_s_generate_prime, -1); + rb_define_method(cBN, "prime?", ossl_bn_is_prime, -1); + + rb_define_method(cBN, "set_bit!", ossl_bn_set_bit, 1); + rb_define_method(cBN, "clear_bit!", ossl_bn_clear_bit, 1); + rb_define_method(cBN, "bit_set?", ossl_bn_is_bit_set, 1); + rb_define_method(cBN, "mask_bits!", ossl_bn_mask_bits, 1); + rb_define_method(cBN, "<<", ossl_bn_lshift, 1); + rb_define_method(cBN, ">>", ossl_bn_rshift, 1); + rb_define_method(cBN, "lshift!", ossl_bn_self_lshift, 1); + rb_define_method(cBN, "rshift!", ossl_bn_self_rshift, 1); + /* lshift1 - DON'T IMPL. */ + /* rshift1 - DON'T IMPL. */ + + /* + * bn2bin + * bin2bn + * bn2hex + * bn2dec + * hex2bn + * dec2bn - all these are implemented in ossl_bn_initialize, and ossl_bn_to_s + * print - NOT IMPL. + * print_fp - NOT IMPL. + * bn2mpi + * mpi2bn + */ + rb_define_method(cBN, "to_s", ossl_bn_to_s, -1); + rb_define_method(cBN, "to_i", ossl_bn_to_i, 0); + rb_define_alias(cBN, "to_int", "to_i"); + rb_define_method(cBN, "to_bn", ossl_bn_to_bn, 0); + rb_define_method(cBN, "coerce", ossl_bn_coerce, 1); + + /* + * TODO: + * But how to: from_bin, from_mpi? PACK? + * to_bin + * to_mpi + */ + + rb_define_method(cBN, "mod_inverse", ossl_bn_mod_inverse, 1); + + /* RECiProcal + * MONTgomery */ + + /* + * TODO: + * Where to belong these? + */ + rb_define_method(cBN, "prime_fasttest?", ossl_bn_is_prime_fasttest, -1); +} + diff --git a/ext/openssl/ossl_bn.h b/ext/openssl/ossl_bn.h new file mode 100644 index 00000000..d6c39622 --- /dev/null +++ b/ext/openssl/ossl_bn.h @@ -0,0 +1,25 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2001-2002 Michal Rokos <m.rokos@sh.cvut.cz> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#if !defined(_OSSL_BN_H_) +#define _OSSL_BN_H_ + +extern VALUE cBN; +extern VALUE eBNError; + +extern BN_CTX *ossl_bn_ctx; + +VALUE ossl_bn_new(const BIGNUM *); +BIGNUM *GetBNPtr(VALUE); +void Init_ossl_bn(void); + + +#endif /* _OSS_BN_H_ */ + diff --git a/ext/openssl/ossl_cipher.c b/ext/openssl/ossl_cipher.c new file mode 100644 index 00000000..0efadd19 --- /dev/null +++ b/ext/openssl/ossl_cipher.c @@ -0,0 +1,987 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2001-2002 Michal Rokos <m.rokos@sh.cvut.cz> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#include "ossl.h" + +#define WrapCipher(obj, klass, ctx) \ + (obj) = TypedData_Wrap_Struct((klass), &ossl_cipher_type, (ctx)) +#define MakeCipher(obj, klass, ctx) \ + (obj) = TypedData_Make_Struct((klass), EVP_CIPHER_CTX, &ossl_cipher_type, (ctx)) +#define AllocCipher(obj, ctx) \ + (DATA_PTR(obj) = (ctx) = ZALLOC(EVP_CIPHER_CTX)) +#define GetCipherInit(obj, ctx) do { \ + TypedData_Get_Struct((obj), EVP_CIPHER_CTX, &ossl_cipher_type, (ctx)); \ +} while (0) +#define GetCipher(obj, ctx) do { \ + GetCipherInit((obj), (ctx)); \ + if (!(ctx)) { \ + ossl_raise(rb_eRuntimeError, "Cipher not inititalized!"); \ + } \ +} while (0) +#define SafeGetCipher(obj, ctx) do { \ + OSSL_Check_Kind((obj), cCipher); \ + GetCipher((obj), (ctx)); \ +} while (0) + +/* + * Classes + */ +VALUE cCipher; +VALUE eCipherError; + +static VALUE ossl_cipher_alloc(VALUE klass); +static void ossl_cipher_free(void *ptr); +static size_t ossl_cipher_memsize(const void *ptr); + +static const rb_data_type_t ossl_cipher_type = { + "OpenSSL/Cipher", + {0, ossl_cipher_free, ossl_cipher_memsize,}, + NULL, NULL, + RUBY_TYPED_FREE_IMMEDIATELY, +}; + +/* + * PUBLIC + */ +const EVP_CIPHER * +GetCipherPtr(VALUE obj) +{ + EVP_CIPHER_CTX *ctx; + + SafeGetCipher(obj, ctx); + + return EVP_CIPHER_CTX_cipher(ctx); +} + +VALUE +ossl_cipher_new(const EVP_CIPHER *cipher) +{ + VALUE ret; + EVP_CIPHER_CTX *ctx; + + ret = ossl_cipher_alloc(cCipher); + AllocCipher(ret, ctx); + EVP_CIPHER_CTX_init(ctx); + if (EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, -1) != 1) + ossl_raise(eCipherError, NULL); + + return ret; +} + +/* + * PRIVATE + */ +static void +ossl_cipher_free(void *ptr) +{ + EVP_CIPHER_CTX *ctx = ptr; + if (ctx) { + EVP_CIPHER_CTX_cleanup(ctx); + ruby_xfree(ctx); + } +} + +static size_t +ossl_cipher_memsize(const void *ptr) +{ + const EVP_CIPHER_CTX *ctx = ptr; + return ctx ? sizeof(*ctx) : 0; +} + +static VALUE +ossl_cipher_alloc(VALUE klass) +{ + VALUE obj; + + WrapCipher(obj, klass, 0); + + return obj; +} + +/* + * call-seq: + * Cipher.new(string) -> cipher + * + * The string must contain a valid cipher name like "AES-128-CBC" or "3DES". + * + * A list of cipher names is available by calling OpenSSL::Cipher.ciphers. + */ +static VALUE +ossl_cipher_initialize(VALUE self, VALUE str) +{ + EVP_CIPHER_CTX *ctx; + const EVP_CIPHER *cipher; + char *name; + unsigned char key[EVP_MAX_KEY_LENGTH]; + + name = StringValuePtr(str); + GetCipherInit(self, ctx); + if (ctx) { + ossl_raise(rb_eRuntimeError, "Cipher already inititalized!"); + } + AllocCipher(self, ctx); + EVP_CIPHER_CTX_init(ctx); + if (!(cipher = EVP_get_cipherbyname(name))) { + ossl_raise(rb_eRuntimeError, "unsupported cipher algorithm (%s)", name); + } + /* + * The EVP which has EVP_CIPH_RAND_KEY flag (such as DES3) allows + * uninitialized key, but other EVPs (such as AES) does not allow it. + * Calling EVP_CipherUpdate() without initializing key causes SEGV so we + * set the data filled with "\0" as the key by default. + */ + memset(key, 0, EVP_MAX_KEY_LENGTH); + if (EVP_CipherInit_ex(ctx, cipher, NULL, key, NULL, -1) != 1) + ossl_raise(eCipherError, NULL); + + return self; +} + +static VALUE +ossl_cipher_copy(VALUE self, VALUE other) +{ + EVP_CIPHER_CTX *ctx1, *ctx2; + + rb_check_frozen(self); + if (self == other) return self; + + GetCipherInit(self, ctx1); + if (!ctx1) { + AllocCipher(self, ctx1); + } + SafeGetCipher(other, ctx2); + if (EVP_CIPHER_CTX_copy(ctx1, ctx2) != 1) + ossl_raise(eCipherError, NULL); + + return self; +} + +#ifdef HAVE_OBJ_NAME_DO_ALL_SORTED +static void* +add_cipher_name_to_ary(const OBJ_NAME *name, VALUE ary) +{ + rb_ary_push(ary, rb_str_new2(name->name)); + return NULL; +} +#endif + +#ifdef HAVE_OBJ_NAME_DO_ALL_SORTED +/* + * call-seq: + * OpenSSL::Cipher.ciphers -> array[string...] + * + * Returns the names of all available ciphers in an array. + */ +static VALUE +ossl_s_ciphers(VALUE self) +{ + VALUE ary; + + ary = rb_ary_new(); + OBJ_NAME_do_all_sorted(OBJ_NAME_TYPE_CIPHER_METH, + (void(*)(const OBJ_NAME*,void*))add_cipher_name_to_ary, + (void*)ary); + + return ary; +} +#else +#define ossl_s_ciphers rb_f_notimplement +#endif + +/* + * call-seq: + * cipher.reset -> self + * + * Fully resets the internal state of the Cipher. By using this, the same + * Cipher instance may be used several times for encryption or decryption tasks. + * + * Internally calls EVP_CipherInit_ex(ctx, NULL, NULL, NULL, NULL, -1). + */ +static VALUE +ossl_cipher_reset(VALUE self) +{ + EVP_CIPHER_CTX *ctx; + + GetCipher(self, ctx); + if (EVP_CipherInit_ex(ctx, NULL, NULL, NULL, NULL, -1) != 1) + ossl_raise(eCipherError, NULL); + + return self; +} + +static VALUE +ossl_cipher_init(int argc, VALUE *argv, VALUE self, int mode) +{ + EVP_CIPHER_CTX *ctx; + unsigned char key[EVP_MAX_KEY_LENGTH], *p_key = NULL; + unsigned char iv[EVP_MAX_IV_LENGTH], *p_iv = NULL; + VALUE pass, init_v; + + if(rb_scan_args(argc, argv, "02", &pass, &init_v) > 0){ + /* + * oops. this code mistakes salt for IV. + * We deprecated the arguments for this method, but we decided + * keeping this behaviour for backward compatibility. + */ + VALUE cname = rb_class_path(rb_obj_class(self)); + rb_warn("arguments for %"PRIsVALUE"#encrypt and %"PRIsVALUE"#decrypt were deprecated; " + "use %"PRIsVALUE"#pkcs5_keyivgen to derive key and IV", + cname, cname, cname); + StringValue(pass); + GetCipher(self, ctx); + if (NIL_P(init_v)) memcpy(iv, "OpenSSL for Ruby rulez!", sizeof(iv)); + else{ + StringValue(init_v); + if (EVP_MAX_IV_LENGTH > RSTRING_LEN(init_v)) { + memset(iv, 0, EVP_MAX_IV_LENGTH); + memcpy(iv, RSTRING_PTR(init_v), RSTRING_LEN(init_v)); + } + else memcpy(iv, RSTRING_PTR(init_v), sizeof(iv)); + } + EVP_BytesToKey(EVP_CIPHER_CTX_cipher(ctx), EVP_md5(), iv, + (unsigned char *)RSTRING_PTR(pass), RSTRING_LENINT(pass), 1, key, NULL); + p_key = key; + p_iv = iv; + } + else { + GetCipher(self, ctx); + } + if (EVP_CipherInit_ex(ctx, NULL, NULL, p_key, p_iv, mode) != 1) { + ossl_raise(eCipherError, NULL); + } + + return self; +} + +/* + * call-seq: + * cipher.encrypt -> self + * + * Initializes the Cipher for encryption. + * + * Make sure to call Cipher#encrypt or Cipher#decrypt before using any of the + * following methods: + * * [key=, iv=, random_key, random_iv, pkcs5_keyivgen] + * + * Internally calls EVP_CipherInit_ex(ctx, NULL, NULL, NULL, NULL, 1). + */ +static VALUE +ossl_cipher_encrypt(int argc, VALUE *argv, VALUE self) +{ + return ossl_cipher_init(argc, argv, self, 1); +} + +/* + * call-seq: + * cipher.decrypt -> self + * + * Initializes the Cipher for decryption. + * + * Make sure to call Cipher#encrypt or Cipher#decrypt before using any of the + * following methods: + * * [key=, iv=, random_key, random_iv, pkcs5_keyivgen] + * + * Internally calls EVP_CipherInit_ex(ctx, NULL, NULL, NULL, NULL, 0). + */ +static VALUE +ossl_cipher_decrypt(int argc, VALUE *argv, VALUE self) +{ + return ossl_cipher_init(argc, argv, self, 0); +} + +/* + * call-seq: + * cipher.pkcs5_keyivgen(pass [, salt [, iterations [, digest]]] ) -> nil + * + * Generates and sets the key/IV based on a password. + * + * WARNING: This method is only PKCS5 v1.5 compliant when using RC2, RC4-40, + * or DES with MD5 or SHA1. Using anything else (like AES) will generate the + * key/iv using an OpenSSL specific method. This method is deprecated and + * should no longer be used. Use a PKCS5 v2 key generation method from + * OpenSSL::PKCS5 instead. + * + * === Parameters + * +salt+ must be an 8 byte string if provided. + * +iterations+ is a integer with a default of 2048. + * +digest+ is a Digest object that defaults to 'MD5' + * + * A minimum of 1000 iterations is recommended. + * + */ +static VALUE +ossl_cipher_pkcs5_keyivgen(int argc, VALUE *argv, VALUE self) +{ + EVP_CIPHER_CTX *ctx; + const EVP_MD *digest; + VALUE vpass, vsalt, viter, vdigest; + unsigned char key[EVP_MAX_KEY_LENGTH], iv[EVP_MAX_IV_LENGTH], *salt = NULL; + int iter; + + rb_scan_args(argc, argv, "13", &vpass, &vsalt, &viter, &vdigest); + StringValue(vpass); + if(!NIL_P(vsalt)){ + StringValue(vsalt); + if(RSTRING_LEN(vsalt) != PKCS5_SALT_LEN) + ossl_raise(eCipherError, "salt must be an 8-octet string"); + salt = (unsigned char *)RSTRING_PTR(vsalt); + } + iter = NIL_P(viter) ? 2048 : NUM2INT(viter); + digest = NIL_P(vdigest) ? EVP_md5() : GetDigestPtr(vdigest); + GetCipher(self, ctx); + EVP_BytesToKey(EVP_CIPHER_CTX_cipher(ctx), digest, salt, + (unsigned char *)RSTRING_PTR(vpass), RSTRING_LENINT(vpass), iter, key, iv); + if (EVP_CipherInit_ex(ctx, NULL, NULL, key, iv, -1) != 1) + ossl_raise(eCipherError, NULL); + OPENSSL_cleanse(key, sizeof key); + OPENSSL_cleanse(iv, sizeof iv); + + return Qnil; +} + +/* + * call-seq: + * cipher.update(data [, buffer]) -> string or buffer + * + * Encrypts data in a streaming fashion. Hand consecutive blocks of data + * to the +update+ method in order to encrypt it. Returns the encrypted + * data chunk. When done, the output of Cipher#final should be additionally + * added to the result. + * + * === Parameters + * +data+ is a nonempty string. + * +buffer+ is an optional string to store the result. + */ +static VALUE +ossl_cipher_update(int argc, VALUE *argv, VALUE self) +{ + EVP_CIPHER_CTX *ctx; + unsigned char *in; + int in_len, out_len; + VALUE data, str; + + rb_scan_args(argc, argv, "11", &data, &str); + + StringValue(data); + in = (unsigned char *)RSTRING_PTR(data); + if ((in_len = RSTRING_LENINT(data)) == 0) + ossl_raise(rb_eArgError, "data must not be empty"); + GetCipher(self, ctx); + out_len = in_len+EVP_CIPHER_CTX_block_size(ctx); + + if (NIL_P(str)) { + str = rb_str_new(0, out_len); + } else { + StringValue(str); + rb_str_resize(str, out_len); + } + + if (!EVP_CipherUpdate(ctx, (unsigned char *)RSTRING_PTR(str), &out_len, in, in_len)) + ossl_raise(eCipherError, NULL); + assert(out_len < RSTRING_LEN(str)); + rb_str_set_len(str, out_len); + + return str; +} + +/* + * call-seq: + * cipher.final -> string + * + * Returns the remaining data held in the cipher object. Further calls to + * Cipher#update or Cipher#final will return garbage. This call should always + * be made as the last call of an encryption or decryption operation, after + * after having fed the entire plaintext or ciphertext to the Cipher instance. + * + * If an authenticated cipher was used, a CipherError is raised if the tag + * could not be authenticated successfully. Only call this method after + * setting the authentication tag and passing the entire contents of the + * ciphertext into the cipher. + */ +static VALUE +ossl_cipher_final(VALUE self) +{ + EVP_CIPHER_CTX *ctx; + int out_len; + VALUE str; + + GetCipher(self, ctx); + str = rb_str_new(0, EVP_CIPHER_CTX_block_size(ctx)); + if (!EVP_CipherFinal_ex(ctx, (unsigned char *)RSTRING_PTR(str), &out_len)) + ossl_raise(eCipherError, NULL); + assert(out_len <= RSTRING_LEN(str)); + rb_str_set_len(str, out_len); + + return str; +} + +/* + * call-seq: + * cipher.name -> string + * + * Returns the name of the cipher which may differ slightly from the original + * name provided. + */ +static VALUE +ossl_cipher_name(VALUE self) +{ + EVP_CIPHER_CTX *ctx; + + GetCipher(self, ctx); + + return rb_str_new2(EVP_CIPHER_name(EVP_CIPHER_CTX_cipher(ctx))); +} + +/* + * call-seq: + * cipher.key = string -> string + * + * Sets the cipher key. To generate a key, you should either use a secure + * random byte string or, if the key is to be derived from a password, you + * should rely on PBKDF2 functionality provided by OpenSSL::PKCS5. To + * generate a secure random-based key, Cipher#random_key may be used. + * + * Only call this method after calling Cipher#encrypt or Cipher#decrypt. + */ +static VALUE +ossl_cipher_set_key(VALUE self, VALUE key) +{ + EVP_CIPHER_CTX *ctx; + + StringValue(key); + GetCipher(self, ctx); + + if (RSTRING_LEN(key) < EVP_CIPHER_CTX_key_length(ctx)) + ossl_raise(eCipherError, "key length too short"); + + if (EVP_CipherInit_ex(ctx, NULL, NULL, (unsigned char *)RSTRING_PTR(key), NULL, -1) != 1) + ossl_raise(eCipherError, NULL); + + return key; +} + +/* + * call-seq: + * cipher.iv = string -> string + * + * Sets the cipher IV. Please note that since you should never be using ECB + * mode, an IV is always explicitly required and should be set prior to + * encryption. The IV itself can be safely transmitted in public, but it + * should be unpredictable to prevent certain kinds of attacks. You may use + * Cipher#random_iv to create a secure random IV. + * + * Only call this method after calling Cipher#encrypt or Cipher#decrypt. + * + * If not explicitly set, the OpenSSL default of an all-zeroes ("\\0") IV is + * used. + */ +static VALUE +ossl_cipher_set_iv(VALUE self, VALUE iv) +{ + EVP_CIPHER_CTX *ctx; + + StringValue(iv); + GetCipher(self, ctx); + + if (RSTRING_LEN(iv) < EVP_CIPHER_CTX_iv_length(ctx)) + ossl_raise(eCipherError, "iv length too short"); + + if (EVP_CipherInit_ex(ctx, NULL, NULL, NULL, (unsigned char *)RSTRING_PTR(iv), -1) != 1) + ossl_raise(eCipherError, NULL); + + return iv; +} + +#ifdef HAVE_AUTHENTICATED_ENCRYPTION +/* + * call-seq: + * cipher.auth_data = string -> string + * + * Sets the cipher's additional authenticated data. This field must be + * set when using AEAD cipher modes such as GCM or CCM. If no associated + * data shall be used, this method must *still* be called with a value of "". + * The contents of this field should be non-sensitive data which will be + * added to the ciphertext to generate the authentication tag which validates + * the contents of the ciphertext. + * + * The AAD must be set prior to encryption or decryption. In encryption mode, + * it must be set after calling Cipher#encrypt and setting Cipher#key= and + * Cipher#iv=. When decrypting, the authenticated data must be set after key, + * iv and especially *after* the authentication tag has been set. I.e. set it + * only after calling Cipher#decrypt, Cipher#key=, Cipher#iv= and + * Cipher#auth_tag= first. + */ +static VALUE +ossl_cipher_set_auth_data(VALUE self, VALUE data) +{ + EVP_CIPHER_CTX *ctx; + unsigned char *in; + int in_len; + int out_len; + + StringValue(data); + + in = (unsigned char *) RSTRING_PTR(data); + in_len = RSTRING_LENINT(data); + + GetCipher(self, ctx); + + if (!EVP_CipherUpdate(ctx, NULL, &out_len, in, in_len)) + ossl_raise(eCipherError, "couldn't set additional authenticated data"); + + return data; +} + +#define ossl_is_gcm(nid) (nid) == NID_aes_128_gcm || \ + (nid) == NID_aes_192_gcm || \ + (nid) == NID_aes_256_gcm + +static VALUE +ossl_get_gcm_auth_tag(EVP_CIPHER_CTX *ctx, int len) +{ + unsigned char *tag; + VALUE ret; + + tag = ALLOC_N(unsigned char, len); + + if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, len, tag)) + ossl_raise(eCipherError, "retrieving the authentication tag failed"); + + ret = rb_str_new((const char *) tag, len); + xfree(tag); + return ret; +} + +/* + * call-seq: + * cipher.auth_tag([ tag_len ] -> string + * + * Gets the authentication tag generated by Authenticated Encryption Cipher + * modes (GCM for example). This tag may be stored along with the ciphertext, + * then set on the decryption cipher to authenticate the contents of the + * ciphertext against changes. If the optional integer parameter +tag_len+ is + * given, the returned tag will be +tag_len+ bytes long. If the parameter is + * omitted, the maximum length of 16 bytes will be returned. For maximum + * security, the default of 16 bytes should be chosen. + * + * The tag may only be retrieved after calling Cipher#final. + */ +static VALUE +ossl_cipher_get_auth_tag(int argc, VALUE *argv, VALUE self) +{ + VALUE vtag_len; + EVP_CIPHER_CTX *ctx; + int nid, tag_len; + + if (rb_scan_args(argc, argv, "01", &vtag_len) == 0) { + tag_len = 16; + } else { + tag_len = NUM2INT(vtag_len); + } + + GetCipher(self, ctx); + nid = EVP_CIPHER_CTX_nid(ctx); + + if (ossl_is_gcm(nid)) { + return ossl_get_gcm_auth_tag(ctx, tag_len); + } else { + ossl_raise(eCipherError, "authentication tag not supported by this cipher"); + return Qnil; /* dummy */ + } +} + +static inline void +ossl_set_gcm_auth_tag(EVP_CIPHER_CTX *ctx, unsigned char *tag, int tag_len) +{ + if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, tag_len, tag)) + ossl_raise(eCipherError, "unable to set GCM tag"); +} + +/* + * call-seq: + * cipher.auth_tag = string -> string + * + * Sets the authentication tag to verify the contents of the + * ciphertext. The tag must be set after calling Cipher#decrypt, + * Cipher#key= and Cipher#iv=, but before assigning the associated + * authenticated data using Cipher#auth_data= and of course, before + * decrypting any of the ciphertext. After all decryption is + * performed, the tag is verified automatically in the call to + * Cipher#final. + */ +static VALUE +ossl_cipher_set_auth_tag(VALUE self, VALUE vtag) +{ + EVP_CIPHER_CTX *ctx; + int nid; + unsigned char *tag; + int tag_len; + + StringValue(vtag); + tag = (unsigned char *) RSTRING_PTR(vtag); + tag_len = RSTRING_LENINT(vtag); + + GetCipher(self, ctx); + nid = EVP_CIPHER_CTX_nid(ctx); + + if (ossl_is_gcm(nid)) { + ossl_set_gcm_auth_tag(ctx, tag, tag_len); + } else { + ossl_raise(eCipherError, "authentication tag not supported by this cipher"); + } + + return vtag; +} + +/* + * call-seq: + * cipher.authenticated? -> boolean + * + * Indicated whether this Cipher instance uses an Authenticated Encryption + * mode. + */ +static VALUE +ossl_cipher_is_authenticated(VALUE self) +{ + EVP_CIPHER_CTX *ctx; + int nid; + + GetCipher(self, ctx); + nid = EVP_CIPHER_CTX_nid(ctx); + + if (ossl_is_gcm(nid)) { + return Qtrue; + } else { + return Qfalse; + } +} +#else +#define ossl_cipher_set_auth_data rb_f_notimplement +#define ossl_cipher_get_auth_tag rb_f_notimplement +#define ossl_cipher_set_auth_tag rb_f_notimplement +#define ossl_cipher_is_authenticated rb_f_notimplement +#endif + +/* + * call-seq: + * cipher.key_len = integer -> integer + * + * Sets the key length of the cipher. If the cipher is a fixed length cipher + * then attempting to set the key length to any value other than the fixed + * value is an error. + * + * Under normal circumstances you do not need to call this method (and probably shouldn't). + * + * See EVP_CIPHER_CTX_set_key_length for further information. + */ +static VALUE +ossl_cipher_set_key_length(VALUE self, VALUE key_length) +{ + int len = NUM2INT(key_length); + EVP_CIPHER_CTX *ctx; + + GetCipher(self, ctx); + if (EVP_CIPHER_CTX_set_key_length(ctx, len) != 1) + ossl_raise(eCipherError, NULL); + + return key_length; +} + +#if defined(HAVE_EVP_CIPHER_CTX_SET_PADDING) +/* + * call-seq: + * cipher.padding = integer -> integer + * + * Enables or disables padding. By default encryption operations are padded using standard block padding and the + * padding is checked and removed when decrypting. If the pad parameter is zero then no padding is performed, the + * total amount of data encrypted or decrypted must then be a multiple of the block size or an error will occur. + * + * See EVP_CIPHER_CTX_set_padding for further information. + */ +static VALUE +ossl_cipher_set_padding(VALUE self, VALUE padding) +{ + EVP_CIPHER_CTX *ctx; + int pad = NUM2INT(padding); + + GetCipher(self, ctx); + if (EVP_CIPHER_CTX_set_padding(ctx, pad) != 1) + ossl_raise(eCipherError, NULL); + return padding; +} +#else +#define ossl_cipher_set_padding rb_f_notimplement +#endif + +#define CIPHER_0ARG_INT(func) \ + static VALUE \ + ossl_cipher_##func(VALUE self) \ + { \ + EVP_CIPHER_CTX *ctx; \ + GetCipher(self, ctx); \ + return INT2NUM(EVP_CIPHER_##func(EVP_CIPHER_CTX_cipher(ctx))); \ + } + +/* + * call-seq: + * cipher.key_len -> integer + * + * Returns the key length in bytes of the Cipher. + */ +CIPHER_0ARG_INT(key_length) +/* + * call-seq: + * cipher.iv_len -> integer + * + * Returns the expected length in bytes for an IV for this Cipher. + */ +CIPHER_0ARG_INT(iv_length) +/* + * call-seq: + * cipher.block_size -> integer + * + * Returns the size in bytes of the blocks on which this Cipher operates on. + */ +CIPHER_0ARG_INT(block_size) + +/* + * INIT + */ +void +Init_ossl_cipher(void) +{ +#if 0 + mOSSL = rb_define_module("OpenSSL"); /* let rdoc know about mOSSL */ +#endif + + /* Document-class: OpenSSL::Cipher + * + * Provides symmetric algorithms for encryption and decryption. The + * algorithms that are available depend on the particular version + * of OpenSSL that is installed. + * + * === Listing all supported algorithms + * + * A list of supported algorithms can be obtained by + * + * puts OpenSSL::Cipher.ciphers + * + * === Instantiating a Cipher + * + * There are several ways to create a Cipher instance. Generally, a + * Cipher algorithm is categorized by its name, the key length in bits + * and the cipher mode to be used. The most generic way to create a + * Cipher is the following + * + * cipher = OpenSSL::Cipher.new('<name>-<key length>-<mode>') + * + * That is, a string consisting of the hyphenated concatenation of the + * individual components name, key length and mode. Either all uppercase + * or all lowercase strings may be used, for example: + * + * cipher = OpenSSL::Cipher.new('AES-128-CBC') + * + * For each algorithm supported, there is a class defined under the + * Cipher class that goes by the name of the cipher, e.g. to obtain an + * instance of AES, you could also use + * + * # these are equivalent + * cipher = OpenSSL::Cipher::AES.new(128, :CBC) + * cipher = OpenSSL::Cipher::AES.new(128, 'CBC') + * cipher = OpenSSL::Cipher::AES.new('128-CBC') + * + * Finally, due to its wide-spread use, there are also extra classes + * defined for the different key sizes of AES + * + * cipher = OpenSSL::Cipher::AES128.new(:CBC) + * cipher = OpenSSL::Cipher::AES192.new(:CBC) + * cipher = OpenSSL::Cipher::AES256.new(:CBC) + * + * === Choosing either encryption or decryption mode + * + * Encryption and decryption are often very similar operations for + * symmetric algorithms, this is reflected by not having to choose + * different classes for either operation, both can be done using the + * same class. Still, after obtaining a Cipher instance, we need to + * tell the instance what it is that we intend to do with it, so we + * need to call either + * + * cipher.encrypt + * + * or + * + * cipher.decrypt + * + * on the Cipher instance. This should be the first call after creating + * the instance, otherwise configuration that has already been set could + * get lost in the process. + * + * === Choosing a key + * + * Symmetric encryption requires a key that is the same for the encrypting + * and for the decrypting party and after initial key establishment should + * be kept as private information. There are a lot of ways to create + * insecure keys, the most notable is to simply take a password as the key + * without processing the password further. A simple and secure way to + * create a key for a particular Cipher is + * + * cipher = OpenSSL::AES256.new(:CFB) + * cipher.encrypt + * key = cipher.random_key # also sets the generated key on the Cipher + * + * If you absolutely need to use passwords as encryption keys, you + * should use Password-Based Key Derivation Function 2 (PBKDF2) by + * generating the key with the help of the functionality provided by + * OpenSSL::PKCS5.pbkdf2_hmac_sha1 or OpenSSL::PKCS5.pbkdf2_hmac. + * + * Although there is Cipher#pkcs5_keyivgen, its use is deprecated and + * it should only be used in legacy applications because it does not use + * the newer PKCS#5 v2 algorithms. + * + * === Choosing an IV + * + * The cipher modes CBC, CFB, OFB and CTR all need an "initialization + * vector", or short, IV. ECB mode is the only mode that does not require + * an IV, but there is almost no legitimate use case for this mode + * because of the fact that it does not sufficiently hide plaintext + * patterns. Therefore + * + * <b>You should never use ECB mode unless you are absolutely sure that + * you absolutely need it</b> + * + * Because of this, you will end up with a mode that explicitly requires + * an IV in any case. Note that for backwards compatibility reasons, + * setting an IV is not explicitly mandated by the Cipher API. If not + * set, OpenSSL itself defaults to an all-zeroes IV ("\\0", not the + * character). Although the IV can be seen as public information, i.e. + * it may be transmitted in public once generated, it should still stay + * unpredictable to prevent certain kinds of attacks. Therefore, ideally + * + * <b>Always create a secure random IV for every encryption of your + * Cipher</b> + * + * A new, random IV should be created for every encryption of data. Think + * of the IV as a nonce (number used once) - it's public but random and + * unpredictable. A secure random IV can be created as follows + * + * cipher = ... + * cipher.encrypt + * key = cipher.random_key + * iv = cipher.random_iv # also sets the generated IV on the Cipher + * + * Although the key is generally a random value, too, it is a bad choice + * as an IV. There are elaborate ways how an attacker can take advantage + * of such an IV. As a general rule of thumb, exposing the key directly + * or indirectly should be avoided at all cost and exceptions only be + * made with good reason. + * + * === Calling Cipher#final + * + * ECB (which should not be used) and CBC are both block-based modes. + * This means that unlike for the other streaming-based modes, they + * operate on fixed-size blocks of data, and therefore they require a + * "finalization" step to produce or correctly decrypt the last block of + * data by appropriately handling some form of padding. Therefore it is + * essential to add the output of OpenSSL::Cipher#final to your + * encryption/decryption buffer or you will end up with decryption errors + * or truncated data. + * + * Although this is not really necessary for streaming-mode ciphers, it is + * still recommended to apply the same pattern of adding the output of + * Cipher#final there as well - it also enables you to switch between + * modes more easily in the future. + * + * === Encrypting and decrypting some data + * + * data = "Very, very confidential data" + * + * cipher = OpenSSL::Cipher::AES.new(128, :CBC) + * cipher.encrypt + * key = cipher.random_key + * iv = cipher.random_iv + * + * encrypted = cipher.update(data) + cipher.final + * ... + * decipher = OpenSSL::Cipher::AES.new(128, :CBC) + * decipher.decrypt + * decipher.key = key + * decipher.iv = iv + * + * plain = decipher.update(encrypted) + decipher.final + * + * puts data == plain #=> true + * + * === Authenticated Encryption and Associated Data (AEAD) + * + * If the OpenSSL version used supports it, an Authenticated Encryption + * mode (such as GCM or CCM) should always be preferred over any + * unauthenticated mode. Currently, OpenSSL supports AE only in combination + * with Associated Data (AEAD) where additional associated data is included + * in the encryption process to compute a tag at the end of the encryption. + * This tag will also be used in the decryption process and by verifying + * its validity, the authenticity of a given ciphertext is established. + * + * This is superior to unauthenticated modes in that it allows to detect + * if somebody effectively changed the ciphertext after it had been + * encrypted. This prevents malicious modifications of the ciphertext that + * could otherwise be exploited to modify ciphertexts in ways beneficial to + * potential attackers. + * + * If no associated data is needed for encryption and later decryption, + * the OpenSSL library still requires a value to be set - "" may be used in + * case none is available. An example using the GCM (Galois Counter Mode): + * + * cipher = OpenSSL::Cipher::AES.new(128, :GCM) + * cipher.encrypt + * key = cipher.random_key + * iv = cipher.random_iv + * cipher.auth_data = "" + * + * encrypted = cipher.update(data) + cipher.final + * tag = cipher.auth_tag + * + * decipher = OpenSSL::Cipher::AES.new(128, :GCM) + * decipher.decrypt + * decipher.key = key + * decipher.iv = iv + * decipher.auth_tag = tag + * decipher.auth_data = "" + * + * plain = decipher.update(encrypted) + decipher.final + * + * puts data == plain #=> true + */ + cCipher = rb_define_class_under(mOSSL, "Cipher", rb_cObject); + eCipherError = rb_define_class_under(cCipher, "CipherError", eOSSLError); + + rb_define_alloc_func(cCipher, ossl_cipher_alloc); + rb_define_copy_func(cCipher, ossl_cipher_copy); + rb_define_module_function(cCipher, "ciphers", ossl_s_ciphers, 0); + rb_define_method(cCipher, "initialize", ossl_cipher_initialize, 1); + rb_define_method(cCipher, "reset", ossl_cipher_reset, 0); + rb_define_method(cCipher, "encrypt", ossl_cipher_encrypt, -1); + rb_define_method(cCipher, "decrypt", ossl_cipher_decrypt, -1); + rb_define_method(cCipher, "pkcs5_keyivgen", ossl_cipher_pkcs5_keyivgen, -1); + rb_define_method(cCipher, "update", ossl_cipher_update, -1); + rb_define_method(cCipher, "final", ossl_cipher_final, 0); + rb_define_method(cCipher, "name", ossl_cipher_name, 0); + rb_define_method(cCipher, "key=", ossl_cipher_set_key, 1); + rb_define_method(cCipher, "auth_data=", ossl_cipher_set_auth_data, 1); + rb_define_method(cCipher, "auth_tag=", ossl_cipher_set_auth_tag, 1); + rb_define_method(cCipher, "auth_tag", ossl_cipher_get_auth_tag, -1); + rb_define_method(cCipher, "authenticated?", ossl_cipher_is_authenticated, 0); + rb_define_method(cCipher, "key_len=", ossl_cipher_set_key_length, 1); + rb_define_method(cCipher, "key_len", ossl_cipher_key_length, 0); + rb_define_method(cCipher, "iv=", ossl_cipher_set_iv, 1); + rb_define_method(cCipher, "iv_len", ossl_cipher_iv_length, 0); + rb_define_method(cCipher, "block_size", ossl_cipher_block_size, 0); + rb_define_method(cCipher, "padding=", ossl_cipher_set_padding, 1); +} + diff --git a/ext/openssl/ossl_cipher.h b/ext/openssl/ossl_cipher.h new file mode 100644 index 00000000..bed4fa85 --- /dev/null +++ b/ext/openssl/ossl_cipher.h @@ -0,0 +1,22 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2001-2002 Michal Rokos <m.rokos@sh.cvut.cz> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#if !defined(_OSSL_CIPHER_H_) +#define _OSSL_CIPHER_H_ + +extern VALUE cCipher; +extern VALUE eCipherError; + +const EVP_CIPHER *GetCipherPtr(VALUE); +VALUE ossl_cipher_new(const EVP_CIPHER *); +void Init_ossl_cipher(void); + +#endif /* _OSSL_CIPHER_H_ */ + diff --git a/ext/openssl/ossl_config.c b/ext/openssl/ossl_config.c new file mode 100644 index 00000000..74a52f71 --- /dev/null +++ b/ext/openssl/ossl_config.c @@ -0,0 +1,83 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2001-2002 Michal Rokos <m.rokos@sh.cvut.cz> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#include "ossl.h" + + +/* + * Classes + */ +VALUE cConfig; +/* Document-class: OpenSSL::ConfigError + * + * General error for openssl library configuration files. Including formatting, + * parsing errors, etc. + */ +VALUE eConfigError; + +/* + * Public + */ + +/* + * GetConfigPtr is a public C-level function for getting OpenSSL CONF struct + * from an OpenSSL::Config(eConfig) instance. We decided to implement + * OpenSSL::Config in Ruby level but we need to pass native CONF struct for + * some OpenSSL features such as X509V3_EXT_*. + */ +CONF * +GetConfigPtr(VALUE obj) +{ + CONF *conf; + VALUE str; + BIO *bio; + long eline = -1; + + OSSL_Check_Kind(obj, cConfig); + str = rb_funcall(obj, rb_intern("to_s"), 0); + bio = ossl_obj2bio(str); + conf = NCONF_new(NULL); + if(!conf){ + BIO_free(bio); + ossl_raise(eConfigError, NULL); + } + if(!NCONF_load_bio(conf, bio, &eline)){ + BIO_free(bio); + NCONF_free(conf); + if (eline <= 0) ossl_raise(eConfigError, "wrong config format"); + else ossl_raise(eConfigError, "error in line %d", eline); + ossl_raise(eConfigError, NULL); + } + BIO_free(bio); + + return conf; +} + +/* Document-const: DEFAULT_CONFIG_FILE + * + * The default system configuration file for openssl + */ + +/* + * INIT + */ +void +Init_ossl_config(void) +{ + char *default_config_file; + eConfigError = rb_define_class_under(mOSSL, "ConfigError", eOSSLError); + cConfig = rb_define_class_under(mOSSL, "Config", rb_cObject); + + default_config_file = CONF_get1_default_config_file(); + rb_define_const(cConfig, "DEFAULT_CONFIG_FILE", + rb_str_new2(default_config_file)); + OPENSSL_free(default_config_file); + /* methods are defined by openssl/config.rb */ +} diff --git a/ext/openssl/ossl_config.h b/ext/openssl/ossl_config.h new file mode 100644 index 00000000..cb226b27 --- /dev/null +++ b/ext/openssl/ossl_config.h @@ -0,0 +1,22 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2001-2002 Michal Rokos <m.rokos@sh.cvut.cz> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#if !defined(_OSSL_CONFIG_H_) +#define _OSSL_CONFIG_H_ + +extern VALUE cConfig; +extern VALUE eConfigError; + +CONF* GetConfigPtr(VALUE obj); +CONF* DupConfigPtr(VALUE obj); +void Init_ossl_config(void); + +#endif /* _OSSL_CONFIG_H_ */ + diff --git a/ext/openssl/ossl_digest.c b/ext/openssl/ossl_digest.c new file mode 100644 index 00000000..bf618c83 --- /dev/null +++ b/ext/openssl/ossl_digest.c @@ -0,0 +1,438 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2001-2002 Michal Rokos <m.rokos@sh.cvut.cz> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#include "ossl.h" + +#define GetDigest(obj, ctx) do { \ + Data_Get_Struct((obj), EVP_MD_CTX, (ctx)); \ + if (!(ctx)) { \ + ossl_raise(rb_eRuntimeError, "Digest CTX wasn't initialized!"); \ + } \ +} while (0) +#define SafeGetDigest(obj, ctx) do { \ + OSSL_Check_Kind((obj), cDigest); \ + GetDigest((obj), (ctx)); \ +} while (0) + +/* + * Classes + */ +VALUE cDigest; +VALUE eDigestError; + +static VALUE ossl_digest_alloc(VALUE klass); + +/* + * Public + */ +const EVP_MD * +GetDigestPtr(VALUE obj) +{ + const EVP_MD *md; + ASN1_OBJECT *oid = NULL; + + if (RB_TYPE_P(obj, T_STRING)) { + const char *name = StringValueCStr(obj); + + md = EVP_get_digestbyname(name); + if (!md) { + oid = OBJ_txt2obj(name, 0); + md = EVP_get_digestbyobj(oid); + ASN1_OBJECT_free(oid); + } + if(!md) + ossl_raise(rb_eRuntimeError, "Unsupported digest algorithm (%s).", name); + } else { + EVP_MD_CTX *ctx; + + SafeGetDigest(obj, ctx); + + md = EVP_MD_CTX_md(ctx); + } + + return md; +} + +VALUE +ossl_digest_new(const EVP_MD *md) +{ + VALUE ret; + EVP_MD_CTX *ctx; + + ret = ossl_digest_alloc(cDigest); + GetDigest(ret, ctx); + if (EVP_DigestInit_ex(ctx, md, NULL) != 1) { + ossl_raise(eDigestError, "Digest initialization failed."); + } + + return ret; +} + +/* + * Private + */ +static VALUE +ossl_digest_alloc(VALUE klass) +{ + EVP_MD_CTX *ctx; + VALUE obj; + + ctx = EVP_MD_CTX_create(); + if (ctx == NULL) + ossl_raise(rb_eRuntimeError, "EVP_MD_CTX_create() failed"); + obj = Data_Wrap_Struct(klass, 0, EVP_MD_CTX_destroy, ctx); + + return obj; +} + +VALUE ossl_digest_update(VALUE, VALUE); + +/* + * call-seq: + * Digest.new(string [, data]) -> Digest + * + * Creates a Digest instance based on +string+, which is either the ln + * (long name) or sn (short name) of a supported digest algorithm. + * If +data+ (a +String+) is given, it is used as the initial input to the + * Digest instance, i.e. + * digest = OpenSSL::Digest.new('sha256', 'digestdata') + * is equal to + * digest = OpenSSL::Digest.new('sha256') + * digest.update('digestdata') + * + * === Example + * digest = OpenSSL::Digest.new('sha1') + * + * + */ +static VALUE +ossl_digest_initialize(int argc, VALUE *argv, VALUE self) +{ + EVP_MD_CTX *ctx; + const EVP_MD *md; + VALUE type, data; + + rb_scan_args(argc, argv, "11", &type, &data); + md = GetDigestPtr(type); + if (!NIL_P(data)) StringValue(data); + + GetDigest(self, ctx); + if (EVP_DigestInit_ex(ctx, md, NULL) != 1) { + ossl_raise(eDigestError, "Digest initialization failed."); + } + + if (!NIL_P(data)) return ossl_digest_update(self, data); + return self; +} + +static VALUE +ossl_digest_copy(VALUE self, VALUE other) +{ + EVP_MD_CTX *ctx1, *ctx2; + + rb_check_frozen(self); + if (self == other) return self; + + GetDigest(self, ctx1); + SafeGetDigest(other, ctx2); + + if (!EVP_MD_CTX_copy(ctx1, ctx2)) { + ossl_raise(eDigestError, NULL); + } + return self; +} + +/* + * call-seq: + * digest.reset -> self + * + * Resets the Digest in the sense that any Digest#update that has been + * performed is abandoned and the Digest is set to its initial state again. + * + */ +static VALUE +ossl_digest_reset(VALUE self) +{ + EVP_MD_CTX *ctx; + + GetDigest(self, ctx); + if (EVP_DigestInit_ex(ctx, EVP_MD_CTX_md(ctx), NULL) != 1) { + ossl_raise(eDigestError, "Digest initialization failed."); + } + + return self; +} + +/* + * call-seq: + * digest.update(string) -> aString + * + * Not every message digest can be computed in one single pass. If a message + * digest is to be computed from several subsequent sources, then each may + * be passed individually to the Digest instance. + * + * === Example + * digest = OpenSSL::Digest::SHA256.new + * digest.update('First input') + * digest << 'Second input' # equivalent to digest.update('Second input') + * result = digest.digest + * + */ +VALUE +ossl_digest_update(VALUE self, VALUE data) +{ + EVP_MD_CTX *ctx; + + StringValue(data); + GetDigest(self, ctx); + EVP_DigestUpdate(ctx, RSTRING_PTR(data), RSTRING_LEN(data)); + + return self; +} + +/* + * call-seq: + * digest.finish -> aString + * + */ +static VALUE +ossl_digest_finish(int argc, VALUE *argv, VALUE self) +{ + EVP_MD_CTX *ctx; + VALUE str; + + rb_scan_args(argc, argv, "01", &str); + + GetDigest(self, ctx); + + if (NIL_P(str)) { + str = rb_str_new(NULL, EVP_MD_CTX_size(ctx)); + } else { + StringValue(str); + rb_str_resize(str, EVP_MD_CTX_size(ctx)); + } + + EVP_DigestFinal_ex(ctx, (unsigned char *)RSTRING_PTR(str), NULL); + + return str; +} + +/* + * call-seq: + * digest.name -> string + * + * Returns the sn of this Digest instance. + * + * === Example + * digest = OpenSSL::Digest::SHA512.new + * puts digest.name # => SHA512 + * + */ +static VALUE +ossl_digest_name(VALUE self) +{ + EVP_MD_CTX *ctx; + + GetDigest(self, ctx); + + return rb_str_new2(EVP_MD_name(EVP_MD_CTX_md(ctx))); +} + +/* + * call-seq: + * digest.digest_length -> integer + * + * Returns the output size of the digest, i.e. the length in bytes of the + * final message digest result. + * + * === Example + * digest = OpenSSL::Digest::SHA1.new + * puts digest.digest_length # => 20 + * + */ +static VALUE +ossl_digest_size(VALUE self) +{ + EVP_MD_CTX *ctx; + + GetDigest(self, ctx); + + return INT2NUM(EVP_MD_CTX_size(ctx)); +} + +/* + * call-seq: + * digest.block_length -> integer + * + * Returns the block length of the digest algorithm, i.e. the length in bytes + * of an individual block. Most modern algorithms partition a message to be + * digested into a sequence of fix-sized blocks that are processed + * consecutively. + * + * === Example + * digest = OpenSSL::Digest::SHA1.new + * puts digest.block_length # => 64 + */ +static VALUE +ossl_digest_block_length(VALUE self) +{ + EVP_MD_CTX *ctx; + + GetDigest(self, ctx); + + return INT2NUM(EVP_MD_CTX_block_size(ctx)); +} + +/* + * INIT + */ +void +Init_ossl_digest(void) +{ + rb_require("digest"); + +#if 0 + mOSSL = rb_define_module("OpenSSL"); /* let rdoc know about mOSSL */ +#endif + + /* Document-class: OpenSSL::Digest + * + * OpenSSL::Digest allows you to compute message digests (sometimes + * interchangeably called "hashes") of arbitrary data that are + * cryptographically secure, i.e. a Digest implements a secure one-way + * function. + * + * One-way functions offer some useful properties. E.g. given two + * distinct inputs the probability that both yield the same output + * is highly unlikely. Combined with the fact that every message digest + * algorithm has a fixed-length output of just a few bytes, digests are + * often used to create unique identifiers for arbitrary data. A common + * example is the creation of a unique id for binary documents that are + * stored in a database. + * + * Another useful characteristic of one-way functions (and thus the name) + * is that given a digest there is no indication about the original + * data that produced it, i.e. the only way to identify the original input + * is to "brute-force" through every possible combination of inputs. + * + * These characteristics make one-way functions also ideal companions + * for public key signature algorithms: instead of signing an entire + * document, first a hash of the document is produced with a considerably + * faster message digest algorithm and only the few bytes of its output + * need to be signed using the slower public key algorithm. To validate + * the integrity of a signed document, it suffices to re-compute the hash + * and verify that it is equal to that in the signature. + * + * Among the supported message digest algorithms are: + * * SHA, SHA1, SHA224, SHA256, SHA384 and SHA512 + * * MD2, MD4, MDC2 and MD5 + * * RIPEMD160 + * * DSS, DSS1 (Pseudo algorithms to be used for DSA signatures. DSS is + * equal to SHA and DSS1 is equal to SHA1) + * + * For each of these algorithms, there is a sub-class of Digest that + * can be instantiated as simply as e.g. + * + * digest = OpenSSL::Digest::SHA1.new + * + * === Mapping between Digest class and sn/ln + * + * The sn (short names) and ln (long names) are defined in + * <openssl/object.h> and <openssl/obj_mac.h>. They are textual + * representations of ASN.1 OBJECT IDENTIFIERs. Each supported digest + * algorithm has an OBJECT IDENTIFIER associated to it and those again + * have short/long names assigned to them. + * E.g. the OBJECT IDENTIFIER for SHA-1 is 1.3.14.3.2.26 and its + * sn is "SHA1" and its ln is "sha1". + * ==== MD2 + * * sn: MD2 + * * ln: md2 + * ==== MD4 + * * sn: MD4 + * * ln: md4 + * ==== MD5 + * * sn: MD5 + * * ln: md5 + * ==== SHA + * * sn: SHA + * * ln: SHA + * ==== SHA-1 + * * sn: SHA1 + * * ln: sha1 + * ==== SHA-224 + * * sn: SHA224 + * * ln: sha224 + * ==== SHA-256 + * * sn: SHA256 + * * ln: sha256 + * ==== SHA-384 + * * sn: SHA384 + * * ln: sha384 + * ==== SHA-512 + * * sn: SHA512 + * * ln: sha512 + * + * "Breaking" a message digest algorithm means defying its one-way + * function characteristics, i.e. producing a collision or finding a way + * to get to the original data by means that are more efficient than + * brute-forcing etc. Most of the supported digest algorithms can be + * considered broken in this sense, even the very popular MD5 and SHA1 + * algorithms. Should security be your highest concern, then you should + * probably rely on SHA224, SHA256, SHA384 or SHA512. + * + * === Hashing a file + * + * data = File.read('document') + * sha256 = OpenSSL::Digest::SHA256.new + * digest = sha256.digest(data) + * + * === Hashing several pieces of data at once + * + * data1 = File.read('file1') + * data2 = File.read('file2') + * data3 = File.read('file3') + * sha256 = OpenSSL::Digest::SHA256.new + * sha256 << data1 + * sha256 << data2 + * sha256 << data3 + * digest = sha256.digest + * + * === Reuse a Digest instance + * + * data1 = File.read('file1') + * sha256 = OpenSSL::Digest::SHA256.new + * digest1 = sha256.digest(data1) + * + * data2 = File.read('file2') + * sha256.reset + * digest2 = sha256.digest(data2) + * + */ + cDigest = rb_define_class_under(mOSSL, "Digest", rb_path2class("Digest::Class")); + /* Document-class: OpenSSL::Digest::DigestError + * + * Generic Exception class that is raised if an error occurs during a + * Digest operation. + */ + eDigestError = rb_define_class_under(cDigest, "DigestError", eOSSLError); + + rb_define_alloc_func(cDigest, ossl_digest_alloc); + + rb_define_method(cDigest, "initialize", ossl_digest_initialize, -1); + rb_define_copy_func(cDigest, ossl_digest_copy); + rb_define_method(cDigest, "reset", ossl_digest_reset, 0); + rb_define_method(cDigest, "update", ossl_digest_update, 1); + rb_define_alias(cDigest, "<<", "update"); + rb_define_private_method(cDigest, "finish", ossl_digest_finish, -1); + rb_define_method(cDigest, "digest_length", ossl_digest_size, 0); + rb_define_method(cDigest, "block_length", ossl_digest_block_length, 0); + + rb_define_method(cDigest, "name", ossl_digest_name, 0); +} diff --git a/ext/openssl/ossl_digest.h b/ext/openssl/ossl_digest.h new file mode 100644 index 00000000..8cc5b1bc --- /dev/null +++ b/ext/openssl/ossl_digest.h @@ -0,0 +1,22 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2001-2002 Michal Rokos <m.rokos@sh.cvut.cz> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#if !defined(_OSSL_DIGEST_H_) +#define _OSSL_DIGEST_H_ + +extern VALUE cDigest; +extern VALUE eDigestError; + +const EVP_MD *GetDigestPtr(VALUE); +VALUE ossl_digest_new(const EVP_MD *); +void Init_ossl_digest(void); + +#endif /* _OSSL_DIGEST_H_ */ + diff --git a/ext/openssl/ossl_engine.c b/ext/openssl/ossl_engine.c new file mode 100644 index 00000000..96bce5e2 --- /dev/null +++ b/ext/openssl/ossl_engine.c @@ -0,0 +1,584 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2003 GOTOU Yuuzou <gotoyuzo@notwork.org> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#include "ossl.h" + +#if defined(OSSL_ENGINE_ENABLED) + +#define WrapEngine(klass, obj, engine) do { \ + if (!(engine)) { \ + ossl_raise(rb_eRuntimeError, "ENGINE wasn't initialized."); \ + } \ + (obj) = Data_Wrap_Struct((klass), 0, ENGINE_free, (engine)); \ +} while(0) +#define GetEngine(obj, engine) do { \ + Data_Get_Struct((obj), ENGINE, (engine)); \ + if (!(engine)) { \ + ossl_raise(rb_eRuntimeError, "ENGINE wasn't initialized."); \ + } \ +} while (0) +#define SafeGetEngine(obj, engine) do { \ + OSSL_Check_Kind((obj), cEngine); \ + GetPKCS7((obj), (engine)); \ +} while (0) + +/* + * Classes + */ +/* Document-class: OpenSSL::Engine + * + * This class is the access to openssl's ENGINE cryptographic module + * implementation. + * + * See also, https://www.openssl.org/docs/crypto/engine.html + */ +VALUE cEngine; +/* Document-class: OpenSSL::Engine::EngineError + * + * This is the generic exception for OpenSSL::Engine related errors + */ +VALUE eEngineError; + +/* + * Private + */ +#define OSSL_ENGINE_LOAD_IF_MATCH(x) \ +do{\ + if(!strcmp(#x, RSTRING_PTR(name))){\ + ENGINE_load_##x();\ + return Qtrue;\ + }\ +}while(0) + +/* Document-method: OpenSSL::Engine.load + * + * call-seq: + * load(enginename = nil) + * + * This method loads engines. If +name+ is nil, then all builtin engines are + * loaded. Otherwise, the given +name+, as a string, is loaded if available to + * your runtime, and returns true. If +name+ is not found, then nil is + * returned. + * + */ +static VALUE +ossl_engine_s_load(int argc, VALUE *argv, VALUE klass) +{ +#if !defined(HAVE_ENGINE_LOAD_BUILTIN_ENGINES) + return Qnil; +#else + VALUE name; + + rb_scan_args(argc, argv, "01", &name); + if(NIL_P(name)){ + ENGINE_load_builtin_engines(); + return Qtrue; + } + StringValue(name); +#ifndef OPENSSL_NO_STATIC_ENGINE +#if HAVE_ENGINE_LOAD_DYNAMIC + OSSL_ENGINE_LOAD_IF_MATCH(dynamic); +#endif +#if HAVE_ENGINE_LOAD_4758CCA + OSSL_ENGINE_LOAD_IF_MATCH(4758cca); +#endif +#if HAVE_ENGINE_LOAD_AEP + OSSL_ENGINE_LOAD_IF_MATCH(aep); +#endif +#if HAVE_ENGINE_LOAD_ATALLA + OSSL_ENGINE_LOAD_IF_MATCH(atalla); +#endif +#if HAVE_ENGINE_LOAD_CHIL + OSSL_ENGINE_LOAD_IF_MATCH(chil); +#endif +#if HAVE_ENGINE_LOAD_CSWIFT + OSSL_ENGINE_LOAD_IF_MATCH(cswift); +#endif +#if HAVE_ENGINE_LOAD_NURON + OSSL_ENGINE_LOAD_IF_MATCH(nuron); +#endif +#if HAVE_ENGINE_LOAD_SUREWARE + OSSL_ENGINE_LOAD_IF_MATCH(sureware); +#endif +#if HAVE_ENGINE_LOAD_UBSEC + OSSL_ENGINE_LOAD_IF_MATCH(ubsec); +#endif +#if HAVE_ENGINE_LOAD_PADLOCK + OSSL_ENGINE_LOAD_IF_MATCH(padlock); +#endif +#if HAVE_ENGINE_LOAD_CAPI + OSSL_ENGINE_LOAD_IF_MATCH(capi); +#endif +#if HAVE_ENGINE_LOAD_GMP + OSSL_ENGINE_LOAD_IF_MATCH(gmp); +#endif +#if HAVE_ENGINE_LOAD_GOST + OSSL_ENGINE_LOAD_IF_MATCH(gost); +#endif +#if HAVE_ENGINE_LOAD_CRYPTODEV + OSSL_ENGINE_LOAD_IF_MATCH(cryptodev); +#endif +#if HAVE_ENGINE_LOAD_AESNI + OSSL_ENGINE_LOAD_IF_MATCH(aesni); +#endif +#endif +#ifdef HAVE_ENGINE_LOAD_OPENBSD_DEV_CRYPTO + OSSL_ENGINE_LOAD_IF_MATCH(openbsd_dev_crypto); +#endif + OSSL_ENGINE_LOAD_IF_MATCH(openssl); + rb_warning("no such builtin loader for `%s'", RSTRING_PTR(name)); + return Qnil; +#endif /* HAVE_ENGINE_LOAD_BUILTIN_ENGINES */ +} + +/* Document-method: OpenSSL::Engine.cleanup + * call-seq: + * OpenSSL::Engine.cleanup + * + * It is only necessary to run cleanup when engines are loaded via + * OpenSSL::Engine.load. However, running cleanup before exit is recommended. + * + * See also, https://www.openssl.org/docs/crypto/engine.html + */ +static VALUE +ossl_engine_s_cleanup(VALUE self) +{ +#if defined(HAVE_ENGINE_CLEANUP) + ENGINE_cleanup(); +#endif + return Qnil; +} + +/* Document-method: OpenSSL::Engine.engines + * + * Returns an array of currently loaded engines. + */ +static VALUE +ossl_engine_s_engines(VALUE klass) +{ + ENGINE *e; + VALUE ary, obj; + + ary = rb_ary_new(); + for(e = ENGINE_get_first(); e; e = ENGINE_get_next(e)){ + /* Need a ref count of two here because of ENGINE_free being + * called internally by OpenSSL when moving to the next ENGINE + * and by us when releasing the ENGINE reference */ + ENGINE_up_ref(e); + WrapEngine(klass, obj, e); + rb_ary_push(ary, obj); + } + + return ary; +} + +/* Document-method: OpenSSL::Engine.by_id + * + * call-seq: + * by_id(name) -> engine + * + * Fetch the engine as specified by the +id+ String + * + * OpenSSL::Engine.by_id("openssl") + * => #<OpenSSL::Engine id="openssl" name="Software engine support"> + * + * See OpenSSL::Engine.engines for the currently loaded engines + */ +static VALUE +ossl_engine_s_by_id(VALUE klass, VALUE id) +{ + ENGINE *e; + VALUE obj; + + StringValue(id); + ossl_engine_s_load(1, &id, klass); + if(!(e = ENGINE_by_id(RSTRING_PTR(id)))) + ossl_raise(eEngineError, NULL); + WrapEngine(klass, obj, e); + if(rb_block_given_p()) rb_yield(obj); + if(!ENGINE_init(e)) + ossl_raise(eEngineError, NULL); + ENGINE_ctrl(e, ENGINE_CTRL_SET_PASSWORD_CALLBACK, + 0, NULL, (void(*)(void))ossl_pem_passwd_cb); + ERR_clear_error(); + + return obj; +} + +static VALUE +ossl_engine_s_alloc(VALUE klass) +{ + ENGINE *e; + VALUE obj; + + if (!(e = ENGINE_new())) { + ossl_raise(eEngineError, NULL); + } + WrapEngine(klass, obj, e); + + return obj; +} + +/* Document-method: OpenSSL::Engine#id + * + * Get the id for this engine + * + * OpenSSL::Engine.load + * OpenSSL::Engine.engines #=> [#<OpenSSL::Engine#>, ...] + * OpenSSL::Engine.engines.first.id + * #=> "rsax" + */ +static VALUE +ossl_engine_get_id(VALUE self) +{ + ENGINE *e; + GetEngine(self, e); + return rb_str_new2(ENGINE_get_id(e)); +} + +/* Document-method: OpenSSL::Engine#name + * + * Get the descriptive name for this engine + * + * OpenSSL::Engine.load + * OpenSSL::Engine.engines #=> [#<OpenSSL::Engine#>, ...] + * OpenSSL::Engine.engines.first.name + * #=> "RSAX engine support" + * + */ +static VALUE +ossl_engine_get_name(VALUE self) +{ + ENGINE *e; + GetEngine(self, e); + return rb_str_new2(ENGINE_get_name(e)); +} + +/* Document-method: OpenSSL::Engine#finish + * + * Releases all internal structural references for this engine. + * + * May raise an EngineError if the engine is unavailable + */ +static VALUE +ossl_engine_finish(VALUE self) +{ + ENGINE *e; + + GetEngine(self, e); + if(!ENGINE_finish(e)) ossl_raise(eEngineError, NULL); + + return Qnil; +} + +#if defined(HAVE_ENGINE_GET_CIPHER) +/* Document-method: OpenSSL::Engine#cipher + * + * call-seq: + * engine.cipher(name) -> OpenSSL::Cipher + * + * This returns an OpenSSL::Cipher by +name+, if it is available in this + * engine. + * + * A EngineError will be raised if the cipher is unavailable. + * + * e = OpenSSL::Engine.by_id("openssl") + * => #<OpenSSL::Engine id="openssl" name="Software engine support"> + * e.cipher("RC4") + * => #<OpenSSL::Cipher:0x007fc5cacc3048> + * + */ +static VALUE +ossl_engine_get_cipher(VALUE self, VALUE name) +{ + ENGINE *e; + const EVP_CIPHER *ciph, *tmp; + char *s; + int nid; + + s = StringValuePtr(name); + tmp = EVP_get_cipherbyname(s); + if(!tmp) ossl_raise(eEngineError, "no such cipher `%s'", s); + nid = EVP_CIPHER_nid(tmp); + GetEngine(self, e); + ciph = ENGINE_get_cipher(e, nid); + if(!ciph) ossl_raise(eEngineError, NULL); + + return ossl_cipher_new(ciph); +} +#else +#define ossl_engine_get_cipher rb_f_notimplement +#endif + +#if defined(HAVE_ENGINE_GET_DIGEST) +/* Document-method: OpenSSL::Engine#digest + * + * call-seq: + * engine.digest(name) -> OpenSSL::Digest + * + * This returns an OpenSSL::Digest by +name+. + * + * Will raise an EngineError if the digest is unavailable. + * + * e = OpenSSL::Engine.by_id("openssl") + * #=> #<OpenSSL::Engine id="openssl" name="Software engine support"> + * e.digest("SHA1") + * #=> #<OpenSSL::Digest: da39a3ee5e6b4b0d3255bfef95601890afd80709> + * e.digest("zomg") + * #=> OpenSSL::Engine::EngineError: no such digest `zomg' + */ +static VALUE +ossl_engine_get_digest(VALUE self, VALUE name) +{ + ENGINE *e; + const EVP_MD *md, *tmp; + char *s; + int nid; + + s = StringValuePtr(name); + tmp = EVP_get_digestbyname(s); + if(!tmp) ossl_raise(eEngineError, "no such digest `%s'", s); + nid = EVP_MD_nid(tmp); + GetEngine(self, e); + md = ENGINE_get_digest(e, nid); + if(!md) ossl_raise(eEngineError, NULL); + + return ossl_digest_new(md); +} +#else +#define ossl_engine_get_digest rb_f_notimplement +#endif + +/* Document-method: OpenSSL::Engine#load_private_key + * + * call-seq: + * engine.load_private_key(id = nil, data = nil) -> OpenSSL::PKey + * + * Loads the given private key by +id+ and +data+. + * + * An EngineError is raised of the OpenSSL::PKey is unavailable. + * + */ +static VALUE +ossl_engine_load_privkey(int argc, VALUE *argv, VALUE self) +{ + ENGINE *e; + EVP_PKEY *pkey; + VALUE id, data, obj; + char *sid, *sdata; + + rb_scan_args(argc, argv, "02", &id, &data); + sid = NIL_P(id) ? NULL : StringValuePtr(id); + sdata = NIL_P(data) ? NULL : StringValuePtr(data); + GetEngine(self, e); +#if OPENSSL_VERSION_NUMBER < 0x00907000L + pkey = ENGINE_load_private_key(e, sid, sdata); +#else + pkey = ENGINE_load_private_key(e, sid, NULL, sdata); +#endif + if (!pkey) ossl_raise(eEngineError, NULL); + obj = ossl_pkey_new(pkey); + OSSL_PKEY_SET_PRIVATE(obj); + + return obj; +} + +/* Document-method: OpenSSL::Engine#load_public_key + * + * call-seq: + * engine.load_public_key(id = nil, data = nil) -> OpenSSL::PKey + * + * Loads the given private key by +id+ and +data+. + * + * An EngineError is raised of the OpenSSL::PKey is unavailable. + * + */ +static VALUE +ossl_engine_load_pubkey(int argc, VALUE *argv, VALUE self) +{ + ENGINE *e; + EVP_PKEY *pkey; + VALUE id, data; + char *sid, *sdata; + + rb_scan_args(argc, argv, "02", &id, &data); + sid = NIL_P(id) ? NULL : StringValuePtr(id); + sdata = NIL_P(data) ? NULL : StringValuePtr(data); + GetEngine(self, e); +#if OPENSSL_VERSION_NUMBER < 0x00907000L + pkey = ENGINE_load_public_key(e, sid, sdata); +#else + pkey = ENGINE_load_public_key(e, sid, NULL, sdata); +#endif + if (!pkey) ossl_raise(eEngineError, NULL); + + return ossl_pkey_new(pkey); +} + +/* Document-method: OpenSSL::Engine#set_default + * + * call-seq: + * engine.set_default(flag) + * + * Set the defaults for this engine with the given +flag+. + * + * These flags are used to control combinations of algorithm methods. + * + * +flag+ can be one of the following, other flags are available depending on + * your OS. + * + * [All flags] 0xFFFF + * [No flags] 0x0000 + * + * See also <openssl/engine.h> + */ +static VALUE +ossl_engine_set_default(VALUE self, VALUE flag) +{ + ENGINE *e; + int f = NUM2INT(flag); + + GetEngine(self, e); + ENGINE_set_default(e, f); + + return Qtrue; +} + +/* Document-method: OpenSSL::Engine#ctrl_cmd + * + * call-seq: + * engine.ctrl_cmd(command, value = nil) -> engine + * + * Send the given +command+ to this engine. + * + * Raises an EngineError if the +command+ fails. + */ +static VALUE +ossl_engine_ctrl_cmd(int argc, VALUE *argv, VALUE self) +{ + ENGINE *e; + VALUE cmd, val; + int ret; + + GetEngine(self, e); + rb_scan_args(argc, argv, "11", &cmd, &val); + StringValue(cmd); + if (!NIL_P(val)) StringValue(val); + ret = ENGINE_ctrl_cmd_string(e, RSTRING_PTR(cmd), + NIL_P(val) ? NULL : RSTRING_PTR(val), 0); + if (!ret) ossl_raise(eEngineError, NULL); + + return self; +} + +static VALUE +ossl_engine_cmd_flag_to_name(int flag) +{ + switch(flag){ + case ENGINE_CMD_FLAG_NUMERIC: return rb_str_new2("NUMERIC"); + case ENGINE_CMD_FLAG_STRING: return rb_str_new2("STRING"); + case ENGINE_CMD_FLAG_NO_INPUT: return rb_str_new2("NO_INPUT"); + case ENGINE_CMD_FLAG_INTERNAL: return rb_str_new2("INTERNAL"); + default: return rb_str_new2("UNKNOWN"); + } +} + +/* Document-method: OpenSSL::Engine#cmds + * + * Returns an array of command definitions for the current engine + */ +static VALUE +ossl_engine_get_cmds(VALUE self) +{ + ENGINE *e; + const ENGINE_CMD_DEFN *defn, *p; + VALUE ary, tmp; + + GetEngine(self, e); + ary = rb_ary_new(); + if ((defn = ENGINE_get_cmd_defns(e)) != NULL){ + for (p = defn; p->cmd_num > 0; p++){ + tmp = rb_ary_new(); + rb_ary_push(tmp, rb_str_new2(p->cmd_name)); + rb_ary_push(tmp, rb_str_new2(p->cmd_desc)); + rb_ary_push(tmp, ossl_engine_cmd_flag_to_name(p->cmd_flags)); + rb_ary_push(ary, tmp); + } + } + + return ary; +} + +/* Document-method: OpenSSL::Engine#inspect + * + * Pretty print this engine + */ +static VALUE +ossl_engine_inspect(VALUE self) +{ + ENGINE *e; + + GetEngine(self, e); + return rb_sprintf("#<%"PRIsVALUE" id=\"%s\" name=\"%s\">", + rb_obj_class(self), ENGINE_get_id(e), ENGINE_get_name(e)); +} + +#define DefEngineConst(x) rb_define_const(cEngine, #x, INT2NUM(ENGINE_##x)) + +void +Init_ossl_engine(void) +{ + cEngine = rb_define_class_under(mOSSL, "Engine", rb_cObject); + eEngineError = rb_define_class_under(cEngine, "EngineError", eOSSLError); + + rb_define_alloc_func(cEngine, ossl_engine_s_alloc); + rb_define_singleton_method(cEngine, "load", ossl_engine_s_load, -1); + rb_define_singleton_method(cEngine, "cleanup", ossl_engine_s_cleanup, 0); + rb_define_singleton_method(cEngine, "engines", ossl_engine_s_engines, 0); + rb_define_singleton_method(cEngine, "by_id", ossl_engine_s_by_id, 1); + rb_undef_method(CLASS_OF(cEngine), "new"); + + rb_define_method(cEngine, "id", ossl_engine_get_id, 0); + rb_define_method(cEngine, "name", ossl_engine_get_name, 0); + rb_define_method(cEngine, "finish", ossl_engine_finish, 0); + rb_define_method(cEngine, "cipher", ossl_engine_get_cipher, 1); + rb_define_method(cEngine, "digest", ossl_engine_get_digest, 1); + rb_define_method(cEngine, "load_private_key", ossl_engine_load_privkey, -1); + rb_define_method(cEngine, "load_public_key", ossl_engine_load_pubkey, -1); + rb_define_method(cEngine, "set_default", ossl_engine_set_default, 1); + rb_define_method(cEngine, "ctrl_cmd", ossl_engine_ctrl_cmd, -1); + rb_define_method(cEngine, "cmds", ossl_engine_get_cmds, 0); + rb_define_method(cEngine, "inspect", ossl_engine_inspect, 0); + + DefEngineConst(METHOD_RSA); + DefEngineConst(METHOD_DSA); + DefEngineConst(METHOD_DH); + DefEngineConst(METHOD_RAND); +#ifdef ENGINE_METHOD_BN_MOD_EXP + DefEngineConst(METHOD_BN_MOD_EXP); +#endif +#ifdef ENGINE_METHOD_BN_MOD_EXP_CRT + DefEngineConst(METHOD_BN_MOD_EXP_CRT); +#endif +#ifdef ENGINE_METHOD_CIPHERS + DefEngineConst(METHOD_CIPHERS); +#endif +#ifdef ENGINE_METHOD_DIGESTS + DefEngineConst(METHOD_DIGESTS); +#endif + DefEngineConst(METHOD_ALL); + DefEngineConst(METHOD_NONE); +} +#else +void +Init_ossl_engine(void) +{ +} +#endif diff --git a/ext/openssl/ossl_engine.h b/ext/openssl/ossl_engine.h new file mode 100644 index 00000000..ea2f2569 --- /dev/null +++ b/ext/openssl/ossl_engine.h @@ -0,0 +1,20 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2003 Michal Rokos <m.rokos@sh.cvut.cz> + * Copyright (C) 2003 GOTOU Yuuzou <gotoyuzo@notwork.org> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#if !defined(OSSL_ENGINE_H) +#define OSSL_ENGINE_H + +extern VALUE cEngine; +extern VALUE eEngineError; + +void Init_ossl_engine(void); + +#endif /* OSSL_ENGINE_H */ diff --git a/ext/openssl/ossl_hmac.c b/ext/openssl/ossl_hmac.c new file mode 100644 index 00000000..516e3ed8 --- /dev/null +++ b/ext/openssl/ossl_hmac.c @@ -0,0 +1,364 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2001-2002 Michal Rokos <m.rokos@sh.cvut.cz> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#if !defined(OPENSSL_NO_HMAC) + +#include "ossl.h" + +#define MakeHMAC(obj, klass, ctx) \ + (obj) = Data_Make_Struct((klass), HMAC_CTX, 0, ossl_hmac_free, (ctx)) +#define GetHMAC(obj, ctx) do { \ + Data_Get_Struct((obj), HMAC_CTX, (ctx)); \ + if (!(ctx)) { \ + ossl_raise(rb_eRuntimeError, "HMAC wasn't initialized"); \ + } \ +} while (0) +#define SafeGetHMAC(obj, ctx) do { \ + OSSL_Check_Kind((obj), cHMAC); \ + GetHMAC((obj), (ctx)); \ +} while (0) + +/* + * Classes + */ +VALUE cHMAC; +VALUE eHMACError; + +/* + * Public + */ + +/* + * Private + */ +static void +ossl_hmac_free(HMAC_CTX *ctx) +{ + HMAC_CTX_cleanup(ctx); + ruby_xfree(ctx); +} + +static VALUE +ossl_hmac_alloc(VALUE klass) +{ + HMAC_CTX *ctx; + VALUE obj; + + MakeHMAC(obj, klass, ctx); + HMAC_CTX_init(ctx); + + return obj; +} + + +/* + * call-seq: + * HMAC.new(key, digest) -> hmac + * + * Returns an instance of OpenSSL::HMAC set with the key and digest + * algorithm to be used. The instance represents the initial state of + * the message authentication code before any data has been processed. + * To process data with it, use the instance method #update with your + * data as an argument. + * + * === Example + * + * key = 'key' + * digest = OpenSSL::Digest.new('sha1') + * instance = OpenSSL::HMAC.new(key, digest) + * #=> f42bb0eeb018ebbd4597ae7213711ec60760843f + * instance.class + * #=> OpenSSL::HMAC + * + * === A note about comparisons + * + * Two instances won't be equal when they're compared, even if they have the + * same value. Use #to_s or #hexdigest to return the authentication code that + * the instance represents. For example: + * + * other_instance = OpenSSL::HMAC.new('key', OpenSSL::Digest.new('sha1')) + * #=> f42bb0eeb018ebbd4597ae7213711ec60760843f + * instance + * #=> f42bb0eeb018ebbd4597ae7213711ec60760843f + * instance == other_instance + * #=> false + * instance.to_s == other_instance.to_s + * #=> true + * + */ +static VALUE +ossl_hmac_initialize(VALUE self, VALUE key, VALUE digest) +{ + HMAC_CTX *ctx; + + StringValue(key); + GetHMAC(self, ctx); + HMAC_Init(ctx, RSTRING_PTR(key), RSTRING_LENINT(key), + GetDigestPtr(digest)); + + return self; +} + +static VALUE +ossl_hmac_copy(VALUE self, VALUE other) +{ + HMAC_CTX *ctx1, *ctx2; + + rb_check_frozen(self); + if (self == other) return self; + + GetHMAC(self, ctx1); + SafeGetHMAC(other, ctx2); + + HMAC_CTX_copy(ctx1, ctx2); + return self; +} + +/* + * call-seq: + * hmac.update(string) -> self + * + * Returns +self+ updated with the message to be authenticated. + * Can be called repeatedly with chunks of the message. + * + * === Example + * + * first_chunk = 'The quick brown fox jumps ' + * second_chunk = 'over the lazy dog' + * + * instance.update(first_chunk) + * #=> 5b9a8038a65d571076d97fe783989e52278a492a + * instance.update(second_chunk) + * #=> de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9 + * + */ +static VALUE +ossl_hmac_update(VALUE self, VALUE data) +{ + HMAC_CTX *ctx; + + StringValue(data); + GetHMAC(self, ctx); + HMAC_Update(ctx, (unsigned char *)RSTRING_PTR(data), RSTRING_LEN(data)); + + return self; +} + +static void +hmac_final(HMAC_CTX *ctx, unsigned char **buf, unsigned int *buf_len) +{ + HMAC_CTX final; + + HMAC_CTX_copy(&final, ctx); + if (!(*buf = OPENSSL_malloc(HMAC_size(&final)))) { + HMAC_CTX_cleanup(&final); + OSSL_Debug("Allocating %d mem", HMAC_size(&final)); + ossl_raise(eHMACError, "Cannot allocate memory for hmac"); + } + HMAC_Final(&final, *buf, buf_len); + HMAC_CTX_cleanup(&final); +} + +/* + * call-seq: + * hmac.digest -> string + * + * Returns the authentication code an instance represents as a binary string. + * + * === Example + * + * instance = OpenSSL::HMAC.new('key', OpenSSL::Digest.new('sha1')) + * #=> f42bb0eeb018ebbd4597ae7213711ec60760843f + * instance.digest + * #=> "\xF4+\xB0\xEE\xB0\x18\xEB\xBDE\x97\xAEr\x13q\x1E\xC6\a`\x84?" + * + */ +static VALUE +ossl_hmac_digest(VALUE self) +{ + HMAC_CTX *ctx; + unsigned char *buf; + unsigned int buf_len; + VALUE digest; + + GetHMAC(self, ctx); + hmac_final(ctx, &buf, &buf_len); + digest = ossl_buf2str((char *)buf, buf_len); + + return digest; +} + +/* + * call-seq: + * hmac.hexdigest -> string + * + * Returns the authentication code an instance represents as a hex-encoded + * string. + * + */ +static VALUE +ossl_hmac_hexdigest(VALUE self) +{ + HMAC_CTX *ctx; + unsigned char *buf; + char *hexbuf; + unsigned int buf_len; + VALUE hexdigest; + + GetHMAC(self, ctx); + hmac_final(ctx, &buf, &buf_len); + if (string2hex(buf, buf_len, &hexbuf, NULL) != 2 * (int)buf_len) { + OPENSSL_free(buf); + ossl_raise(eHMACError, "Memory alloc error"); + } + OPENSSL_free(buf); + hexdigest = ossl_buf2str(hexbuf, 2 * buf_len); + + return hexdigest; +} + +/* + * call-seq: + * hmac.reset -> self + * + * Returns +self+ as it was when it was first initialized, with all processed + * data cleared from it. + * + * === Example + * + * data = "The quick brown fox jumps over the lazy dog" + * instance = OpenSSL::HMAC.new('key', OpenSSL::Digest.new('sha1')) + * #=> f42bb0eeb018ebbd4597ae7213711ec60760843f + * + * instance.update(data) + * #=> de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9 + * instance.reset + * #=> f42bb0eeb018ebbd4597ae7213711ec60760843f + * + */ +static VALUE +ossl_hmac_reset(VALUE self) +{ + HMAC_CTX *ctx; + + GetHMAC(self, ctx); + HMAC_Init(ctx, NULL, 0, NULL); + + return self; +} + +/* + * call-seq: + * HMAC.digest(digest, key, data) -> aString + * + * Returns the authentication code as a binary string. The +digest+ parameter + * must be an instance of OpenSSL::Digest. + * + * === Example + * + * key = 'key' + * data = 'The quick brown fox jumps over the lazy dog' + * digest = OpenSSL::Digest.new('sha1') + * + * hmac = OpenSSL::HMAC.digest(digest, key, data) + * #=> "\xDE|\x9B\x85\xB8\xB7\x8A\xA6\xBC\x8Az6\xF7\n\x90p\x1C\x9D\xB4\xD9" + * + */ +static VALUE +ossl_hmac_s_digest(VALUE klass, VALUE digest, VALUE key, VALUE data) +{ + unsigned char *buf; + unsigned int buf_len; + + StringValue(key); + StringValue(data); + buf = HMAC(GetDigestPtr(digest), RSTRING_PTR(key), RSTRING_LENINT(key), + (unsigned char *)RSTRING_PTR(data), RSTRING_LEN(data), NULL, &buf_len); + + return rb_str_new((const char *)buf, buf_len); +} + +/* + * call-seq: + * HMAC.hexdigest(digest, key, data) -> aString + * + * Returns the authentication code as a hex-encoded string. The +digest+ + * parameter must be an instance of OpenSSL::Digest. + * + * === Example + * + * key = 'key' + * data = 'The quick brown fox jumps over the lazy dog' + * digest = OpenSSL::Digest.new('sha1') + * + * hmac = OpenSSL::HMAC.hexdigest(digest, key, data) + * #=> "de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9" + * + */ +static VALUE +ossl_hmac_s_hexdigest(VALUE klass, VALUE digest, VALUE key, VALUE data) +{ + unsigned char *buf; + char *hexbuf; + unsigned int buf_len; + VALUE hexdigest; + + StringValue(key); + StringValue(data); + + buf = HMAC(GetDigestPtr(digest), RSTRING_PTR(key), RSTRING_LENINT(key), + (unsigned char *)RSTRING_PTR(data), RSTRING_LEN(data), NULL, &buf_len); + if (string2hex(buf, buf_len, &hexbuf, NULL) != 2 * (int)buf_len) { + ossl_raise(eHMACError, "Cannot convert buf to hexbuf"); + } + hexdigest = ossl_buf2str(hexbuf, 2 * buf_len); + + return hexdigest; +} + +/* + * INIT + */ +void +Init_ossl_hmac(void) +{ +#if 0 + /* :nodoc: */ + mOSSL = rb_define_module("OpenSSL"); /* let rdoc know about mOSSL */ +#endif + + eHMACError = rb_define_class_under(mOSSL, "HMACError", eOSSLError); + + cHMAC = rb_define_class_under(mOSSL, "HMAC", rb_cObject); + + rb_define_alloc_func(cHMAC, ossl_hmac_alloc); + rb_define_singleton_method(cHMAC, "digest", ossl_hmac_s_digest, 3); + rb_define_singleton_method(cHMAC, "hexdigest", ossl_hmac_s_hexdigest, 3); + + rb_define_method(cHMAC, "initialize", ossl_hmac_initialize, 2); + rb_define_copy_func(cHMAC, ossl_hmac_copy); + + rb_define_method(cHMAC, "reset", ossl_hmac_reset, 0); + rb_define_method(cHMAC, "update", ossl_hmac_update, 1); + rb_define_alias(cHMAC, "<<", "update"); + rb_define_method(cHMAC, "digest", ossl_hmac_digest, 0); + rb_define_method(cHMAC, "hexdigest", ossl_hmac_hexdigest, 0); + rb_define_alias(cHMAC, "inspect", "hexdigest"); + rb_define_alias(cHMAC, "to_s", "hexdigest"); +} + +#else /* NO_HMAC */ +# warning >>> OpenSSL is compiled without HMAC support <<< +void +Init_ossl_hmac(void) +{ + rb_warning("HMAC is not available: OpenSSL is compiled without HMAC."); +} +#endif /* NO_HMAC */ diff --git a/ext/openssl/ossl_hmac.h b/ext/openssl/ossl_hmac.h new file mode 100644 index 00000000..1a2978b3 --- /dev/null +++ b/ext/openssl/ossl_hmac.h @@ -0,0 +1,19 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2001-2002 Michal Rokos <m.rokos@sh.cvut.cz> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#if !defined(_OSSL_HMAC_H_) +#define _OSSL_HMAC_H_ + +extern VALUE cHMAC; +extern VALUE eHMACError; + +void Init_ossl_hmac(void); + +#endif /* _OSSL_HMAC_H_ */ diff --git a/ext/openssl/ossl_ns_spki.c b/ext/openssl/ossl_ns_spki.c new file mode 100644 index 00000000..965bbe87 --- /dev/null +++ b/ext/openssl/ossl_ns_spki.c @@ -0,0 +1,389 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2001-2002 Michal Rokos <m.rokos@sh.cvut.cz> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#include "ossl.h" + +#define WrapSPKI(klass, obj, spki) do { \ + if (!(spki)) { \ + ossl_raise(rb_eRuntimeError, "SPKI wasn't initialized!"); \ + } \ + (obj) = Data_Wrap_Struct((klass), 0, NETSCAPE_SPKI_free, (spki)); \ +} while (0) +#define GetSPKI(obj, spki) do { \ + Data_Get_Struct((obj), NETSCAPE_SPKI, (spki)); \ + if (!(spki)) { \ + ossl_raise(rb_eRuntimeError, "SPKI wasn't initialized!"); \ + } \ +} while (0) + +/* + * Classes + */ +VALUE mNetscape; +VALUE cSPKI; +VALUE eSPKIError; + +/* + * Public functions + */ + +/* + * Private functions + */ +static VALUE +ossl_spki_alloc(VALUE klass) +{ + NETSCAPE_SPKI *spki; + VALUE obj; + + if (!(spki = NETSCAPE_SPKI_new())) { + ossl_raise(eSPKIError, NULL); + } + WrapSPKI(klass, obj, spki); + + return obj; +} + +/* + * call-seq: + * SPKI.new([request]) => spki + * + * === Parameters + * * +request+ - optional raw request, either in PEM or DER format. + */ +static VALUE +ossl_spki_initialize(int argc, VALUE *argv, VALUE self) +{ + NETSCAPE_SPKI *spki; + VALUE buffer; + const unsigned char *p; + + if (rb_scan_args(argc, argv, "01", &buffer) == 0) { + return self; + } + StringValue(buffer); + if (!(spki = NETSCAPE_SPKI_b64_decode(RSTRING_PTR(buffer), -1))) { + p = (unsigned char *)RSTRING_PTR(buffer); + if (!(spki = d2i_NETSCAPE_SPKI(NULL, &p, RSTRING_LEN(buffer)))) { + ossl_raise(eSPKIError, NULL); + } + } + NETSCAPE_SPKI_free(DATA_PTR(self)); + DATA_PTR(self) = spki; + ERR_clear_error(); + + return self; +} + +/* + * call-seq: + * spki.to_der => DER-encoded string + * + * Returns the DER encoding of this SPKI. + */ +static VALUE +ossl_spki_to_der(VALUE self) +{ + NETSCAPE_SPKI *spki; + VALUE str; + long len; + unsigned char *p; + + GetSPKI(self, spki); + if ((len = i2d_NETSCAPE_SPKI(spki, NULL)) <= 0) + ossl_raise(eX509CertError, NULL); + str = rb_str_new(0, len); + p = (unsigned char *)RSTRING_PTR(str); + if (i2d_NETSCAPE_SPKI(spki, &p) <= 0) + ossl_raise(eX509CertError, NULL); + ossl_str_adjust(str, p); + + return str; +} + +/* + * call-seq: + * spki.to_pem => PEM-encoded string + * + * Returns the PEM encoding of this SPKI. + */ +static VALUE +ossl_spki_to_pem(VALUE self) +{ + NETSCAPE_SPKI *spki; + char *data; + VALUE str; + + GetSPKI(self, spki); + if (!(data = NETSCAPE_SPKI_b64_encode(spki))) { + ossl_raise(eSPKIError, NULL); + } + str = ossl_buf2str(data, rb_long2int(strlen(data))); + + return str; +} + +/* + * call-seq: + * spki.to_text => string + * + * Returns a textual representation of this SPKI, useful for debugging + * purposes. + */ +static VALUE +ossl_spki_print(VALUE self) +{ + NETSCAPE_SPKI *spki; + BIO *out; + BUF_MEM *buf; + VALUE str; + + GetSPKI(self, spki); + if (!(out = BIO_new(BIO_s_mem()))) { + ossl_raise(eSPKIError, NULL); + } + if (!NETSCAPE_SPKI_print(out, spki)) { + BIO_free(out); + ossl_raise(eSPKIError, NULL); + } + BIO_get_mem_ptr(out, &buf); + str = rb_str_new(buf->data, buf->length); + BIO_free(out); + + return str; +} + +/* + * call-seq: + * spki.public_key => pkey + * + * Returns the public key associated with the SPKI, an instance of + * OpenSSL::PKey. + */ +static VALUE +ossl_spki_get_public_key(VALUE self) +{ + NETSCAPE_SPKI *spki; + EVP_PKEY *pkey; + + GetSPKI(self, spki); + if (!(pkey = NETSCAPE_SPKI_get_pubkey(spki))) { /* adds an reference */ + ossl_raise(eSPKIError, NULL); + } + + return ossl_pkey_new(pkey); /* NO DUP - OK */ +} + +/* + * call-seq: + * spki.public_key = pub => pkey + * + * === Parameters + * * +pub+ - the public key to be set for this instance + * + * Sets the public key to be associated with the SPKI, an instance of + * OpenSSL::PKey. This should be the public key corresponding to the + * private key used for signing the SPKI. + */ +static VALUE +ossl_spki_set_public_key(VALUE self, VALUE key) +{ + NETSCAPE_SPKI *spki; + + GetSPKI(self, spki); + if (!NETSCAPE_SPKI_set_pubkey(spki, GetPKeyPtr(key))) { /* NO NEED TO DUP */ + ossl_raise(eSPKIError, NULL); + } + + return key; +} + +/* + * call-seq: + * spki.challenge => string + * + * Returns the challenge string associated with this SPKI. + */ +static VALUE +ossl_spki_get_challenge(VALUE self) +{ + NETSCAPE_SPKI *spki; + + GetSPKI(self, spki); + if (spki->spkac->challenge->length <= 0) { + OSSL_Debug("Challenge.length <= 0?"); + return rb_str_new(0, 0); + } + + return rb_str_new((const char *)spki->spkac->challenge->data, + spki->spkac->challenge->length); +} + +/* + * call-seq: + * spki.challenge = str => string + * + * === Parameters + * * +str+ - the challenge string to be set for this instance + * + * Sets the challenge to be associated with the SPKI. May be used by the + * server, e.g. to prevent replay. + */ +static VALUE +ossl_spki_set_challenge(VALUE self, VALUE str) +{ + NETSCAPE_SPKI *spki; + + StringValue(str); + GetSPKI(self, spki); + if (!ASN1_STRING_set(spki->spkac->challenge, RSTRING_PTR(str), + RSTRING_LENINT(str))) { + ossl_raise(eSPKIError, NULL); + } + + return str; +} + +/* + * call-seq: + * spki.sign(key, digest) => spki + * + * === Parameters + * * +key+ - the private key to be used for signing this instance + * * +digest+ - the digest to be used for signing this instance + * + * To sign an SPKI, the private key corresponding to the public key set + * for this instance should be used, in addition to a digest algorithm in + * the form of an OpenSSL::Digest. The private key should be an instance of + * OpenSSL::PKey. + */ +static VALUE +ossl_spki_sign(VALUE self, VALUE key, VALUE digest) +{ + NETSCAPE_SPKI *spki; + EVP_PKEY *pkey; + const EVP_MD *md; + + pkey = GetPrivPKeyPtr(key); /* NO NEED TO DUP */ + md = GetDigestPtr(digest); + GetSPKI(self, spki); + if (!NETSCAPE_SPKI_sign(spki, pkey, md)) { + ossl_raise(eSPKIError, NULL); + } + + return self; +} + +/* + * call-seq: + * spki.verify(key) => boolean + * + * === Parameters + * * +key+ - the public key to be used for verifying the SPKI signature + * + * Returns +true+ if the signature is valid, +false+ otherwise. To verify an + * SPKI, the public key contained within the SPKI should be used. + */ +static VALUE +ossl_spki_verify(VALUE self, VALUE key) +{ + NETSCAPE_SPKI *spki; + + GetSPKI(self, spki); + switch (NETSCAPE_SPKI_verify(spki, GetPKeyPtr(key))) { /* NO NEED TO DUP */ + case 0: + return Qfalse; + case 1: + return Qtrue; + default: + ossl_raise(eSPKIError, NULL); + } + return Qnil; /* dummy */ +} + +/* Document-class: OpenSSL::Netscape::SPKI + * + * A Simple Public Key Infrastructure implementation (pronounced "spookey"). + * The structure is defined as + * PublicKeyAndChallenge ::= SEQUENCE { + * spki SubjectPublicKeyInfo, + * challenge IA5STRING + * } + * + * SignedPublicKeyAndChallenge ::= SEQUENCE { + * publicKeyAndChallenge PublicKeyAndChallenge, + * signatureAlgorithm AlgorithmIdentifier, + * signature BIT STRING + * } + * where the definitions of SubjectPublicKeyInfo and AlgorithmIdentifier can + * be found in RFC5280. SPKI is typically used in browsers for generating + * a public/private key pair and a subsequent certificate request, using + * the HTML <keygen> element. + * + * == Examples + * + * === Creating an SPKI + * key = OpenSSL::PKey::RSA.new 2048 + * spki = OpenSSL::Netscape::SPKI.new + * spki.challenge = "RandomChallenge" + * spki.public_key = key.public_key + * spki.sign(key, OpenSSL::Digest::SHA256.new) + * #send a request containing this to a server generating a certificate + * === Verifiying an SPKI request + * request = #... + * spki = OpenSSL::Netscape::SPKI.new request + * unless spki.verify(spki.public_key) + * # signature is invalid + * end + * #proceed + */ + +/* Document-module: OpenSSL::Netscape + * + * OpenSSL::Netscape is a namespace for SPKI (Simple Public Key + * Infrastructure) which implements Signed Public Key and Challenge. + * See {RFC 2692}[http://tools.ietf.org/html/rfc2692] and {RFC + * 2693}[http://tools.ietf.org/html/rfc2692] for details. + */ + +/* Document-class: OpenSSL::Netscape::SPKIError + * + * Generic Exception class that is raised if an error occurs during an + * operation on an instance of OpenSSL::Netscape::SPKI. + */ + +void +Init_ossl_ns_spki(void) +{ +#if 0 + mOSSL = rb_define_module("OpenSSL"); /* let rdoc know about mOSSL */ +#endif + + mNetscape = rb_define_module_under(mOSSL, "Netscape"); + + eSPKIError = rb_define_class_under(mNetscape, "SPKIError", eOSSLError); + + cSPKI = rb_define_class_under(mNetscape, "SPKI", rb_cObject); + + rb_define_alloc_func(cSPKI, ossl_spki_alloc); + rb_define_method(cSPKI, "initialize", ossl_spki_initialize, -1); + + rb_define_method(cSPKI, "to_der", ossl_spki_to_der, 0); + rb_define_method(cSPKI, "to_pem", ossl_spki_to_pem, 0); + rb_define_alias(cSPKI, "to_s", "to_pem"); + rb_define_method(cSPKI, "to_text", ossl_spki_print, 0); + rb_define_method(cSPKI, "public_key", ossl_spki_get_public_key, 0); + rb_define_method(cSPKI, "public_key=", ossl_spki_set_public_key, 1); + rb_define_method(cSPKI, "sign", ossl_spki_sign, 2); + rb_define_method(cSPKI, "verify", ossl_spki_verify, 1); + rb_define_method(cSPKI, "challenge", ossl_spki_get_challenge, 0); + rb_define_method(cSPKI, "challenge=", ossl_spki_set_challenge, 1); +} + diff --git a/ext/openssl/ossl_ns_spki.h b/ext/openssl/ossl_ns_spki.h new file mode 100644 index 00000000..9977035a --- /dev/null +++ b/ext/openssl/ossl_ns_spki.h @@ -0,0 +1,21 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2001-2002 Michal Rokos <m.rokos@sh.cvut.cz> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#if !defined(_OSSL_NS_SPKI_H_) +#define _OSSL_NS_SPKI_H_ + +extern VALUE mNetscape; +extern VALUE cSPKI; +extern VALUE eSPKIError; + +void Init_ossl_ns_spki(void); + +#endif /* _OSSL_NS_SPKI_H_ */ + diff --git a/ext/openssl/ossl_ocsp.c b/ext/openssl/ossl_ocsp.c new file mode 100644 index 00000000..9848ba27 --- /dev/null +++ b/ext/openssl/ossl_ocsp.c @@ -0,0 +1,1188 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2003 Michal Rokos <m.rokos@sh.cvut.cz> + * Copyright (C) 2003 GOTOU Yuuzou <gotoyuzo@notwork.org> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#include "ossl.h" + +#if defined(OSSL_OCSP_ENABLED) + +#define WrapOCSPReq(klass, obj, req) do { \ + if(!(req)) ossl_raise(rb_eRuntimeError, "Request wasn't initialized!"); \ + (obj) = Data_Wrap_Struct((klass), 0, OCSP_REQUEST_free, (req)); \ +} while (0) +#define GetOCSPReq(obj, req) do { \ + Data_Get_Struct((obj), OCSP_REQUEST, (req)); \ + if(!(req)) ossl_raise(rb_eRuntimeError, "Request wasn't initialized!"); \ +} while (0) +#define SafeGetOCSPReq(obj, req) do { \ + OSSL_Check_Kind((obj), cOCSPReq); \ + GetOCSPReq((obj), (req)); \ +} while (0) + +#define WrapOCSPRes(klass, obj, res) do { \ + if(!(res)) ossl_raise(rb_eRuntimeError, "Response wasn't initialized!"); \ + (obj) = Data_Wrap_Struct((klass), 0, OCSP_RESPONSE_free, (res)); \ +} while (0) +#define GetOCSPRes(obj, res) do { \ + Data_Get_Struct((obj), OCSP_RESPONSE, (res)); \ + if(!(res)) ossl_raise(rb_eRuntimeError, "Response wasn't initialized!"); \ +} while (0) +#define SafeGetOCSPRes(obj, res) do { \ + OSSL_Check_Kind((obj), cOCSPRes); \ + GetOCSPRes((obj), (res)); \ +} while (0) + +#define WrapOCSPBasicRes(klass, obj, res) do { \ + if(!(res)) ossl_raise(rb_eRuntimeError, "Response wasn't initialized!"); \ + (obj) = Data_Wrap_Struct((klass), 0, OCSP_BASICRESP_free, (res)); \ +} while (0) +#define GetOCSPBasicRes(obj, res) do { \ + Data_Get_Struct((obj), OCSP_BASICRESP, (res)); \ + if(!(res)) ossl_raise(rb_eRuntimeError, "Response wasn't initialized!"); \ +} while (0) +#define SafeGetOCSPBasicRes(obj, res) do { \ + OSSL_Check_Kind((obj), cOCSPBasicRes); \ + GetOCSPBasicRes((obj), (res)); \ +} while (0) + +#define WrapOCSPCertId(klass, obj, cid) do { \ + if(!(cid)) ossl_raise(rb_eRuntimeError, "Cert ID wasn't initialized!"); \ + (obj) = Data_Wrap_Struct((klass), 0, OCSP_CERTID_free, (cid)); \ +} while (0) +#define GetOCSPCertId(obj, cid) do { \ + Data_Get_Struct((obj), OCSP_CERTID, (cid)); \ + if(!(cid)) ossl_raise(rb_eRuntimeError, "Cert ID wasn't initialized!"); \ +} while (0) +#define SafeGetOCSPCertId(obj, cid) do { \ + OSSL_Check_Kind((obj), cOCSPCertId); \ + GetOCSPCertId((obj), (cid)); \ +} while (0) + +VALUE mOCSP; +VALUE eOCSPError; +VALUE cOCSPReq; +VALUE cOCSPRes; +VALUE cOCSPBasicRes; +VALUE cOCSPCertId; + +/* + * Public + */ +static VALUE +ossl_ocspcertid_new(OCSP_CERTID *cid) +{ + VALUE obj; + WrapOCSPCertId(cOCSPCertId, obj, cid); + return obj; +} + +/* + * OCSP::Resquest + */ +static VALUE +ossl_ocspreq_alloc(VALUE klass) +{ + OCSP_REQUEST *req; + VALUE obj; + + if (!(req = OCSP_REQUEST_new())) + ossl_raise(eOCSPError, NULL); + WrapOCSPReq(klass, obj, req); + + return obj; +} + +/* + * call-seq: + * OpenSSL::OCSP::Request.new -> request + * OpenSSL::OCSP::Request.new(request_der) -> request + * + * Creates a new OpenSSL::OCSP::Request. The request may be created empty or + * from a +request_der+ string. + */ + +static VALUE +ossl_ocspreq_initialize(int argc, VALUE *argv, VALUE self) +{ + VALUE arg; + const unsigned char *p; + + rb_scan_args(argc, argv, "01", &arg); + if(!NIL_P(arg)){ + OCSP_REQUEST *req = DATA_PTR(self), *x; + arg = ossl_to_der_if_possible(arg); + StringValue(arg); + p = (unsigned char*)RSTRING_PTR(arg); + x = d2i_OCSP_REQUEST(&req, &p, RSTRING_LEN(arg)); + DATA_PTR(self) = req; + if(!x){ + ossl_raise(eOCSPError, "cannot load DER encoded request"); + } + } + + return self; +} + +/* + * call-seq: + * request.add_nonce(nonce = nil) -> request + * + * Adds a +nonce+ to the OCSP request. If no nonce is given a random one will + * be generated. + * + * The nonce is used to prevent replay attacks but some servers do not support + * it. + */ + +static VALUE +ossl_ocspreq_add_nonce(int argc, VALUE *argv, VALUE self) +{ + OCSP_REQUEST *req; + VALUE val; + int ret; + + rb_scan_args(argc, argv, "01", &val); + if(NIL_P(val)) { + GetOCSPReq(self, req); + ret = OCSP_request_add1_nonce(req, NULL, -1); + } + else{ + StringValue(val); + GetOCSPReq(self, req); + ret = OCSP_request_add1_nonce(req, (unsigned char *)RSTRING_PTR(val), RSTRING_LENINT(val)); + } + if(!ret) ossl_raise(eOCSPError, NULL); + + return self; +} + +/* + * call-seq: + * request.check_nonce(response) -> result + * + * Checks the nonce validity for this request and +response+. + * + * The return value is one of the following: + * + * -1 :: nonce in request only. + * 0 :: nonces both present and not equal. + * 1 :: nonces present and equal. + * 2 :: nonces both absent. + * 3 :: nonce present in response only. + * + * For most responses, clients can check +result+ > 0. If a responder doesn't + * handle nonces <code>result.nonzero?</code> may be necessary. A result of + * <code>0</code> is always an error. + */ + +static VALUE +ossl_ocspreq_check_nonce(VALUE self, VALUE basic_resp) +{ + OCSP_REQUEST *req; + OCSP_BASICRESP *bs; + int res; + + GetOCSPReq(self, req); + SafeGetOCSPBasicRes(basic_resp, bs); + res = OCSP_check_nonce(req, bs); + + return INT2NUM(res); +} + +/* + * call-seq: + * request.add_certid(certificate_id) -> request + * + * Adds +certificate_id+ to the request. + */ + +static VALUE +ossl_ocspreq_add_certid(VALUE self, VALUE certid) +{ + OCSP_REQUEST *req; + OCSP_CERTID *id; + + GetOCSPReq(self, req); + GetOCSPCertId(certid, id); + if(!OCSP_request_add0_id(req, OCSP_CERTID_dup(id))) + ossl_raise(eOCSPError, NULL); + + return self; +} + +/* + * call-seq: + * request.certid -> [certificate_id, ...] + * + * Returns all certificate IDs in this request. + */ + +static VALUE +ossl_ocspreq_get_certid(VALUE self) +{ + OCSP_REQUEST *req; + OCSP_ONEREQ *one; + OCSP_CERTID *id; + VALUE ary, tmp; + int i, count; + + GetOCSPReq(self, req); + count = OCSP_request_onereq_count(req); + ary = (count > 0) ? rb_ary_new() : Qnil; + for(i = 0; i < count; i++){ + one = OCSP_request_onereq_get0(req, i); + if(!(id = OCSP_CERTID_dup(OCSP_onereq_get0_id(one)))) + ossl_raise(eOCSPError, NULL); + WrapOCSPCertId(cOCSPCertId, tmp, id); + rb_ary_push(ary, tmp); + } + + return ary; +} + +/* + * call-seq: + * request.sign(signer_cert, signer_key) -> self + * request.sign(signer_cert, signer_key, certificates) -> self + * request.sign(signer_cert, signer_key, certificates, flags) -> self + * + * Signs this OCSP request using +signer_cert+ and +signer_key+. + * +certificates+ is an optional Array of certificates that may be included in + * the request. + */ + +static VALUE +ossl_ocspreq_sign(int argc, VALUE *argv, VALUE self) +{ + VALUE signer_cert, signer_key, certs, flags; + OCSP_REQUEST *req; + X509 *signer; + EVP_PKEY *key; + STACK_OF(X509) *x509s; + unsigned long flg; + int ret; + + rb_scan_args(argc, argv, "22", &signer_cert, &signer_key, &certs, &flags); + signer = GetX509CertPtr(signer_cert); + key = GetPrivPKeyPtr(signer_key); + flg = NIL_P(flags) ? 0 : NUM2INT(flags); + if(NIL_P(certs)){ + x509s = sk_X509_new_null(); + flags |= OCSP_NOCERTS; + } + else x509s = ossl_x509_ary2sk(certs); + GetOCSPReq(self, req); + ret = OCSP_request_sign(req, signer, key, EVP_sha1(), x509s, flg); + sk_X509_pop_free(x509s, X509_free); + if(!ret) ossl_raise(eOCSPError, NULL); + + return self; +} + +/* + * call-seq: + * request.verify(certificates, store) -> true or false + * request.verify(certificates, store, flags) -> true or false + * + * Verifies this request using the given +certificates+ and X509 +store+. + */ + +static VALUE +ossl_ocspreq_verify(int argc, VALUE *argv, VALUE self) +{ + VALUE certs, store, flags; + OCSP_REQUEST *req; + STACK_OF(X509) *x509s; + X509_STORE *x509st; + int flg, result; + + rb_scan_args(argc, argv, "21", &certs, &store, &flags); + x509st = GetX509StorePtr(store); + flg = NIL_P(flags) ? 0 : NUM2INT(flags); + x509s = ossl_x509_ary2sk(certs); + GetOCSPReq(self, req); + result = OCSP_request_verify(req, x509s, x509st, flg); + sk_X509_pop_free(x509s, X509_free); + if(!result) rb_warn("%s", ERR_error_string(ERR_peek_error(), NULL)); + + return result ? Qtrue : Qfalse; +} + +/* + * Returns this request as a DER-encoded string + */ + +static VALUE +ossl_ocspreq_to_der(VALUE self) +{ + OCSP_REQUEST *req; + VALUE str; + unsigned char *p; + long len; + + GetOCSPReq(self, req); + if((len = i2d_OCSP_REQUEST(req, NULL)) <= 0) + ossl_raise(eOCSPError, NULL); + str = rb_str_new(0, len); + p = (unsigned char *)RSTRING_PTR(str); + if(i2d_OCSP_REQUEST(req, &p) <= 0) + ossl_raise(eOCSPError, NULL); + ossl_str_adjust(str, p); + + return str; +} + +/* + * OCSP::Response + */ + +/* call-seq: + * OpenSSL::OCSP::Response.create(status, basic_response = nil) -> response + * + * Creates an OpenSSL::OCSP::Response from +status+ and +basic_response+. + */ + +static VALUE +ossl_ocspres_s_create(VALUE klass, VALUE status, VALUE basic_resp) +{ + OCSP_BASICRESP *bs; + OCSP_RESPONSE *res; + VALUE obj; + int st = NUM2INT(status); + + if(NIL_P(basic_resp)) bs = NULL; + else GetOCSPBasicRes(basic_resp, bs); /* NO NEED TO DUP */ + if(!(res = OCSP_response_create(st, bs))) + ossl_raise(eOCSPError, NULL); + WrapOCSPRes(klass, obj, res); + + return obj; +} + +static VALUE +ossl_ocspres_alloc(VALUE klass) +{ + OCSP_RESPONSE *res; + VALUE obj; + + if(!(res = OCSP_RESPONSE_new())) + ossl_raise(eOCSPError, NULL); + WrapOCSPRes(klass, obj, res); + + return obj; +} + +/* + * call-seq: + * OpenSSL::OCSP::Response.new -> response + * OpenSSL::OCSP::Response.new(response_der) -> response + * + * Creates a new OpenSSL::OCSP::Response. The response may be created empty or + * from a +response_der+ string. + */ + +static VALUE +ossl_ocspres_initialize(int argc, VALUE *argv, VALUE self) +{ + VALUE arg; + const unsigned char *p; + + rb_scan_args(argc, argv, "01", &arg); + if(!NIL_P(arg)){ + OCSP_RESPONSE *res = DATA_PTR(self), *x; + arg = ossl_to_der_if_possible(arg); + StringValue(arg); + p = (unsigned char *)RSTRING_PTR(arg); + x = d2i_OCSP_RESPONSE(&res, &p, RSTRING_LEN(arg)); + DATA_PTR(self) = res; + if(!x){ + ossl_raise(eOCSPError, "cannot load DER encoded response"); + } + } + + return self; +} + +/* + * call-seq: + * response.status -> Integer + * + * Returns the status of the response. + */ + +static VALUE +ossl_ocspres_status(VALUE self) +{ + OCSP_RESPONSE *res; + int st; + + GetOCSPRes(self, res); + st = OCSP_response_status(res); + + return INT2NUM(st); +} + +/* + * call-seq: + * response.status_string -> String + * + * Returns a status string for the response. + */ + +static VALUE +ossl_ocspres_status_string(VALUE self) +{ + OCSP_RESPONSE *res; + int st; + + GetOCSPRes(self, res); + st = OCSP_response_status(res); + + return rb_str_new2(OCSP_response_status_str(st)); +} + +/* + * call-seq: + * response.basic + * + * Returns a BasicResponse for this response + */ + +static VALUE +ossl_ocspres_get_basic(VALUE self) +{ + OCSP_RESPONSE *res; + OCSP_BASICRESP *bs; + VALUE ret; + + GetOCSPRes(self, res); + if(!(bs = OCSP_response_get1_basic(res))) + return Qnil; + WrapOCSPBasicRes(cOCSPBasicRes, ret, bs); + + return ret; +} + +/* + * call-seq: + * response.to_der -> String + * + * Returns this response as a DER-encoded string. + */ + +static VALUE +ossl_ocspres_to_der(VALUE self) +{ + OCSP_RESPONSE *res; + VALUE str; + long len; + unsigned char *p; + + GetOCSPRes(self, res); + if((len = i2d_OCSP_RESPONSE(res, NULL)) <= 0) + ossl_raise(eOCSPError, NULL); + str = rb_str_new(0, len); + p = (unsigned char *)RSTRING_PTR(str); + if(i2d_OCSP_RESPONSE(res, &p) <= 0) + ossl_raise(eOCSPError, NULL); + ossl_str_adjust(str, p); + + return str; +} + +/* + * OCSP::BasicResponse + */ +static VALUE +ossl_ocspbres_alloc(VALUE klass) +{ + OCSP_BASICRESP *bs; + VALUE obj; + + if(!(bs = OCSP_BASICRESP_new())) + ossl_raise(eOCSPError, NULL); + WrapOCSPBasicRes(klass, obj, bs); + + return obj; +} + +/* + * call-seq: + * OpenSSL::OCSP::BasicResponse.new(*) -> basic_response + * + * Creates a new BasicResponse and ignores all arguments. + */ + +static VALUE +ossl_ocspbres_initialize(int argc, VALUE *argv, VALUE self) +{ + return self; +} + +/* + * call-seq: + * basic_response.copy_nonce(request) -> Integer + * + * Copies the nonce from +request+ into this response. Returns 1 on success + * and 0 on failure. + */ + +static VALUE +ossl_ocspbres_copy_nonce(VALUE self, VALUE request) +{ + OCSP_BASICRESP *bs; + OCSP_REQUEST *req; + int ret; + + GetOCSPBasicRes(self, bs); + SafeGetOCSPReq(request, req); + ret = OCSP_copy_nonce(bs, req); + + return INT2NUM(ret); +} + +/* + * call-seq: + * basic_response.add_nonce(nonce = nil) + * + * Adds +nonce+ to this response. If no nonce was provided a random nonce + * will be added. + */ + +static VALUE +ossl_ocspbres_add_nonce(int argc, VALUE *argv, VALUE self) +{ + OCSP_BASICRESP *bs; + VALUE val; + int ret; + + rb_scan_args(argc, argv, "01", &val); + if(NIL_P(val)) { + GetOCSPBasicRes(self, bs); + ret = OCSP_basic_add1_nonce(bs, NULL, -1); + } + else{ + StringValue(val); + GetOCSPBasicRes(self, bs); + ret = OCSP_basic_add1_nonce(bs, (unsigned char *)RSTRING_PTR(val), RSTRING_LENINT(val)); + } + if(!ret) ossl_raise(eOCSPError, NULL); + + return self; +} + +/* + * call-seq: + * basic_response.add_status(certificate_id, status, reason, revocation_time, this_update, next_update, extensions) -> basic_response + * + * Adds a validation +status+ (0 for revoked, 1 for success) to this + * response for +certificate_id+. +reason+ describes the reason for the + * revocation, if any. + * + * The +revocation_time+, +this_update+ and +next_update+ are times for the + * certificate's revocation time, the time of this status and the next update + * time for a new status, respectively. + * + * +extensions+ may be an Array of OpenSSL::X509::Extension that will + * be added to this response or nil. + */ + +static VALUE +ossl_ocspbres_add_status(VALUE self, VALUE cid, VALUE status, + VALUE reason, VALUE revtime, + VALUE thisupd, VALUE nextupd, VALUE ext) +{ + OCSP_BASICRESP *bs; + OCSP_SINGLERESP *single; + OCSP_CERTID *id; + int st, rsn; + ASN1_TIME *ths, *nxt, *rev; + int error, i, rstatus = 0; + VALUE tmp; + + st = NUM2INT(status); + rsn = NIL_P(status) ? 0 : NUM2INT(reason); + if(!NIL_P(ext)){ + /* All ary's members should be X509Extension */ + Check_Type(ext, T_ARRAY); + for (i = 0; i < RARRAY_LEN(ext); i++) + OSSL_Check_Kind(RARRAY_PTR(ext)[i], cX509Ext); + } + + error = 0; + ths = nxt = rev = NULL; + if(!NIL_P(revtime)){ + tmp = rb_protect(rb_Integer, revtime, &rstatus); + if(rstatus) goto err; + rev = X509_gmtime_adj(NULL, NUM2INT(tmp)); + } + tmp = rb_protect(rb_Integer, thisupd, &rstatus); + if(rstatus) goto err; + ths = X509_gmtime_adj(NULL, NUM2INT(tmp)); + tmp = rb_protect(rb_Integer, nextupd, &rstatus); + if(rstatus) goto err; + nxt = X509_gmtime_adj(NULL, NUM2INT(tmp)); + + GetOCSPBasicRes(self, bs); + SafeGetOCSPCertId(cid, id); + if(!(single = OCSP_basic_add1_status(bs, id, st, rsn, rev, ths, nxt))){ + error = 1; + goto err; + } + + if(!NIL_P(ext)){ + X509_EXTENSION *x509ext; + sk_X509_EXTENSION_pop_free(single->singleExtensions, X509_EXTENSION_free); + single->singleExtensions = NULL; + for(i = 0; i < RARRAY_LEN(ext); i++){ + x509ext = DupX509ExtPtr(RARRAY_PTR(ext)[i]); + if(!OCSP_SINGLERESP_add_ext(single, x509ext, -1)){ + X509_EXTENSION_free(x509ext); + error = 1; + goto err; + } + X509_EXTENSION_free(x509ext); + } + } + + err: + ASN1_TIME_free(ths); + ASN1_TIME_free(nxt); + ASN1_TIME_free(rev); + if(error) ossl_raise(eOCSPError, NULL); + if(rstatus) rb_jump_tag(rstatus); + + return self; +} + +/* + * call-seq: + * basic_response.status -> statuses + * + * Returns an Array of statuses for this response. Each status contains a + * CertificateId, the status (0 for success, 1 for revoked), the reason for + * the status, the revocation time, the time of this update, the time for the + * next update and a list of OpenSSL::X509::Extensions. + */ + +static VALUE +ossl_ocspbres_get_status(VALUE self) +{ + OCSP_BASICRESP *bs; + OCSP_SINGLERESP *single; + OCSP_CERTID *cid; + ASN1_TIME *revtime, *thisupd, *nextupd; + int status, reason; + X509_EXTENSION *x509ext; + VALUE ret, ary, ext; + int count, ext_count, i, j; + + GetOCSPBasicRes(self, bs); + ret = rb_ary_new(); + count = OCSP_resp_count(bs); + for(i = 0; i < count; i++){ + single = OCSP_resp_get0(bs, i); + if(!single) continue; + + revtime = thisupd = nextupd = NULL; + status = OCSP_single_get0_status(single, &reason, &revtime, + &thisupd, &nextupd); + if(status < 0) continue; + if(!(cid = OCSP_CERTID_dup(single->certId))) + ossl_raise(eOCSPError, NULL); + ary = rb_ary_new(); + rb_ary_push(ary, ossl_ocspcertid_new(cid)); + rb_ary_push(ary, INT2NUM(status)); + rb_ary_push(ary, INT2NUM(reason)); + rb_ary_push(ary, revtime ? asn1time_to_time(revtime) : Qnil); + rb_ary_push(ary, thisupd ? asn1time_to_time(thisupd) : Qnil); + rb_ary_push(ary, nextupd ? asn1time_to_time(nextupd) : Qnil); + ext = rb_ary_new(); + ext_count = OCSP_SINGLERESP_get_ext_count(single); + for(j = 0; j < ext_count; j++){ + x509ext = OCSP_SINGLERESP_get_ext(single, j); + rb_ary_push(ext, ossl_x509ext_new(x509ext)); + } + rb_ary_push(ary, ext); + rb_ary_push(ret, ary); + } + + return ret; +} + +/* + * call-seq: + * basic_response.sign(signer_cert, signer_key) -> self + * basic_response.sign(signer_cert, signer_key, certificates) -> self + * basic_response.sign(signer_cert, signer_key, certificates, flags) -> self + * + * Signs this response using the +signer_cert+ and +signer_key+. Additional + * +certificates+ may be added to the signature along with a set of +flags+. + */ + +static VALUE +ossl_ocspbres_sign(int argc, VALUE *argv, VALUE self) +{ + VALUE signer_cert, signer_key, certs, flags; + OCSP_BASICRESP *bs; + X509 *signer; + EVP_PKEY *key; + STACK_OF(X509) *x509s; + unsigned long flg; + int ret; + + rb_scan_args(argc, argv, "22", &signer_cert, &signer_key, &certs, &flags); + signer = GetX509CertPtr(signer_cert); + key = GetPrivPKeyPtr(signer_key); + flg = NIL_P(flags) ? 0 : NUM2INT(flags); + if(NIL_P(certs)){ + x509s = sk_X509_new_null(); + flg |= OCSP_NOCERTS; + } + else{ + x509s = ossl_x509_ary2sk(certs); + } + GetOCSPBasicRes(self, bs); + ret = OCSP_basic_sign(bs, signer, key, EVP_sha1(), x509s, flg); + sk_X509_pop_free(x509s, X509_free); + if(!ret) ossl_raise(eOCSPError, NULL); + + return self; +} + +/* + * call-seq: + * basic_response.verify(certificates, store) -> true or false + * basic_response.verify(certificates, store, flags) -> true or false + * + * Verifies the signature of the response using the given +certificates+, + * +store+ and +flags+. + */ +static VALUE +ossl_ocspbres_verify(int argc, VALUE *argv, VALUE self) +{ + VALUE certs, store, flags, result; + OCSP_BASICRESP *bs; + STACK_OF(X509) *x509s; + X509_STORE *x509st; + int flg; + + rb_scan_args(argc, argv, "21", &certs, &store, &flags); + x509st = GetX509StorePtr(store); + flg = NIL_P(flags) ? 0 : NUM2INT(flags); + x509s = ossl_x509_ary2sk(certs); + GetOCSPBasicRes(self, bs); + result = OCSP_basic_verify(bs, x509s, x509st, flg) > 0 ? Qtrue : Qfalse; + sk_X509_pop_free(x509s, X509_free); + if(!result) rb_warn("%s", ERR_error_string(ERR_peek_error(), NULL)); + + return result; +} + +/* + * OCSP::CertificateId + */ +static VALUE +ossl_ocspcid_alloc(VALUE klass) +{ + OCSP_CERTID *id; + VALUE obj; + + if(!(id = OCSP_CERTID_new())) + ossl_raise(eOCSPError, NULL); + WrapOCSPCertId(klass, obj, id); + + return obj; +} + +/* + * call-seq: + * OpenSSL::OCSP::CertificateId.new(subject, issuer, digest = nil) -> certificate_id + * + * Creates a new OpenSSL::OCSP::CertificateId for the given +subject+ and + * +issuer+ X509 certificates. The +digest+ is used to compute the + * certificate ID and must be an OpenSSL::Digest instance. + */ + +static VALUE +ossl_ocspcid_initialize(int argc, VALUE *argv, VALUE self) +{ + OCSP_CERTID *id, *newid; + X509 *x509s, *x509i; + VALUE subject, issuer, digest; + const EVP_MD *md; + + if (rb_scan_args(argc, argv, "21", &subject, &issuer, &digest) == 0) { + return self; + } + + x509s = GetX509CertPtr(subject); /* NO NEED TO DUP */ + x509i = GetX509CertPtr(issuer); /* NO NEED TO DUP */ + + if (!NIL_P(digest)) { + md = GetDigestPtr(digest); + newid = OCSP_cert_to_id(md, x509s, x509i); + } else { + newid = OCSP_cert_to_id(NULL, x509s, x509i); + } + if(!newid) + ossl_raise(eOCSPError, NULL); + GetOCSPCertId(self, id); + OCSP_CERTID_free(id); + RDATA(self)->data = newid; + + return self; +} + +/* + * call-seq: + * certificate_id.cmp(other) -> true or false + * + * Compares this certificate id with +other+ and returns true if they are the + * same. + */ +static VALUE +ossl_ocspcid_cmp(VALUE self, VALUE other) +{ + OCSP_CERTID *id, *id2; + int result; + + GetOCSPCertId(self, id); + SafeGetOCSPCertId(other, id2); + result = OCSP_id_cmp(id, id2); + + return (result == 0) ? Qtrue : Qfalse; +} + +/* + * call-seq: + * certificate_id.cmp_issuer(other) -> true or false + * + * Compares this certificate id's issuer with +other+ and returns true if + * they are the same. + */ + +static VALUE +ossl_ocspcid_cmp_issuer(VALUE self, VALUE other) +{ + OCSP_CERTID *id, *id2; + int result; + + GetOCSPCertId(self, id); + SafeGetOCSPCertId(other, id2); + result = OCSP_id_issuer_cmp(id, id2); + + return (result == 0) ? Qtrue : Qfalse; +} + +/* + * call-seq: + * certificate_id.get_serial -> Integer + * + * Returns the serial number of the issuing certificate. + */ + +static VALUE +ossl_ocspcid_get_serial(VALUE self) +{ + OCSP_CERTID *id; + + GetOCSPCertId(self, id); + + return asn1integer_to_num(id->serialNumber); +} + +void +Init_ossl_ocsp(void) +{ + /* + * OpenSSL::OCSP implements Online Certificate Status Protocol requests + * and responses. + * + * Creating and sending an OCSP request requires a subject certificate + * that contains an OCSP URL in an authorityInfoAccess extension and the + * issuer certificate for the subject certificate. First, load the issuer + * and subject certificates: + * + * subject = OpenSSL::X509::Certificate.new subject_pem + * issuer = OpenSSL::X509::Certificate.new issuer_pem + * + * To create the request we need to create a certificate ID for the + * subject certificate so the CA knows which certificate we are asking + * about: + * + * digest = OpenSSL::Digest::SHA1.new + * certificate_id = + * OpenSSL::OCSP::CertificateId.new subject, issuer, digest + * + * Then create a request and add the certificate ID to it: + * + * request = OpenSSL::OCSP::Request.new + * request.add_certid certificate_id + * + * Adding a nonce to the request protects against replay attacks but not + * all CA process the nonce. + * + * request.add_nonce + * + * To submit the request to the CA for verification we need to extract the + * OCSP URI from the subject certificate: + * + * authority_info_access = subject.extensions.find do |extension| + * extension.oid == 'authorityInfoAccess' + * end + * + * descriptions = authority_info_access.value.split "\n" + * ocsp = descriptions.find do |description| + * description.start_with? 'OCSP' + * end + * + * require 'uri' + * + * ocsp_uri = URI ocsp[/URI:(.*)/, 1] + * + * To submit the request we'll POST the request to the OCSP URI (per RFC + * 2560). Note that we only handle HTTP requests and don't handle any + * redirects in this example, so this is insufficient for serious use. + * + * require 'net/http' + * + * http_response = + * Net::HTTP.start ocsp_uri.hostname, ocsp.port do |http| + * http.post ocsp_uri.path, request.to_der, + * 'content-type' => 'application/ocsp-request' + * end + * + * response = OpenSSL::OCSP::Response.new http_response.body + * response_basic = response.basic + * + * First we check if the response has a valid signature. Without a valid + * signature we cannot trust it. If you get a failure here you may be + * missing a system certificate store or may be missing the intermediate + * certificates. + * + * store = OpenSSL::X509::Store.new + * store.set_default_paths + * + * unless response.verify [], store then + * raise 'response is not signed by a trusted certificate' + * end + * + * The response contains the status information (success/fail). We can + * display the status as a string: + * + * puts response.status_string #=> successful + * + * Next we need to know the response details to determine if the response + * matches our request. First we check the nonce. Again, not all CAs + * support a nonce. See Request#check_nonce for the meanings of the + * return values. + * + * p request.check_nonce basic_response #=> value from -1 to 3 + * + * Then extract the status information from the basic response. (You can + * check multiple certificates in a request, but for this example we only + * submitted one.) + * + * response_certificate_id, status, reason, revocation_time, + * this_update, next_update, extensions = basic_response.status + * + * Then check the various fields. + * + * unless response_certificate_id == certificate_id then + * raise 'certificate id mismatch' + * end + * + * now = Time.now + * + * if this_update > now then + * raise 'update date is in the future' + * end + * + * if now > next_update then + * raise 'next update time has passed' + * end + */ + + mOCSP = rb_define_module_under(mOSSL, "OCSP"); + + /* + * OCSP error class. + */ + + eOCSPError = rb_define_class_under(mOCSP, "OCSPError", eOSSLError); + + /* + * An OpenSSL::OCSP::Request contains the certificate information for + * determining if a certificate has been revoked or not. A Request can be + * created for a certificate or from a DER-encoded request created + * elsewhere. + */ + + cOCSPReq = rb_define_class_under(mOCSP, "Request", rb_cObject); + rb_define_alloc_func(cOCSPReq, ossl_ocspreq_alloc); + rb_define_method(cOCSPReq, "initialize", ossl_ocspreq_initialize, -1); + rb_define_method(cOCSPReq, "add_nonce", ossl_ocspreq_add_nonce, -1); + rb_define_method(cOCSPReq, "check_nonce", ossl_ocspreq_check_nonce, 1); + rb_define_method(cOCSPReq, "add_certid", ossl_ocspreq_add_certid, 1); + rb_define_method(cOCSPReq, "certid", ossl_ocspreq_get_certid, 0); + rb_define_method(cOCSPReq, "sign", ossl_ocspreq_sign, -1); + rb_define_method(cOCSPReq, "verify", ossl_ocspreq_verify, -1); + rb_define_method(cOCSPReq, "to_der", ossl_ocspreq_to_der, 0); + + /* + * An OpenSSL::OCSP::Response contains the status of a certificate check + * which is created from an OpenSSL::OCSP::Request. + */ + + cOCSPRes = rb_define_class_under(mOCSP, "Response", rb_cObject); + rb_define_singleton_method(cOCSPRes, "create", ossl_ocspres_s_create, 2); + rb_define_alloc_func(cOCSPRes, ossl_ocspres_alloc); + rb_define_method(cOCSPRes, "initialize", ossl_ocspres_initialize, -1); + rb_define_method(cOCSPRes, "status", ossl_ocspres_status, 0); + rb_define_method(cOCSPRes, "status_string", ossl_ocspres_status_string, 0); + rb_define_method(cOCSPRes, "basic", ossl_ocspres_get_basic, 0); + rb_define_method(cOCSPRes, "to_der", ossl_ocspres_to_der, 0); + + /* + * An OpenSSL::OCSP::BasicResponse contains the status of a certificate + * check which is created from an OpenSSL::OCSP::Request. A + * BasicResponse is more detailed than a Response. + */ + + cOCSPBasicRes = rb_define_class_under(mOCSP, "BasicResponse", rb_cObject); + rb_define_alloc_func(cOCSPBasicRes, ossl_ocspbres_alloc); + rb_define_method(cOCSPBasicRes, "initialize", ossl_ocspbres_initialize, -1); + rb_define_method(cOCSPBasicRes, "copy_nonce", ossl_ocspbres_copy_nonce, 1); + rb_define_method(cOCSPBasicRes, "add_nonce", ossl_ocspbres_add_nonce, -1); + rb_define_method(cOCSPBasicRes, "add_status", ossl_ocspbres_add_status, 7); + rb_define_method(cOCSPBasicRes, "status", ossl_ocspbres_get_status, 0); + rb_define_method(cOCSPBasicRes, "sign", ossl_ocspbres_sign, -1); + rb_define_method(cOCSPBasicRes, "verify", ossl_ocspbres_verify, -1); + + /* + * An OpenSSL::OCSP::CertificateId identifies a certificate to the CA so + * that a status check can be performed. + */ + + cOCSPCertId = rb_define_class_under(mOCSP, "CertificateId", rb_cObject); + rb_define_alloc_func(cOCSPCertId, ossl_ocspcid_alloc); + rb_define_method(cOCSPCertId, "initialize", ossl_ocspcid_initialize, -1); + rb_define_method(cOCSPCertId, "cmp", ossl_ocspcid_cmp, 1); + rb_define_method(cOCSPCertId, "cmp_issuer", ossl_ocspcid_cmp_issuer, 1); + rb_define_method(cOCSPCertId, "serial", ossl_ocspcid_get_serial, 0); + + /* Internal error in issuer */ + rb_define_const(mOCSP, "RESPONSE_STATUS_INTERNALERROR", INT2NUM(OCSP_RESPONSE_STATUS_INTERNALERROR)); + + /* Illegal confirmation request */ + rb_define_const(mOCSP, "RESPONSE_STATUS_MALFORMEDREQUEST", INT2NUM(OCSP_RESPONSE_STATUS_MALFORMEDREQUEST)); + + /* The certificate was revoked for an unknown reason */ + rb_define_const(mOCSP, "REVOKED_STATUS_NOSTATUS", INT2NUM(OCSP_REVOKED_STATUS_NOSTATUS)); + + /* You must sign the request and resubmit */ + rb_define_const(mOCSP, "RESPONSE_STATUS_SIGREQUIRED", INT2NUM(OCSP_RESPONSE_STATUS_SIGREQUIRED)); + + /* Response has valid confirmations */ + rb_define_const(mOCSP, "RESPONSE_STATUS_SUCCESSFUL", INT2NUM(OCSP_RESPONSE_STATUS_SUCCESSFUL)); + + /* Try again later */ + rb_define_const(mOCSP, "RESPONSE_STATUS_TRYLATER", INT2NUM(OCSP_RESPONSE_STATUS_TRYLATER)); + + /* The certificate subject's name or other information changed */ + rb_define_const(mOCSP, "REVOKED_STATUS_AFFILIATIONCHANGED", INT2NUM(OCSP_REVOKED_STATUS_AFFILIATIONCHANGED)); + + /* This CA certificate was revoked due to a key compromise */ + rb_define_const(mOCSP, "REVOKED_STATUS_CACOMPROMISE", INT2NUM(OCSP_REVOKED_STATUS_CACOMPROMISE)); + + /* The certificate is on hold */ + rb_define_const(mOCSP, "REVOKED_STATUS_CERTIFICATEHOLD", INT2NUM(OCSP_REVOKED_STATUS_CERTIFICATEHOLD)); + + /* The certificate is no longer needed */ + rb_define_const(mOCSP, "REVOKED_STATUS_CESSATIONOFOPERATION", INT2NUM(OCSP_REVOKED_STATUS_CESSATIONOFOPERATION)); + + /* The certificate was revoked due to a key compromise */ + rb_define_const(mOCSP, "REVOKED_STATUS_KEYCOMPROMISE", INT2NUM(OCSP_REVOKED_STATUS_KEYCOMPROMISE)); + + /* The certificate was previously on hold and should now be removed from + * the CRL */ + rb_define_const(mOCSP, "REVOKED_STATUS_REMOVEFROMCRL", INT2NUM(OCSP_REVOKED_STATUS_REMOVEFROMCRL)); + + /* The certificate was superseded by a new certificate */ + rb_define_const(mOCSP, "REVOKED_STATUS_SUPERSEDED", INT2NUM(OCSP_REVOKED_STATUS_SUPERSEDED)); + + /* Your request is unauthorized. */ + rb_define_const(mOCSP, "RESPONSE_STATUS_UNAUTHORIZED", INT2NUM(OCSP_RESPONSE_STATUS_UNAUTHORIZED)); + + /* The certificate was revoked for an unspecified reason */ + rb_define_const(mOCSP, "REVOKED_STATUS_UNSPECIFIED", INT2NUM(OCSP_REVOKED_STATUS_UNSPECIFIED)); + + /* Do not include certificates in the response */ + rb_define_const(mOCSP, "NOCERTS", INT2NUM(OCSP_NOCERTS)); + + /* Do not search certificates contained in the response for a signer */ + rb_define_const(mOCSP, "NOINTERN", INT2NUM(OCSP_NOINTERN)); + + /* Do not check the signature on the response */ + rb_define_const(mOCSP, "NOSIGS", INT2NUM(OCSP_NOSIGS)); + + /* Do not verify the certificate chain on the response */ + rb_define_const(mOCSP, "NOCHAIN", INT2NUM(OCSP_NOCHAIN)); + + /* Do not verify the response at all */ + rb_define_const(mOCSP, "NOVERIFY", INT2NUM(OCSP_NOVERIFY)); + + /* Do not check trust */ + rb_define_const(mOCSP, "NOEXPLICIT", INT2NUM(OCSP_NOEXPLICIT)); + + /* (This flag is not used by OpenSSL 1.0.1g) */ + rb_define_const(mOCSP, "NOCASIGN", INT2NUM(OCSP_NOCASIGN)); + + /* (This flag is not used by OpenSSL 1.0.1g) */ + rb_define_const(mOCSP, "NODELEGATED", INT2NUM(OCSP_NODELEGATED)); + + /* Do not make additional signing certificate checks */ + rb_define_const(mOCSP, "NOCHECKS", INT2NUM(OCSP_NOCHECKS)); + + /* Do not verify additional certificates */ + rb_define_const(mOCSP, "TRUSTOTHER", INT2NUM(OCSP_TRUSTOTHER)); + + /* Identify the response by signing the certificate key ID */ + rb_define_const(mOCSP, "RESPID_KEY", INT2NUM(OCSP_RESPID_KEY)); + + /* Do not include producedAt time in response */ + rb_define_const(mOCSP, "NOTIME", INT2NUM(OCSP_NOTIME)); + + /* Indicates the certificate is not revoked but does not necessarily mean + * the certificate was issued or that this response is within the + * certificate's validity interval */ + rb_define_const(mOCSP, "V_CERTSTATUS_GOOD", INT2NUM(V_OCSP_CERTSTATUS_GOOD)); + /* Indicates the certificate has been revoked either permanently or + * temporarily (on hold). */ + rb_define_const(mOCSP, "V_CERTSTATUS_REVOKED", INT2NUM(V_OCSP_CERTSTATUS_REVOKED)); + + /* Indicates the responder does not know about the certificate being + * requested. */ + rb_define_const(mOCSP, "V_CERTSTATUS_UNKNOWN", INT2NUM(V_OCSP_CERTSTATUS_UNKNOWN)); + + /* The responder ID is based on the key name. */ + rb_define_const(mOCSP, "V_RESPID_NAME", INT2NUM(V_OCSP_RESPID_NAME)); + + /* The responder ID is based on the public key. */ + rb_define_const(mOCSP, "V_RESPID_KEY", INT2NUM(V_OCSP_RESPID_KEY)); +} + +#else /* ! OSSL_OCSP_ENABLED */ +void +Init_ossl_ocsp(void) +{ +} +#endif diff --git a/ext/openssl/ossl_ocsp.h b/ext/openssl/ossl_ocsp.h new file mode 100644 index 00000000..65b4f2e2 --- /dev/null +++ b/ext/openssl/ossl_ocsp.h @@ -0,0 +1,24 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2003 Michal Rokos <m.rokos@sh.cvut.cz> + * Copyright (C) 2003 GOTOU Yuuzou <gotoyuzo@notwork.org> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#if !defined(_OSSL_OCSP_H_) +#define _OSSL_OCSP_H_ + +#if defined(OSSL_OCSP_ENABLED) +extern VALUE mOCSP; +extern VALUE cOPCSReq; +extern VALUE cOPCSRes; +extern VALUE cOPCSBasicRes; +#endif + +void Init_ossl_ocsp(void); + +#endif /* _OSSL_OCSP_H_ */ diff --git a/ext/openssl/ossl_pkcs12.c b/ext/openssl/ossl_pkcs12.c new file mode 100644 index 00000000..b3974cb7 --- /dev/null +++ b/ext/openssl/ossl_pkcs12.c @@ -0,0 +1,212 @@ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + * $Id$ + */ +#include "ossl.h" + +#define WrapPKCS12(klass, obj, p12) do { \ + if(!(p12)) ossl_raise(rb_eRuntimeError, "PKCS12 wasn't initialized."); \ + (obj) = Data_Wrap_Struct((klass), 0, PKCS12_free, (p12)); \ +} while (0) + +#define GetPKCS12(obj, p12) do { \ + Data_Get_Struct((obj), PKCS12, (p12)); \ + if(!(p12)) ossl_raise(rb_eRuntimeError, "PKCS12 wasn't initialized."); \ +} while (0) + +#define SafeGetPKCS12(obj, p12) do { \ + OSSL_Check_Kind((obj), cPKCS12); \ + GetPKCS12((obj), (p12)); \ +} while (0) + +#define ossl_pkcs12_set_key(o,v) rb_iv_set((o), "@key", (v)) +#define ossl_pkcs12_set_cert(o,v) rb_iv_set((o), "@certificate", (v)) +#define ossl_pkcs12_set_ca_certs(o,v) rb_iv_set((o), "@ca_certs", (v)) +#define ossl_pkcs12_get_key(o) rb_iv_get((o), "@key") +#define ossl_pkcs12_get_cert(o) rb_iv_get((o), "@certificate") +#define ossl_pkcs12_get_ca_certs(o) rb_iv_get((o), "@ca_certs") + +/* + * Classes + */ +VALUE cPKCS12; +VALUE ePKCS12Error; + +/* + * Private + */ +static VALUE +ossl_pkcs12_s_allocate(VALUE klass) +{ + PKCS12 *p12; + VALUE obj; + + if(!(p12 = PKCS12_new())) ossl_raise(ePKCS12Error, NULL); + WrapPKCS12(klass, obj, p12); + + return obj; +} + +/* + * call-seq: + * PKCS12.create(pass, name, key, cert [, ca, [, key_pbe [, cert_pbe [, key_iter [, mac_iter [, keytype]]]]]]) + * + * === Parameters + * * +pass+ - string + * * +name+ - A string describing the key. + * * +key+ - Any PKey. + * * +cert+ - A X509::Certificate. + * * * The public_key portion of the certificate must contain a valid public key. + * * * The not_before and not_after fields must be filled in. + * * +ca+ - An optional array of X509::Certificate's. + * * +key_pbe+ - string + * * +cert_pbe+ - string + * * +key_iter+ - integer + * * +mac_iter+ - integer + * * +keytype+ - An integer representing an MSIE specific extension. + * + * Any optional arguments may be supplied as nil to preserve the OpenSSL defaults. + * + * See the OpenSSL documentation for PKCS12_create(). + */ +static VALUE +ossl_pkcs12_s_create(int argc, VALUE *argv, VALUE self) +{ + VALUE pass, name, pkey, cert, ca, key_nid, cert_nid, key_iter, mac_iter, keytype; + VALUE obj; + char *passphrase, *friendlyname; + EVP_PKEY *key; + X509 *x509; + STACK_OF(X509) *x509s; + int nkey = 0, ncert = 0, kiter = 0, miter = 0, ktype = 0; + PKCS12 *p12; + + rb_scan_args(argc, argv, "46", &pass, &name, &pkey, &cert, &ca, &key_nid, &cert_nid, &key_iter, &mac_iter, &keytype); + passphrase = NIL_P(pass) ? NULL : StringValuePtr(pass); + friendlyname = NIL_P(name) ? NULL : StringValuePtr(name); + key = GetPKeyPtr(pkey); + x509 = GetX509CertPtr(cert); + x509s = NIL_P(ca) ? NULL : ossl_x509_ary2sk(ca); +/* TODO: make a VALUE to nid function */ + if (!NIL_P(key_nid)) { + if ((nkey = OBJ_txt2nid(StringValuePtr(key_nid))) == NID_undef) + ossl_raise(rb_eArgError, "Unknown PBE algorithm %s", StringValuePtr(key_nid)); + } + if (!NIL_P(cert_nid)) { + if ((ncert = OBJ_txt2nid(StringValuePtr(cert_nid))) == NID_undef) + ossl_raise(rb_eArgError, "Unknown PBE algorithm %s", StringValuePtr(cert_nid)); + } + if (!NIL_P(key_iter)) + kiter = NUM2INT(key_iter); + if (!NIL_P(mac_iter)) + miter = NUM2INT(mac_iter); + if (!NIL_P(keytype)) + ktype = NUM2INT(keytype); + + p12 = PKCS12_create(passphrase, friendlyname, key, x509, x509s, + nkey, ncert, kiter, miter, ktype); + sk_X509_pop_free(x509s, X509_free); + if(!p12) ossl_raise(ePKCS12Error, NULL); + WrapPKCS12(cPKCS12, obj, p12); + + ossl_pkcs12_set_key(obj, pkey); + ossl_pkcs12_set_cert(obj, cert); + ossl_pkcs12_set_ca_certs(obj, ca); + + return obj; +} + +/* + * call-seq: + * PKCS12.new -> pkcs12 + * PKCS12.new(str) -> pkcs12 + * PKCS12.new(str, pass) -> pkcs12 + * + * === Parameters + * * +str+ - Must be a DER encoded PKCS12 string. + * * +pass+ - string + */ +static VALUE +ossl_pkcs12_initialize(int argc, VALUE *argv, VALUE self) +{ + BIO *in; + VALUE arg, pass, pkey, cert, ca; + char *passphrase; + EVP_PKEY *key; + X509 *x509; + STACK_OF(X509) *x509s = NULL; + int st = 0; + PKCS12 *pkcs = DATA_PTR(self); + + if(rb_scan_args(argc, argv, "02", &arg, &pass) == 0) return self; + passphrase = NIL_P(pass) ? NULL : StringValuePtr(pass); + in = ossl_obj2bio(arg); + d2i_PKCS12_bio(in, &pkcs); + DATA_PTR(self) = pkcs; + BIO_free(in); + + pkey = cert = ca = Qnil; + if(!PKCS12_parse(pkcs, passphrase, &key, &x509, &x509s)) + ossl_raise(ePKCS12Error, "PKCS12_parse"); + pkey = rb_protect((VALUE(*)_((VALUE)))ossl_pkey_new, (VALUE)key, + &st); /* NO DUP */ + if(st) goto err; + cert = rb_protect((VALUE(*)_((VALUE)))ossl_x509_new, (VALUE)x509, &st); + if(st) goto err; + if(x509s){ + ca = + rb_protect((VALUE(*)_((VALUE)))ossl_x509_sk2ary, (VALUE)x509s, &st); + if(st) goto err; + } + + err: + X509_free(x509); + sk_X509_pop_free(x509s, X509_free); + ossl_pkcs12_set_key(self, pkey); + ossl_pkcs12_set_cert(self, cert); + ossl_pkcs12_set_ca_certs(self, ca); + if(st) rb_jump_tag(st); + + return self; +} + +static VALUE +ossl_pkcs12_to_der(VALUE self) +{ + PKCS12 *p12; + VALUE str; + long len; + unsigned char *p; + + GetPKCS12(self, p12); + if((len = i2d_PKCS12(p12, NULL)) <= 0) + ossl_raise(ePKCS12Error, NULL); + str = rb_str_new(0, len); + p = (unsigned char *)RSTRING_PTR(str); + if(i2d_PKCS12(p12, &p) <= 0) + ossl_raise(ePKCS12Error, NULL); + ossl_str_adjust(str, p); + + return str; +} + +void +Init_ossl_pkcs12(void) +{ + /* + * Defines a file format commonly used to store private keys with + * accompanying public key certificates, protected with a password-based + * symmetric key. + */ + cPKCS12 = rb_define_class_under(mOSSL, "PKCS12", rb_cObject); + ePKCS12Error = rb_define_class_under(cPKCS12, "PKCS12Error", eOSSLError); + rb_define_singleton_method(cPKCS12, "create", ossl_pkcs12_s_create, -1); + + rb_define_alloc_func(cPKCS12, ossl_pkcs12_s_allocate); + rb_attr(cPKCS12, rb_intern("key"), 1, 0, Qfalse); + rb_attr(cPKCS12, rb_intern("certificate"), 1, 0, Qfalse); + rb_attr(cPKCS12, rb_intern("ca_certs"), 1, 0, Qfalse); + rb_define_method(cPKCS12, "initialize", ossl_pkcs12_initialize, -1); + rb_define_method(cPKCS12, "to_der", ossl_pkcs12_to_der, 0); +} diff --git a/ext/openssl/ossl_pkcs12.h b/ext/openssl/ossl_pkcs12.h new file mode 100644 index 00000000..24d25d00 --- /dev/null +++ b/ext/openssl/ossl_pkcs12.h @@ -0,0 +1,15 @@ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + * $Id$ + */ +#if !defined(_OSSL_PKCS12_H_) +#define _OSSL_PKCS12_H_ + +extern VALUE cPKCS12; +extern VALUE ePKCS12Error; + +void Init_ossl_pkcs12(void); + +#endif /* _OSSL_PKCS12_H_ */ + diff --git a/ext/openssl/ossl_pkcs5.c b/ext/openssl/ossl_pkcs5.c new file mode 100644 index 00000000..6c7738a2 --- /dev/null +++ b/ext/openssl/ossl_pkcs5.c @@ -0,0 +1,189 @@ +/* + * $Id$ + * Copyright (C) 2007 Technorama Ltd. <oss-ruby@technorama.net> + */ +#include "ossl.h" + +VALUE mPKCS5; +VALUE ePKCS5; + +#ifdef HAVE_PKCS5_PBKDF2_HMAC +/* + * call-seq: + * PKCS5.pbkdf2_hmac(pass, salt, iter, keylen, digest) => string + * + * === Parameters + * * +pass+ - string + * * +salt+ - string - should be at least 8 bytes long. + * * +iter+ - integer - should be greater than 1000. 20000 is better. + * * +keylen+ - integer + * * +digest+ - a string or OpenSSL::Digest object. + * + * Available in OpenSSL 0.9.4. + * + * Digests other than SHA1 may not be supported by other cryptography libraries. + */ +static VALUE +ossl_pkcs5_pbkdf2_hmac(VALUE self, VALUE pass, VALUE salt, VALUE iter, VALUE keylen, VALUE digest) +{ + VALUE str; + const EVP_MD *md; + int len = NUM2INT(keylen); + + StringValue(pass); + StringValue(salt); + md = GetDigestPtr(digest); + + str = rb_str_new(0, len); + + if (PKCS5_PBKDF2_HMAC(RSTRING_PTR(pass), RSTRING_LENINT(pass), + (unsigned char *)RSTRING_PTR(salt), RSTRING_LENINT(salt), + NUM2INT(iter), md, len, + (unsigned char *)RSTRING_PTR(str)) != 1) + ossl_raise(ePKCS5, "PKCS5_PBKDF2_HMAC"); + + return str; +} +#else +#define ossl_pkcs5_pbkdf2_hmac rb_f_notimplement +#endif + + +#ifdef HAVE_PKCS5_PBKDF2_HMAC_SHA1 +/* + * call-seq: + * PKCS5.pbkdf2_hmac_sha1(pass, salt, iter, keylen) => string + * + * === Parameters + * * +pass+ - string + * * +salt+ - string - should be at least 8 bytes long. + * * +iter+ - integer - should be greater than 1000. 20000 is better. + * * +keylen+ - integer + * + * This method is available in almost any version of OpenSSL. + * + * Conforms to rfc2898. + */ +static VALUE +ossl_pkcs5_pbkdf2_hmac_sha1(VALUE self, VALUE pass, VALUE salt, VALUE iter, VALUE keylen) +{ + VALUE str; + int len = NUM2INT(keylen); + + StringValue(pass); + StringValue(salt); + + str = rb_str_new(0, len); + + if (PKCS5_PBKDF2_HMAC_SHA1(RSTRING_PTR(pass), RSTRING_LENINT(pass), + (const unsigned char *)RSTRING_PTR(salt), RSTRING_LENINT(salt), NUM2INT(iter), + len, (unsigned char *)RSTRING_PTR(str)) != 1) + ossl_raise(ePKCS5, "PKCS5_PBKDF2_HMAC_SHA1"); + + return str; +} +#else +#define ossl_pkcs5_pbkdf2_hmac_sha1 rb_f_notimplement +#endif + +void +Init_ossl_pkcs5(void) +{ + /* + * Password-based Encryption + * + */ + + #if 0 + mOSSL = rb_define_module("OpenSSL"); /* let rdoc know about mOSSL */ + #endif + + /* Document-class: OpenSSL::PKCS5 + * + * Provides password-based encryption functionality based on PKCS#5. + * Typically used for securely deriving arbitrary length symmetric keys + * to be used with an OpenSSL::Cipher from passwords. Another use case + * is for storing passwords: Due to the ability to tweak the effort of + * computation by increasing the iteration count, computation can be + * slowed down artificially in order to render possible attacks infeasible. + * + * PKCS5 offers support for PBKDF2 with an OpenSSL::Digest::SHA1-based + * HMAC, or an arbitrary Digest if the underlying version of OpenSSL + * already supports it (>= 0.9.4). + * + * === Parameters + * ==== Password + * Typically an arbitrary String that represents the password to be used + * for deriving a key. + * ==== Salt + * Prevents attacks based on dictionaries of common passwords. It is a + * public value that can be safely stored along with the password (e.g. + * if PBKDF2 is used for password storage). For maximum security, a fresh, + * random salt should be generated for each stored password. According + * to PKCS#5, a salt should be at least 8 bytes long. + * ==== Iteration Count + * Allows to tweak the length that the actual computation will take. The + * larger the iteration count, the longer it will take. + * ==== Key Length + * Specifies the length in bytes of the output that will be generated. + * Typically, the key length should be larger than or equal to the output + * length of the underlying digest function, otherwise an attacker could + * simply try to brute-force the key. According to PKCS#5, security is + * limited by the output length of the underlying digest function, i.e. + * security is not improved if a key length strictly larger than the + * digest output length is chosen. Therefore, when using PKCS5 for + * password storage, it suffices to store values equal to the digest + * output length, nothing is gained by storing larger values. + * + * == Examples + * === Generating a 128 bit key for a Cipher (e.g. AES) + * pass = "secret" + * salt = OpenSSL::Random.random_bytes(16) + * iter = 20000 + * key_len = 16 + * key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(pass, salt, iter, key_len) + * + * === Storing Passwords + * pass = "secret" + * salt = OpenSSL::Random.random_bytes(16) #store this with the generated value + * iter = 20000 + * digest = OpenSSL::Digest::SHA256.new + * len = digest.digest_length + * #the final value to be stored + * value = OpenSSL::PKCS5.pbkdf2_hmac(pass, salt, iter, len, digest) + * + * === Important Note on Checking Passwords + * When comparing passwords provided by the user with previously stored + * values, a common mistake made is comparing the two values using "==". + * Typically, "==" short-circuits on evaluation, and is therefore + * vulnerable to timing attacks. The proper way is to use a method that + * always takes the same amount of time when comparing two values, thus + * not leaking any information to potential attackers. To compare two + * values, the following could be used: + * def eql_time_cmp(a, b) + * unless a.length == b.length + * return false + * end + * cmp = b.bytes.to_a + * result = 0 + * a.bytes.each_with_index {|c,i| + * result |= c ^ cmp[i] + * } + * result == 0 + * end + * Please note that the premature return in case of differing lengths + * typically does not leak valuable information - when using PKCS#5, the + * length of the values to be compared is of fixed size. + */ + + mPKCS5 = rb_define_module_under(mOSSL, "PKCS5"); + /* Document-class: OpenSSL::PKCS5::PKCS5Error + * + * Generic Exception class that is raised if an error occurs during a + * computation. + */ + ePKCS5 = rb_define_class_under(mPKCS5, "PKCS5Error", eOSSLError); + + rb_define_module_function(mPKCS5, "pbkdf2_hmac", ossl_pkcs5_pbkdf2_hmac, 5); + rb_define_module_function(mPKCS5, "pbkdf2_hmac_sha1", ossl_pkcs5_pbkdf2_hmac_sha1, 4); +} diff --git a/ext/openssl/ossl_pkcs5.h b/ext/openssl/ossl_pkcs5.h new file mode 100644 index 00000000..a3b132bc --- /dev/null +++ b/ext/openssl/ossl_pkcs5.h @@ -0,0 +1,6 @@ +#if !defined(_OSSL_PKCS5_H_) +#define _OSSL_PKCS5_H_ + +void Init_ossl_pkcs5(void); + +#endif /* _OSSL_PKCS5_H_ */ diff --git a/ext/openssl/ossl_pkcs7.c b/ext/openssl/ossl_pkcs7.c new file mode 100644 index 00000000..f476807f --- /dev/null +++ b/ext/openssl/ossl_pkcs7.c @@ -0,0 +1,1048 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2001-2002 Michal Rokos <m.rokos@sh.cvut.cz> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#include "ossl.h" + +#define WrapPKCS7(klass, obj, pkcs7) do { \ + if (!(pkcs7)) { \ + ossl_raise(rb_eRuntimeError, "PKCS7 wasn't initialized."); \ + } \ + (obj) = Data_Wrap_Struct((klass), 0, PKCS7_free, (pkcs7)); \ +} while (0) +#define GetPKCS7(obj, pkcs7) do { \ + Data_Get_Struct((obj), PKCS7, (pkcs7)); \ + if (!(pkcs7)) { \ + ossl_raise(rb_eRuntimeError, "PKCS7 wasn't initialized."); \ + } \ +} while (0) +#define SafeGetPKCS7(obj, pkcs7) do { \ + OSSL_Check_Kind((obj), cPKCS7); \ + GetPKCS7((obj), (pkcs7)); \ +} while (0) + +#define WrapPKCS7si(klass, obj, p7si) do { \ + if (!(p7si)) { \ + ossl_raise(rb_eRuntimeError, "PKCS7si wasn't initialized."); \ + } \ + (obj) = Data_Wrap_Struct((klass), 0, PKCS7_SIGNER_INFO_free, (p7si)); \ +} while (0) +#define GetPKCS7si(obj, p7si) do { \ + Data_Get_Struct((obj), PKCS7_SIGNER_INFO, (p7si)); \ + if (!(p7si)) { \ + ossl_raise(rb_eRuntimeError, "PKCS7si wasn't initialized."); \ + } \ +} while (0) +#define SafeGetPKCS7si(obj, p7si) do { \ + OSSL_Check_Kind((obj), cPKCS7Signer); \ + GetPKCS7si((obj), (p7si)); \ +} while (0) + +#define WrapPKCS7ri(klass, obj, p7ri) do { \ + if (!(p7ri)) { \ + ossl_raise(rb_eRuntimeError, "PKCS7ri wasn't initialized."); \ + } \ + (obj) = Data_Wrap_Struct((klass), 0, PKCS7_RECIP_INFO_free, (p7ri)); \ +} while (0) +#define GetPKCS7ri(obj, p7ri) do { \ + Data_Get_Struct((obj), PKCS7_RECIP_INFO, (p7ri)); \ + if (!(p7ri)) { \ + ossl_raise(rb_eRuntimeError, "PKCS7ri wasn't initialized."); \ + } \ +} while (0) +#define SafeGetPKCS7ri(obj, p7ri) do { \ + OSSL_Check_Kind((obj), cPKCS7Recipient); \ + GetPKCS7ri((obj), (p7ri)); \ +} while (0) + +#define numberof(ary) (int)(sizeof(ary)/sizeof((ary)[0])) + +#define ossl_pkcs7_set_data(o,v) rb_iv_set((o), "@data", (v)) +#define ossl_pkcs7_get_data(o) rb_iv_get((o), "@data") +#define ossl_pkcs7_set_err_string(o,v) rb_iv_set((o), "@error_string", (v)) +#define ossl_pkcs7_get_err_string(o) rb_iv_get((o), "@error_string") + +/* + * Classes + */ +VALUE cPKCS7; +VALUE cPKCS7Signer; +VALUE cPKCS7Recipient; +VALUE ePKCS7Error; + +/* + * Public + * (MADE PRIVATE UNTIL SOMEBODY WILL NEED THEM) + */ +static VALUE +ossl_pkcs7si_new(PKCS7_SIGNER_INFO *p7si) +{ + PKCS7_SIGNER_INFO *pkcs7; + VALUE obj; + + pkcs7 = p7si ? PKCS7_SIGNER_INFO_dup(p7si) : PKCS7_SIGNER_INFO_new(); + if (!pkcs7) ossl_raise(ePKCS7Error, NULL); + WrapPKCS7si(cPKCS7Signer, obj, pkcs7); + + return obj; +} + +static PKCS7_SIGNER_INFO * +DupPKCS7SignerPtr(VALUE obj) +{ + PKCS7_SIGNER_INFO *p7si, *pkcs7; + + SafeGetPKCS7si(obj, p7si); + if (!(pkcs7 = PKCS7_SIGNER_INFO_dup(p7si))) { + ossl_raise(ePKCS7Error, NULL); + } + + return pkcs7; +} + +static VALUE +ossl_pkcs7ri_new(PKCS7_RECIP_INFO *p7ri) +{ + PKCS7_RECIP_INFO *pkcs7; + VALUE obj; + + pkcs7 = p7ri ? PKCS7_RECIP_INFO_dup(p7ri) : PKCS7_RECIP_INFO_new(); + if (!pkcs7) ossl_raise(ePKCS7Error, NULL); + WrapPKCS7ri(cPKCS7Recipient, obj, pkcs7); + + return obj; +} + +static PKCS7_RECIP_INFO * +DupPKCS7RecipientPtr(VALUE obj) +{ + PKCS7_RECIP_INFO *p7ri, *pkcs7; + + SafeGetPKCS7ri(obj, p7ri); + if (!(pkcs7 = PKCS7_RECIP_INFO_dup(p7ri))) { + ossl_raise(ePKCS7Error, NULL); + } + + return pkcs7; +} + +/* + * call-seq: + * PKCS7.read_smime(string) => pkcs7 + */ +static VALUE +ossl_pkcs7_s_read_smime(VALUE klass, VALUE arg) +{ + BIO *in, *out; + PKCS7 *pkcs7; + VALUE ret, data; + + in = ossl_obj2bio(arg); + out = NULL; + pkcs7 = SMIME_read_PKCS7(in, &out); + BIO_free(in); + if(!pkcs7) ossl_raise(ePKCS7Error, NULL); + data = out ? ossl_membio2str(out) : Qnil; + WrapPKCS7(cPKCS7, ret, pkcs7); + ossl_pkcs7_set_data(ret, data); + ossl_pkcs7_set_err_string(ret, Qnil); + + return ret; +} + +/* + * call-seq: + * PKCS7.write_smime(pkcs7 [, data [, flags]]) => string + */ +static VALUE +ossl_pkcs7_s_write_smime(int argc, VALUE *argv, VALUE klass) +{ + VALUE pkcs7, data, flags; + BIO *out, *in; + PKCS7 *p7; + VALUE str; + int flg; + + rb_scan_args(argc, argv, "12", &pkcs7, &data, &flags); + flg = NIL_P(flags) ? 0 : NUM2INT(flags); + if(NIL_P(data)) data = ossl_pkcs7_get_data(pkcs7); + SafeGetPKCS7(pkcs7, p7); + if(!NIL_P(data) && PKCS7_is_detached(p7)) + flg |= PKCS7_DETACHED; + in = NIL_P(data) ? NULL : ossl_obj2bio(data); + if(!(out = BIO_new(BIO_s_mem()))){ + BIO_free(in); + ossl_raise(ePKCS7Error, NULL); + } + if(!SMIME_write_PKCS7(out, p7, in, flg)){ + BIO_free(out); + BIO_free(in); + ossl_raise(ePKCS7Error, NULL); + } + BIO_free(in); + str = ossl_membio2str(out); + + return str; +} + +/* + * call-seq: + * PKCS7.sign(cert, key, data, [, certs [, flags]]) => pkcs7 + */ +static VALUE +ossl_pkcs7_s_sign(int argc, VALUE *argv, VALUE klass) +{ + VALUE cert, key, data, certs, flags; + X509 *x509; + EVP_PKEY *pkey; + BIO *in; + STACK_OF(X509) *x509s; + int flg, status = 0; + PKCS7 *pkcs7; + VALUE ret; + + rb_scan_args(argc, argv, "32", &cert, &key, &data, &certs, &flags); + x509 = GetX509CertPtr(cert); /* NO NEED TO DUP */ + pkey = GetPrivPKeyPtr(key); /* NO NEED TO DUP */ + flg = NIL_P(flags) ? 0 : NUM2INT(flags); + in = ossl_obj2bio(data); + if(NIL_P(certs)) x509s = NULL; + else{ + x509s = ossl_protect_x509_ary2sk(certs, &status); + if(status){ + BIO_free(in); + rb_jump_tag(status); + } + } + if(!(pkcs7 = PKCS7_sign(x509, pkey, x509s, in, flg))){ + BIO_free(in); + sk_X509_pop_free(x509s, X509_free); + ossl_raise(ePKCS7Error, NULL); + } + WrapPKCS7(cPKCS7, ret, pkcs7); + ossl_pkcs7_set_data(ret, data); + ossl_pkcs7_set_err_string(ret, Qnil); + BIO_free(in); + sk_X509_pop_free(x509s, X509_free); + + return ret; +} + +/* + * call-seq: + * PKCS7.encrypt(certs, data, [, cipher [, flags]]) => pkcs7 + */ +static VALUE +ossl_pkcs7_s_encrypt(int argc, VALUE *argv, VALUE klass) +{ + VALUE certs, data, cipher, flags; + STACK_OF(X509) *x509s; + BIO *in; + const EVP_CIPHER *ciph; + int flg, status = 0; + VALUE ret; + PKCS7 *p7; + + rb_scan_args(argc, argv, "22", &certs, &data, &cipher, &flags); + if(NIL_P(cipher)){ +#if !defined(OPENSSL_NO_RC2) + ciph = EVP_rc2_40_cbc(); +#elif !defined(OPENSSL_NO_DES) + ciph = EVP_des_ede3_cbc(); +#elif !defined(OPENSSL_NO_RC2) + ciph = EVP_rc2_40_cbc(); +#elif !defined(OPENSSL_NO_AES) + ciph = EVP_EVP_aes_128_cbc(); +#else + ossl_raise(ePKCS7Error, "Must specify cipher"); +#endif + + } + else ciph = GetCipherPtr(cipher); /* NO NEED TO DUP */ + flg = NIL_P(flags) ? 0 : NUM2INT(flags); + in = ossl_obj2bio(data); + x509s = ossl_protect_x509_ary2sk(certs, &status); + if(status){ + BIO_free(in); + rb_jump_tag(status); + } + if(!(p7 = PKCS7_encrypt(x509s, in, (EVP_CIPHER*)ciph, flg))){ + BIO_free(in); + sk_X509_pop_free(x509s, X509_free); + ossl_raise(ePKCS7Error, NULL); + } + BIO_free(in); + WrapPKCS7(cPKCS7, ret, p7); + ossl_pkcs7_set_data(ret, data); + sk_X509_pop_free(x509s, X509_free); + + return ret; +} + +static VALUE +ossl_pkcs7_alloc(VALUE klass) +{ + PKCS7 *pkcs7; + VALUE obj; + + if (!(pkcs7 = PKCS7_new())) { + ossl_raise(ePKCS7Error, NULL); + } + WrapPKCS7(klass, obj, pkcs7); + + return obj; +} + +/* + * call-seq: + * PKCS7.new => pkcs7 + * PKCS7.new(string) => pkcs7 + * + * Many methods in this class aren't documented. + */ +static VALUE +ossl_pkcs7_initialize(int argc, VALUE *argv, VALUE self) +{ + PKCS7 *p7, *pkcs = DATA_PTR(self); + BIO *in; + VALUE arg; + + if(rb_scan_args(argc, argv, "01", &arg) == 0) + return self; + arg = ossl_to_der_if_possible(arg); + in = ossl_obj2bio(arg); + p7 = PEM_read_bio_PKCS7(in, &pkcs, NULL, NULL); + if (!p7) { + OSSL_BIO_reset(in); + p7 = d2i_PKCS7_bio(in, &pkcs); + if (!p7) { + BIO_free(in); + PKCS7_free(pkcs); + DATA_PTR(self) = NULL; + ossl_raise(rb_eArgError, "Could not parse the PKCS7"); + } + } + DATA_PTR(self) = pkcs; + BIO_free(in); + ossl_pkcs7_set_data(self, Qnil); + ossl_pkcs7_set_err_string(self, Qnil); + + return self; +} + +static VALUE +ossl_pkcs7_copy(VALUE self, VALUE other) +{ + PKCS7 *a, *b, *pkcs7; + + rb_check_frozen(self); + if (self == other) return self; + + GetPKCS7(self, a); + SafeGetPKCS7(other, b); + + pkcs7 = PKCS7_dup(b); + if (!pkcs7) { + ossl_raise(ePKCS7Error, NULL); + } + DATA_PTR(self) = pkcs7; + PKCS7_free(a); + + return self; +} + +static int +ossl_pkcs7_sym2typeid(VALUE sym) +{ + int i, ret = Qnil; + const char *s; + size_t l; + + static const struct { + char name[20]; + int nid; + } p7_type_tab[] = { + { "signed", NID_pkcs7_signed }, + { "data", NID_pkcs7_data }, + { "signedAndEnveloped", NID_pkcs7_signedAndEnveloped }, + { "enveloped", NID_pkcs7_enveloped }, + { "encrypted", NID_pkcs7_encrypted }, + { "digest", NID_pkcs7_digest }, + }; + + if (RB_TYPE_P(sym, T_SYMBOL)) sym = rb_sym2str(sym); + else StringValue(sym); + RSTRING_GETMEM(sym, s, l); + for(i = 0; ; i++){ + if(i == numberof(p7_type_tab)) + ossl_raise(ePKCS7Error, "unknown type \"%s\"", s); + if(strlen(p7_type_tab[i].name) != l) continue; + if(strcmp(p7_type_tab[i].name, s) == 0){ + ret = p7_type_tab[i].nid; + break; + } + } + + return ret; +} + +/* + * call-seq: + * pkcs7.type = type => type + */ +static VALUE +ossl_pkcs7_set_type(VALUE self, VALUE type) +{ + PKCS7 *p7; + + GetPKCS7(self, p7); + if(!PKCS7_set_type(p7, ossl_pkcs7_sym2typeid(type))) + ossl_raise(ePKCS7Error, NULL); + + return type; +} + +/* + * call-seq: + * pkcs7.type => string or nil + */ +static VALUE +ossl_pkcs7_get_type(VALUE self) +{ + PKCS7 *p7; + + GetPKCS7(self, p7); + if(PKCS7_type_is_signed(p7)) + return ID2SYM(rb_intern("signed")); + if(PKCS7_type_is_encrypted(p7)) + return ID2SYM(rb_intern("encrypted")); + if(PKCS7_type_is_enveloped(p7)) + return ID2SYM(rb_intern("enveloped")); + if(PKCS7_type_is_signedAndEnveloped(p7)) + return ID2SYM(rb_intern("signedAndEnveloped")); + if(PKCS7_type_is_data(p7)) + return ID2SYM(rb_intern("data")); + return Qnil; +} + +static VALUE +ossl_pkcs7_set_detached(VALUE self, VALUE flag) +{ + PKCS7 *p7; + + GetPKCS7(self, p7); + if(flag != Qtrue && flag != Qfalse) + ossl_raise(ePKCS7Error, "must specify a boolean"); + if(!PKCS7_set_detached(p7, flag == Qtrue ? 1 : 0)) + ossl_raise(ePKCS7Error, NULL); + + return flag; +} + +static VALUE +ossl_pkcs7_get_detached(VALUE self) +{ + PKCS7 *p7; + GetPKCS7(self, p7); + return PKCS7_get_detached(p7) ? Qtrue : Qfalse; +} + +static VALUE +ossl_pkcs7_detached_p(VALUE self) +{ + PKCS7 *p7; + GetPKCS7(self, p7); + return PKCS7_is_detached(p7) ? Qtrue : Qfalse; +} + +static VALUE +ossl_pkcs7_set_cipher(VALUE self, VALUE cipher) +{ + PKCS7 *pkcs7; + + GetPKCS7(self, pkcs7); + if (!PKCS7_set_cipher(pkcs7, GetCipherPtr(cipher))) { + ossl_raise(ePKCS7Error, NULL); + } + + return cipher; +} + +static VALUE +ossl_pkcs7_add_signer(VALUE self, VALUE signer) +{ + PKCS7 *pkcs7; + PKCS7_SIGNER_INFO *p7si; + + p7si = DupPKCS7SignerPtr(signer); /* NEED TO DUP */ + GetPKCS7(self, pkcs7); + if (!PKCS7_add_signer(pkcs7, p7si)) { + PKCS7_SIGNER_INFO_free(p7si); + ossl_raise(ePKCS7Error, "Could not add signer."); + } + if (PKCS7_type_is_signed(pkcs7)){ + PKCS7_add_signed_attribute(p7si, NID_pkcs9_contentType, + V_ASN1_OBJECT, OBJ_nid2obj(NID_pkcs7_data)); + } + + return self; +} + +static VALUE +ossl_pkcs7_get_signer(VALUE self) +{ + PKCS7 *pkcs7; + STACK_OF(PKCS7_SIGNER_INFO) *sk; + PKCS7_SIGNER_INFO *si; + int num, i; + VALUE ary; + + GetPKCS7(self, pkcs7); + if (!(sk = PKCS7_get_signer_info(pkcs7))) { + OSSL_Debug("OpenSSL::PKCS7#get_signer_info == NULL!"); + return rb_ary_new(); + } + if ((num = sk_PKCS7_SIGNER_INFO_num(sk)) < 0) { + ossl_raise(ePKCS7Error, "Negative number of signers!"); + } + ary = rb_ary_new2(num); + for (i=0; i<num; i++) { + si = sk_PKCS7_SIGNER_INFO_value(sk, i); + rb_ary_push(ary, ossl_pkcs7si_new(si)); + } + + return ary; +} + +static VALUE +ossl_pkcs7_add_recipient(VALUE self, VALUE recip) +{ + PKCS7 *pkcs7; + PKCS7_RECIP_INFO *ri; + + ri = DupPKCS7RecipientPtr(recip); /* NEED TO DUP */ + GetPKCS7(self, pkcs7); + if (!PKCS7_add_recipient_info(pkcs7, ri)) { + PKCS7_RECIP_INFO_free(ri); + ossl_raise(ePKCS7Error, "Could not add recipient."); + } + + return self; +} + +static VALUE +ossl_pkcs7_get_recipient(VALUE self) +{ + PKCS7 *pkcs7; + STACK_OF(PKCS7_RECIP_INFO) *sk; + PKCS7_RECIP_INFO *si; + int num, i; + VALUE ary; + + GetPKCS7(self, pkcs7); + if (PKCS7_type_is_enveloped(pkcs7)) + sk = pkcs7->d.enveloped->recipientinfo; + else if (PKCS7_type_is_signedAndEnveloped(pkcs7)) + sk = pkcs7->d.signed_and_enveloped->recipientinfo; + else sk = NULL; + if (!sk) return rb_ary_new(); + if ((num = sk_PKCS7_RECIP_INFO_num(sk)) < 0) { + ossl_raise(ePKCS7Error, "Negative number of recipient!"); + } + ary = rb_ary_new2(num); + for (i=0; i<num; i++) { + si = sk_PKCS7_RECIP_INFO_value(sk, i); + rb_ary_push(ary, ossl_pkcs7ri_new(si)); + } + + return ary; +} + +static VALUE +ossl_pkcs7_add_certificate(VALUE self, VALUE cert) +{ + PKCS7 *pkcs7; + X509 *x509; + + GetPKCS7(self, pkcs7); + x509 = GetX509CertPtr(cert); /* NO NEED TO DUP */ + if (!PKCS7_add_certificate(pkcs7, x509)){ + ossl_raise(ePKCS7Error, NULL); + } + + return self; +} + +static STACK_OF(X509) * +pkcs7_get_certs(VALUE self) +{ + PKCS7 *pkcs7; + STACK_OF(X509) *certs; + int i; + + GetPKCS7(self, pkcs7); + i = OBJ_obj2nid(pkcs7->type); + switch(i){ + case NID_pkcs7_signed: + certs = pkcs7->d.sign->cert; + break; + case NID_pkcs7_signedAndEnveloped: + certs = pkcs7->d.signed_and_enveloped->cert; + break; + default: + certs = NULL; + } + + return certs; +} + +static STACK_OF(X509_CRL) * +pkcs7_get_crls(VALUE self) +{ + PKCS7 *pkcs7; + STACK_OF(X509_CRL) *crls; + int i; + + GetPKCS7(self, pkcs7); + i = OBJ_obj2nid(pkcs7->type); + switch(i){ + case NID_pkcs7_signed: + crls = pkcs7->d.sign->crl; + break; + case NID_pkcs7_signedAndEnveloped: + crls = pkcs7->d.signed_and_enveloped->crl; + break; + default: + crls = NULL; + } + + return crls; +} + +static VALUE +ossl_pkcs7_set_certs_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, arg)) +{ + return ossl_pkcs7_add_certificate(arg, i); +} + +static VALUE +ossl_pkcs7_set_certificates(VALUE self, VALUE ary) +{ + STACK_OF(X509) *certs; + X509 *cert; + + certs = pkcs7_get_certs(self); + while((cert = sk_X509_pop(certs))) X509_free(cert); + rb_block_call(ary, rb_intern("each"), 0, 0, ossl_pkcs7_set_certs_i, self); + + return ary; +} + +static VALUE +ossl_pkcs7_get_certificates(VALUE self) +{ + return ossl_x509_sk2ary(pkcs7_get_certs(self)); +} + +static VALUE +ossl_pkcs7_add_crl(VALUE self, VALUE crl) +{ + PKCS7 *pkcs7; + X509_CRL *x509crl; + + GetPKCS7(self, pkcs7); /* NO DUP needed! */ + x509crl = GetX509CRLPtr(crl); + if (!PKCS7_add_crl(pkcs7, x509crl)) { + ossl_raise(ePKCS7Error, NULL); + } + + return self; +} + +static VALUE +ossl_pkcs7_set_crls_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, arg)) +{ + return ossl_pkcs7_add_crl(arg, i); +} + +static VALUE +ossl_pkcs7_set_crls(VALUE self, VALUE ary) +{ + STACK_OF(X509_CRL) *crls; + X509_CRL *crl; + + crls = pkcs7_get_crls(self); + while((crl = sk_X509_CRL_pop(crls))) X509_CRL_free(crl); + rb_block_call(ary, rb_intern("each"), 0, 0, ossl_pkcs7_set_crls_i, self); + + return ary; +} + +static VALUE +ossl_pkcs7_get_crls(VALUE self) +{ + return ossl_x509crl_sk2ary(pkcs7_get_crls(self)); +} + +static VALUE +ossl_pkcs7_verify(int argc, VALUE *argv, VALUE self) +{ + VALUE certs, store, indata, flags; + STACK_OF(X509) *x509s; + X509_STORE *x509st; + int flg, ok, status = 0; + BIO *in, *out; + PKCS7 *p7; + VALUE data; + const char *msg; + + rb_scan_args(argc, argv, "22", &certs, &store, &indata, &flags); + flg = NIL_P(flags) ? 0 : NUM2INT(flags); + if(NIL_P(indata)) indata = ossl_pkcs7_get_data(self); + in = NIL_P(indata) ? NULL : ossl_obj2bio(indata); + if(NIL_P(certs)) x509s = NULL; + else{ + x509s = ossl_protect_x509_ary2sk(certs, &status); + if(status){ + BIO_free(in); + rb_jump_tag(status); + } + } + x509st = GetX509StorePtr(store); + GetPKCS7(self, p7); + if(!(out = BIO_new(BIO_s_mem()))){ + BIO_free(in); + sk_X509_pop_free(x509s, X509_free); + ossl_raise(ePKCS7Error, NULL); + } + ok = PKCS7_verify(p7, x509s, x509st, in, out, flg); + BIO_free(in); + if (ok < 0) ossl_raise(ePKCS7Error, NULL); + msg = ERR_reason_error_string(ERR_get_error()); + ossl_pkcs7_set_err_string(self, msg ? rb_str_new2(msg) : Qnil); + ERR_clear_error(); + data = ossl_membio2str(out); + ossl_pkcs7_set_data(self, data); + sk_X509_pop_free(x509s, X509_free); + + return (ok == 1) ? Qtrue : Qfalse; +} + +static VALUE +ossl_pkcs7_decrypt(int argc, VALUE *argv, VALUE self) +{ + VALUE pkey, cert, flags; + EVP_PKEY *key; + X509 *x509; + int flg; + PKCS7 *p7; + BIO *out; + VALUE str; + + rb_scan_args(argc, argv, "21", &pkey, &cert, &flags); + key = GetPrivPKeyPtr(pkey); /* NO NEED TO DUP */ + x509 = GetX509CertPtr(cert); /* NO NEED TO DUP */ + flg = NIL_P(flags) ? 0 : NUM2INT(flags); + GetPKCS7(self, p7); + if(!(out = BIO_new(BIO_s_mem()))) + ossl_raise(ePKCS7Error, NULL); + if(!PKCS7_decrypt(p7, key, x509, out, flg)){ + BIO_free(out); + ossl_raise(ePKCS7Error, NULL); + } + str = ossl_membio2str(out); /* out will be free */ + + return str; +} + +static VALUE +ossl_pkcs7_add_data(VALUE self, VALUE data) +{ + PKCS7 *pkcs7; + BIO *out, *in; + char buf[4096]; + int len; + + in = ossl_obj2bio(data); + GetPKCS7(self, pkcs7); + if(PKCS7_type_is_signed(pkcs7)){ + if(!PKCS7_content_new(pkcs7, NID_pkcs7_data)) + ossl_raise(ePKCS7Error, NULL); + } + if(!(out = PKCS7_dataInit(pkcs7, NULL))) goto err; + for(;;){ + if((len = BIO_read(in, buf, sizeof(buf))) <= 0) + break; + if(BIO_write(out, buf, len) != len) + goto err; + } + if(!PKCS7_dataFinal(pkcs7, out)) goto err; + ossl_pkcs7_set_data(self, Qnil); + + err: + BIO_free(out); + BIO_free(in); + if(ERR_peek_error()){ + ossl_raise(ePKCS7Error, NULL); + } + + return data; +} + +static VALUE +ossl_pkcs7_to_der(VALUE self) +{ + PKCS7 *pkcs7; + VALUE str; + long len; + unsigned char *p; + + GetPKCS7(self, pkcs7); + if((len = i2d_PKCS7(pkcs7, NULL)) <= 0) + ossl_raise(ePKCS7Error, NULL); + str = rb_str_new(0, len); + p = (unsigned char *)RSTRING_PTR(str); + if(i2d_PKCS7(pkcs7, &p) <= 0) + ossl_raise(ePKCS7Error, NULL); + ossl_str_adjust(str, p); + + return str; +} + +static VALUE +ossl_pkcs7_to_pem(VALUE self) +{ + PKCS7 *pkcs7; + BIO *out; + VALUE str; + + GetPKCS7(self, pkcs7); + if (!(out = BIO_new(BIO_s_mem()))) { + ossl_raise(ePKCS7Error, NULL); + } + if (!PEM_write_bio_PKCS7(out, pkcs7)) { + BIO_free(out); + ossl_raise(ePKCS7Error, NULL); + } + str = ossl_membio2str(out); + + return str; +} + +/* + * SIGNER INFO + */ +static VALUE +ossl_pkcs7si_alloc(VALUE klass) +{ + PKCS7_SIGNER_INFO *p7si; + VALUE obj; + + if (!(p7si = PKCS7_SIGNER_INFO_new())) { + ossl_raise(ePKCS7Error, NULL); + } + WrapPKCS7si(klass, obj, p7si); + + return obj; +} + +static VALUE +ossl_pkcs7si_initialize(VALUE self, VALUE cert, VALUE key, VALUE digest) +{ + PKCS7_SIGNER_INFO *p7si; + EVP_PKEY *pkey; + X509 *x509; + const EVP_MD *md; + + pkey = GetPrivPKeyPtr(key); /* NO NEED TO DUP */ + x509 = GetX509CertPtr(cert); /* NO NEED TO DUP */ + md = GetDigestPtr(digest); + GetPKCS7si(self, p7si); + if (!(PKCS7_SIGNER_INFO_set(p7si, x509, pkey, (EVP_MD*)md))) { + ossl_raise(ePKCS7Error, NULL); + } + + return self; +} + +static VALUE +ossl_pkcs7si_get_issuer(VALUE self) +{ + PKCS7_SIGNER_INFO *p7si; + + GetPKCS7si(self, p7si); + + return ossl_x509name_new(p7si->issuer_and_serial->issuer); +} + +static VALUE +ossl_pkcs7si_get_serial(VALUE self) +{ + PKCS7_SIGNER_INFO *p7si; + + GetPKCS7si(self, p7si); + + return asn1integer_to_num(p7si->issuer_and_serial->serial); +} + +static VALUE +ossl_pkcs7si_get_signed_time(VALUE self) +{ + PKCS7_SIGNER_INFO *p7si; + ASN1_TYPE *asn1obj; + + GetPKCS7si(self, p7si); + + if (!(asn1obj = PKCS7_get_signed_attribute(p7si, NID_pkcs9_signingTime))) { + ossl_raise(ePKCS7Error, NULL); + } + if (asn1obj->type == V_ASN1_UTCTIME) { + return asn1time_to_time(asn1obj->value.utctime); + } + /* + * OR + * ossl_raise(ePKCS7Error, "..."); + * ? + */ + + return Qnil; +} + +/* + * RECIPIENT INFO + */ +static VALUE +ossl_pkcs7ri_alloc(VALUE klass) +{ + PKCS7_RECIP_INFO *p7ri; + VALUE obj; + + if (!(p7ri = PKCS7_RECIP_INFO_new())) { + ossl_raise(ePKCS7Error, NULL); + } + WrapPKCS7ri(klass, obj, p7ri); + + return obj; +} + +static VALUE +ossl_pkcs7ri_initialize(VALUE self, VALUE cert) +{ + PKCS7_RECIP_INFO *p7ri; + X509 *x509; + + x509 = GetX509CertPtr(cert); /* NO NEED TO DUP */ + GetPKCS7ri(self, p7ri); + if (!PKCS7_RECIP_INFO_set(p7ri, x509)) { + ossl_raise(ePKCS7Error, NULL); + } + + return self; +} + +static VALUE +ossl_pkcs7ri_get_issuer(VALUE self) +{ + PKCS7_RECIP_INFO *p7ri; + + GetPKCS7ri(self, p7ri); + + return ossl_x509name_new(p7ri->issuer_and_serial->issuer); +} + +static VALUE +ossl_pkcs7ri_get_serial(VALUE self) +{ + PKCS7_RECIP_INFO *p7ri; + + GetPKCS7ri(self, p7ri); + + return asn1integer_to_num(p7ri->issuer_and_serial->serial); +} + +static VALUE +ossl_pkcs7ri_get_enc_key(VALUE self) +{ + PKCS7_RECIP_INFO *p7ri; + + GetPKCS7ri(self, p7ri); + + return asn1str_to_str(p7ri->enc_key); +} + +/* + * INIT + */ +void +Init_ossl_pkcs7(void) +{ + cPKCS7 = rb_define_class_under(mOSSL, "PKCS7", rb_cObject); + ePKCS7Error = rb_define_class_under(cPKCS7, "PKCS7Error", eOSSLError); + rb_define_singleton_method(cPKCS7, "read_smime", ossl_pkcs7_s_read_smime, 1); + rb_define_singleton_method(cPKCS7, "write_smime", ossl_pkcs7_s_write_smime, -1); + rb_define_singleton_method(cPKCS7, "sign", ossl_pkcs7_s_sign, -1); + rb_define_singleton_method(cPKCS7, "encrypt", ossl_pkcs7_s_encrypt, -1); + rb_attr(cPKCS7, rb_intern("data"), 1, 0, Qfalse); + rb_attr(cPKCS7, rb_intern("error_string"), 1, 1, Qfalse); + rb_define_alloc_func(cPKCS7, ossl_pkcs7_alloc); + rb_define_copy_func(cPKCS7, ossl_pkcs7_copy); + rb_define_method(cPKCS7, "initialize", ossl_pkcs7_initialize, -1); + rb_define_method(cPKCS7, "type=", ossl_pkcs7_set_type, 1); + rb_define_method(cPKCS7, "type", ossl_pkcs7_get_type, 0); + rb_define_method(cPKCS7, "detached=", ossl_pkcs7_set_detached, 1); + rb_define_method(cPKCS7, "detached", ossl_pkcs7_get_detached, 0); + rb_define_method(cPKCS7, "detached?", ossl_pkcs7_detached_p, 0); + rb_define_method(cPKCS7, "cipher=", ossl_pkcs7_set_cipher, 1); + rb_define_method(cPKCS7, "add_signer", ossl_pkcs7_add_signer, 1); + rb_define_method(cPKCS7, "signers", ossl_pkcs7_get_signer, 0); + rb_define_method(cPKCS7, "add_recipient", ossl_pkcs7_add_recipient, 1); + rb_define_method(cPKCS7, "recipients", ossl_pkcs7_get_recipient, 0); + rb_define_method(cPKCS7, "add_certificate", ossl_pkcs7_add_certificate, 1); + rb_define_method(cPKCS7, "certificates=", ossl_pkcs7_set_certificates, 1); + rb_define_method(cPKCS7, "certificates", ossl_pkcs7_get_certificates, 0); + rb_define_method(cPKCS7, "add_crl", ossl_pkcs7_add_crl, 1); + rb_define_method(cPKCS7, "crls=", ossl_pkcs7_set_crls, 1); + rb_define_method(cPKCS7, "crls", ossl_pkcs7_get_crls, 0); + rb_define_method(cPKCS7, "add_data", ossl_pkcs7_add_data, 1); + rb_define_alias(cPKCS7, "data=", "add_data"); + rb_define_method(cPKCS7, "verify", ossl_pkcs7_verify, -1); + rb_define_method(cPKCS7, "decrypt", ossl_pkcs7_decrypt, -1); + rb_define_method(cPKCS7, "to_pem", ossl_pkcs7_to_pem, 0); + rb_define_alias(cPKCS7, "to_s", "to_pem"); + rb_define_method(cPKCS7, "to_der", ossl_pkcs7_to_der, 0); + + cPKCS7Signer = rb_define_class_under(cPKCS7, "SignerInfo", rb_cObject); + rb_define_const(cPKCS7, "Signer", cPKCS7Signer); + rb_define_alloc_func(cPKCS7Signer, ossl_pkcs7si_alloc); + rb_define_method(cPKCS7Signer, "initialize", ossl_pkcs7si_initialize,3); + rb_define_method(cPKCS7Signer, "issuer", ossl_pkcs7si_get_issuer, 0); + rb_define_alias(cPKCS7Signer, "name", "issuer"); + rb_define_method(cPKCS7Signer, "serial", ossl_pkcs7si_get_serial,0); + rb_define_method(cPKCS7Signer,"signed_time",ossl_pkcs7si_get_signed_time,0); + + cPKCS7Recipient = rb_define_class_under(cPKCS7,"RecipientInfo",rb_cObject); + rb_define_alloc_func(cPKCS7Recipient, ossl_pkcs7ri_alloc); + rb_define_method(cPKCS7Recipient, "initialize", ossl_pkcs7ri_initialize,1); + rb_define_method(cPKCS7Recipient, "issuer", ossl_pkcs7ri_get_issuer,0); + rb_define_method(cPKCS7Recipient, "serial", ossl_pkcs7ri_get_serial,0); + rb_define_method(cPKCS7Recipient, "enc_key", ossl_pkcs7ri_get_enc_key,0); + +#define DefPKCS7Const(x) rb_define_const(cPKCS7, #x, INT2NUM(PKCS7_##x)) + + DefPKCS7Const(TEXT); + DefPKCS7Const(NOCERTS); + DefPKCS7Const(NOSIGS); + DefPKCS7Const(NOCHAIN); + DefPKCS7Const(NOINTERN); + DefPKCS7Const(NOVERIFY); + DefPKCS7Const(DETACHED); + DefPKCS7Const(BINARY); + DefPKCS7Const(NOATTR); + DefPKCS7Const(NOSMIMECAP); +} diff --git a/ext/openssl/ossl_pkcs7.h b/ext/openssl/ossl_pkcs7.h new file mode 100644 index 00000000..371c4211 --- /dev/null +++ b/ext/openssl/ossl_pkcs7.h @@ -0,0 +1,22 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2001-2002 Michal Rokos <m.rokos@sh.cvut.cz> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#if !defined(_OSSL_PKCS7_H_) +#define _OSSL_PKCS7_H_ + +extern VALUE cPKCS7; +extern VALUE cPKCS7Signer; +extern VALUE cPKCS7Recipient; +extern VALUE ePKCS7Error; + +void Init_ossl_pkcs7(void); + +#endif /* _OSSL_PKCS7_H_ */ + diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c new file mode 100644 index 00000000..43942274 --- /dev/null +++ b/ext/openssl/ossl_pkey.c @@ -0,0 +1,439 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2001-2002 Michal Rokos <m.rokos@sh.cvut.cz> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#include "ossl.h" + +/* + * Classes + */ +VALUE mPKey; +VALUE cPKey; +VALUE ePKeyError; +ID id_private_q; + +/* + * callback for generating keys + */ +void +ossl_generate_cb(int p, int n, void *arg) +{ + VALUE ary; + + ary = rb_ary_new2(2); + rb_ary_store(ary, 0, INT2NUM(p)); + rb_ary_store(ary, 1, INT2NUM(n)); + + rb_yield(ary); +} + +#if HAVE_BN_GENCB +/* OpenSSL 2nd version of GN generation callback */ +int +ossl_generate_cb_2(int p, int n, BN_GENCB *cb) +{ + VALUE ary; + struct ossl_generate_cb_arg *arg; + int state; + + arg = (struct ossl_generate_cb_arg *)cb->arg; + if (arg->yield) { + ary = rb_ary_new2(2); + rb_ary_store(ary, 0, INT2NUM(p)); + rb_ary_store(ary, 1, INT2NUM(n)); + + /* + * can be break by raising exception or 'break' + */ + rb_protect(rb_yield, ary, &state); + if (state) { + arg->stop = 1; + arg->state = state; + } + } + if (arg->stop) return 0; + return 1; +} + +void +ossl_generate_cb_stop(void *ptr) +{ + struct ossl_generate_cb_arg *arg = (struct ossl_generate_cb_arg *)ptr; + arg->stop = 1; +} +#endif + +/* + * Public + */ +VALUE +ossl_pkey_new(EVP_PKEY *pkey) +{ + if (!pkey) { + ossl_raise(ePKeyError, "Cannot make new key from NULL."); + } + switch (EVP_PKEY_type(pkey->type)) { +#if !defined(OPENSSL_NO_RSA) + case EVP_PKEY_RSA: + return ossl_rsa_new(pkey); +#endif +#if !defined(OPENSSL_NO_DSA) + case EVP_PKEY_DSA: + return ossl_dsa_new(pkey); +#endif +#if !defined(OPENSSL_NO_DH) + case EVP_PKEY_DH: + return ossl_dh_new(pkey); +#endif +#if !defined(OPENSSL_NO_EC) && (OPENSSL_VERSION_NUMBER >= 0x0090802fL) + case EVP_PKEY_EC: + return ossl_ec_new(pkey); +#endif + default: + ossl_raise(ePKeyError, "unsupported key type"); + } + + UNREACHABLE; +} + +VALUE +ossl_pkey_new_from_file(VALUE filename) +{ + FILE *fp; + EVP_PKEY *pkey; + + SafeStringValue(filename); + if (!(fp = fopen(RSTRING_PTR(filename), "r"))) { + ossl_raise(ePKeyError, "%s", strerror(errno)); + } + rb_fd_fix_cloexec(fileno(fp)); + + pkey = PEM_read_PrivateKey(fp, NULL, ossl_pem_passwd_cb, NULL); + fclose(fp); + if (!pkey) { + ossl_raise(ePKeyError, NULL); + } + + return ossl_pkey_new(pkey); +} + +/* + * call-seq: + * OpenSSL::PKey.read(string [, pwd ] ) -> PKey + * OpenSSL::PKey.read(file [, pwd ]) -> PKey + * + * === Parameters + * * +string+ is a DER- or PEM-encoded string containing an arbitrary private + * or public key. + * * +file+ is an instance of +File+ containing a DER- or PEM-encoded + * arbitrary private or public key. + * * +pwd+ is an optional password in case +string+ or +file+ is an encrypted + * PEM resource. + */ +static VALUE +ossl_pkey_new_from_data(int argc, VALUE *argv, VALUE self) +{ + EVP_PKEY *pkey; + BIO *bio; + VALUE data, pass; + char *passwd = NULL; + + rb_scan_args(argc, argv, "11", &data, &pass); + + bio = ossl_obj2bio(data); + if (!(pkey = d2i_PrivateKey_bio(bio, NULL))) { + OSSL_BIO_reset(bio); + if (!NIL_P(pass)) { + passwd = StringValuePtr(pass); + } + if (!(pkey = PEM_read_bio_PrivateKey(bio, NULL, ossl_pem_passwd_cb, passwd))) { + OSSL_BIO_reset(bio); + if (!(pkey = d2i_PUBKEY_bio(bio, NULL))) { + OSSL_BIO_reset(bio); + if (!NIL_P(pass)) { + passwd = StringValuePtr(pass); + } + pkey = PEM_read_bio_PUBKEY(bio, NULL, ossl_pem_passwd_cb, passwd); + } + } + } + + BIO_free(bio); + if (!pkey) + ossl_raise(rb_eArgError, "Could not parse PKey"); + return ossl_pkey_new(pkey); +} + +EVP_PKEY * +GetPKeyPtr(VALUE obj) +{ + EVP_PKEY *pkey; + + SafeGetPKey(obj, pkey); + + return pkey; +} + +EVP_PKEY * +GetPrivPKeyPtr(VALUE obj) +{ + EVP_PKEY *pkey; + + if (rb_funcall(obj, id_private_q, 0, NULL) != Qtrue) { + ossl_raise(rb_eArgError, "Private key is needed."); + } + SafeGetPKey(obj, pkey); + + return pkey; +} + +EVP_PKEY * +DupPKeyPtr(VALUE obj) +{ + EVP_PKEY *pkey; + + SafeGetPKey(obj, pkey); + CRYPTO_add(&pkey->references, 1, CRYPTO_LOCK_EVP_PKEY); + + return pkey; +} + +EVP_PKEY * +DupPrivPKeyPtr(VALUE obj) +{ + EVP_PKEY *pkey; + + if (rb_funcall(obj, id_private_q, 0, NULL) != Qtrue) { + ossl_raise(rb_eArgError, "Private key is needed."); + } + SafeGetPKey(obj, pkey); + CRYPTO_add(&pkey->references, 1, CRYPTO_LOCK_EVP_PKEY); + + return pkey; +} + +/* + * Private + */ +static VALUE +ossl_pkey_alloc(VALUE klass) +{ + EVP_PKEY *pkey; + VALUE obj; + + if (!(pkey = EVP_PKEY_new())) { + ossl_raise(ePKeyError, NULL); + } + WrapPKey(klass, obj, pkey); + + return obj; +} + +/* + * call-seq: + * PKeyClass.new -> self + * + * Because PKey is an abstract class, actually calling this method explicitly + * will raise a +NotImplementedError+. + */ +static VALUE +ossl_pkey_initialize(VALUE self) +{ + if (rb_obj_is_instance_of(self, cPKey)) { + ossl_raise(rb_eNotImpError, "OpenSSL::PKey::PKey is an abstract class."); + } + return self; +} + +/* + * call-seq: + * pkey.sign(digest, data) -> String + * + * To sign the +String+ +data+, +digest+, an instance of OpenSSL::Digest, must + * be provided. The return value is again a +String+ containing the signature. + * A PKeyError is raised should errors occur. + * Any previous state of the +Digest+ instance is irrelevant to the signature + * outcome, the digest instance is reset to its initial state during the + * operation. + * + * == Example + * data = 'Sign me!' + * digest = OpenSSL::Digest::SHA256.new + * pkey = OpenSSL::PKey::RSA.new(2048) + * signature = pkey.sign(digest, data) + */ +static VALUE +ossl_pkey_sign(VALUE self, VALUE digest, VALUE data) +{ + EVP_PKEY *pkey; + EVP_MD_CTX ctx; + unsigned int buf_len; + VALUE str; + + if (rb_funcall(self, id_private_q, 0, NULL) != Qtrue) { + ossl_raise(rb_eArgError, "Private key is needed."); + } + GetPKey(self, pkey); + EVP_SignInit(&ctx, GetDigestPtr(digest)); + StringValue(data); + EVP_SignUpdate(&ctx, RSTRING_PTR(data), RSTRING_LEN(data)); + str = rb_str_new(0, EVP_PKEY_size(pkey)+16); + if (!EVP_SignFinal(&ctx, (unsigned char *)RSTRING_PTR(str), &buf_len, pkey)) + ossl_raise(ePKeyError, NULL); + assert((long)buf_len <= RSTRING_LEN(str)); + rb_str_set_len(str, buf_len); + + return str; +} + +/* + * call-seq: + * pkey.verify(digest, signature, data) -> String + * + * To verify the +String+ +signature+, +digest+, an instance of + * OpenSSL::Digest, must be provided to re-compute the message digest of the + * original +data+, also a +String+. The return value is +true+ if the + * signature is valid, +false+ otherwise. A PKeyError is raised should errors + * occur. + * Any previous state of the +Digest+ instance is irrelevant to the validation + * outcome, the digest instance is reset to its initial state during the + * operation. + * + * == Example + * data = 'Sign me!' + * digest = OpenSSL::Digest::SHA256.new + * pkey = OpenSSL::PKey::RSA.new(2048) + * signature = pkey.sign(digest, data) + * pub_key = pkey.public_key + * puts pub_key.verify(digest, signature, data) # => true + */ +static VALUE +ossl_pkey_verify(VALUE self, VALUE digest, VALUE sig, VALUE data) +{ + EVP_PKEY *pkey; + EVP_MD_CTX ctx; + int result; + + GetPKey(self, pkey); + StringValue(sig); + StringValue(data); + EVP_VerifyInit(&ctx, GetDigestPtr(digest)); + EVP_VerifyUpdate(&ctx, RSTRING_PTR(data), RSTRING_LEN(data)); + result = EVP_VerifyFinal(&ctx, (unsigned char *)RSTRING_PTR(sig), RSTRING_LENINT(sig), pkey); + EVP_MD_CTX_cleanup(&ctx); + switch (result) { + case 0: + return Qfalse; + case 1: + return Qtrue; + default: + ossl_raise(ePKeyError, NULL); + } + return Qnil; /* dummy */ +} + +/* + * INIT + */ +void +Init_ossl_pkey(void) +{ +#if 0 + mOSSL = rb_define_module("OpenSSL"); /* let rdoc know about mOSSL */ +#endif + + /* Document-module: OpenSSL::PKey + * + * == Asymmetric Public Key Algorithms + * + * Asymmetric public key algorithms solve the problem of establishing and + * sharing secret keys to en-/decrypt messages. The key in such an + * algorithm consists of two parts: a public key that may be distributed + * to others and a private key that needs to remain secret. + * + * Messages encrypted with a public key can only be encrypted by + * recipients that are in possession of the associated private key. + * Since public key algorithms are considerably slower than symmetric + * key algorithms (cf. OpenSSL::Cipher) they are often used to establish + * a symmetric key shared between two parties that are in possession of + * each other's public key. + * + * Asymmetric algorithms offer a lot of nice features that are used in a + * lot of different areas. A very common application is the creation and + * validation of digital signatures. To sign a document, the signatory + * generally uses a message digest algorithm (cf. OpenSSL::Digest) to + * compute a digest of the document that is then encrypted (i.e. signed) + * using the private key. Anyone in possession of the public key may then + * verify the signature by computing the message digest of the original + * document on their own, decrypting the signature using the signatory's + * public key and comparing the result to the message digest they + * previously computed. The signature is valid if and only if the + * decrypted signature is equal to this message digest. + * + * The PKey module offers support for three popular public/private key + * algorithms: + * * RSA (OpenSSL::PKey::RSA) + * * DSA (OpenSSL::PKey::DSA) + * * Elliptic Curve Cryptography (OpenSSL::PKey::EC) + * Each of these implementations is in fact a sub-class of the abstract + * PKey class which offers the interface for supporting digital signatures + * in the form of PKey#sign and PKey#verify. + * + * == Diffie-Hellman Key Exchange + * + * Finally PKey also features OpenSSL::PKey::DH, an implementation of + * the Diffie-Hellman key exchange protocol based on discrete logarithms + * in finite fields, the same basis that DSA is built on. + * The Diffie-Hellman protocol can be used to exchange (symmetric) keys + * over insecure channels without needing any prior joint knowledge + * between the participating parties. As the security of DH demands + * relatively long "public keys" (i.e. the part that is overtly + * transmitted between participants) DH tends to be quite slow. If + * security or speed is your primary concern, OpenSSL::PKey::EC offers + * another implementation of the Diffie-Hellman protocol. + * + */ + mPKey = rb_define_module_under(mOSSL, "PKey"); + + /* Document-class: OpenSSL::PKey::PKeyError + * + *Raised when errors occur during PKey#sign or PKey#verify. + */ + ePKeyError = rb_define_class_under(mPKey, "PKeyError", eOSSLError); + + /* Document-class: OpenSSL::PKey::PKey + * + * An abstract class that bundles signature creation (PKey#sign) and + * validation (PKey#verify) that is common to all implementations except + * OpenSSL::PKey::DH + * * OpenSSL::PKey::RSA + * * OpenSSL::PKey::DSA + * * OpenSSL::PKey::EC + */ + cPKey = rb_define_class_under(mPKey, "PKey", rb_cObject); + + rb_define_module_function(mPKey, "read", ossl_pkey_new_from_data, -1); + + rb_define_alloc_func(cPKey, ossl_pkey_alloc); + rb_define_method(cPKey, "initialize", ossl_pkey_initialize, 0); + + rb_define_method(cPKey, "sign", ossl_pkey_sign, 2); + rb_define_method(cPKey, "verify", ossl_pkey_verify, 3); + + id_private_q = rb_intern("private?"); + + /* + * INIT rsa, dsa, dh, ec + */ + Init_ossl_rsa(); + Init_ossl_dsa(); + Init_ossl_dh(); + Init_ossl_ec(); +} + diff --git a/ext/openssl/ossl_pkey.h b/ext/openssl/ossl_pkey.h new file mode 100644 index 00000000..686e956e --- /dev/null +++ b/ext/openssl/ossl_pkey.h @@ -0,0 +1,151 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2001 Michal Rokos <m.rokos@sh.cvut.cz> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#if !defined(_OSSL_PKEY_H_) +#define _OSSL_PKEY_H_ + +extern VALUE mPKey; +extern VALUE cPKey; +extern VALUE ePKeyError; +extern ID id_private_q; + +#define OSSL_PKEY_SET_PRIVATE(obj) rb_iv_set((obj), "private", Qtrue) +#define OSSL_PKEY_SET_PUBLIC(obj) rb_iv_set((obj), "private", Qfalse) +#define OSSL_PKEY_IS_PRIVATE(obj) (rb_iv_get((obj), "private") == Qtrue) + +#define WrapPKey(klass, obj, pkey) do { \ + if (!(pkey)) { \ + rb_raise(rb_eRuntimeError, "PKEY wasn't initialized!"); \ + } \ + (obj) = Data_Wrap_Struct((klass), 0, EVP_PKEY_free, (pkey)); \ + OSSL_PKEY_SET_PUBLIC(obj); \ +} while (0) +#define GetPKey(obj, pkey) do {\ + Data_Get_Struct((obj), EVP_PKEY, (pkey));\ + if (!(pkey)) { \ + rb_raise(rb_eRuntimeError, "PKEY wasn't initialized!");\ + } \ +} while (0) +#define SafeGetPKey(obj, pkey) do { \ + OSSL_Check_Kind((obj), cPKey); \ + GetPKey((obj), (pkey)); \ +} while (0) + +void ossl_generate_cb(int, int, void *); +#define HAVE_BN_GENCB defined(HAVE_RSA_GENERATE_KEY_EX) || defined(HAVE_DH_GENERATE_PARAMETERS_EX) || defined(HAVE_DSA_GENERATE_PARAMETERS_EX) +#if HAVE_BN_GENCB +struct ossl_generate_cb_arg { + int yield; + int stop; + int state; +}; +int ossl_generate_cb_2(int p, int n, BN_GENCB *cb); +void ossl_generate_cb_stop(void *ptr); +#endif + +VALUE ossl_pkey_new(EVP_PKEY *); +VALUE ossl_pkey_new_from_file(VALUE); +EVP_PKEY *GetPKeyPtr(VALUE); +EVP_PKEY *DupPKeyPtr(VALUE); +EVP_PKEY *GetPrivPKeyPtr(VALUE); +EVP_PKEY *DupPrivPKeyPtr(VALUE); +void Init_ossl_pkey(void); + +/* + * RSA + */ +extern VALUE cRSA; +extern VALUE eRSAError; + +VALUE ossl_rsa_new(EVP_PKEY *); +void Init_ossl_rsa(void); + +/* + * DSA + */ +extern VALUE cDSA; +extern VALUE eDSAError; + +VALUE ossl_dsa_new(EVP_PKEY *); +void Init_ossl_dsa(void); + +/* + * DH + */ +extern VALUE cDH; +extern VALUE eDHError; +extern DH *OSSL_DEFAULT_DH_512; +extern DH *OSSL_DEFAULT_DH_1024; + +VALUE ossl_dh_new(EVP_PKEY *); +void Init_ossl_dh(void); + +/* + * EC + */ +extern VALUE cEC; +extern VALUE eECError; +extern VALUE cEC_GROUP; +extern VALUE eEC_GROUP; +extern VALUE cEC_POINT; +extern VALUE eEC_POINT; +VALUE ossl_ec_new(EVP_PKEY *); +void Init_ossl_ec(void); + + +#define OSSL_PKEY_BN(keytype, name) \ +/* \ + * call-seq: \ + * key.##name -> aBN \ + */ \ +static VALUE ossl_##keytype##_get_##name(VALUE self) \ +{ \ + EVP_PKEY *pkey; \ + BIGNUM *bn; \ + \ + GetPKey(self, pkey); \ + bn = pkey->pkey.keytype->name; \ + if (bn == NULL) \ + return Qnil; \ + return ossl_bn_new(bn); \ +} \ +/* \ + * call-seq: \ + * key.##name = bn -> bn \ + */ \ +static VALUE ossl_##keytype##_set_##name(VALUE self, VALUE bignum) \ +{ \ + EVP_PKEY *pkey; \ + BIGNUM *bn; \ + \ + GetPKey(self, pkey); \ + if (NIL_P(bignum)) { \ + BN_clear_free(pkey->pkey.keytype->name); \ + pkey->pkey.keytype->name = NULL; \ + return Qnil; \ + } \ + \ + bn = GetBNPtr(bignum); \ + if (pkey->pkey.keytype->name == NULL) \ + pkey->pkey.keytype->name = BN_new(); \ + if (pkey->pkey.keytype->name == NULL) \ + ossl_raise(eBNError, NULL); \ + if (BN_copy(pkey->pkey.keytype->name, bn) == NULL) \ + ossl_raise(eBNError, NULL); \ + return bignum; \ +} + +#define DEF_OSSL_PKEY_BN(class, keytype, name) \ +do { \ + rb_define_method((class), #name, ossl_##keytype##_get_##name, 0); \ + rb_define_method((class), #name "=", ossl_##keytype##_set_##name, 1);\ +} while (0) + +#endif /* _OSSL_PKEY_H_ */ diff --git a/ext/openssl/ossl_pkey_dh.c b/ext/openssl/ossl_pkey_dh.c new file mode 100644 index 00000000..cf283263 --- /dev/null +++ b/ext/openssl/ossl_pkey_dh.c @@ -0,0 +1,666 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2001-2002 Michal Rokos <m.rokos@sh.cvut.cz> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#if !defined(OPENSSL_NO_DH) + +#include "ossl.h" + +#define GetPKeyDH(obj, pkey) do { \ + GetPKey((obj), (pkey)); \ + if (EVP_PKEY_type((pkey)->type) != EVP_PKEY_DH) { /* PARANOIA? */ \ + ossl_raise(rb_eRuntimeError, "THIS IS NOT A DH!") ; \ + } \ +} while (0) + +#define DH_HAS_PRIVATE(dh) ((dh)->priv_key) + +#ifdef OSSL_ENGINE_ENABLED +# define DH_PRIVATE(dh) (DH_HAS_PRIVATE(dh) || (dh)->engine) +#else +# define DH_PRIVATE(dh) DH_HAS_PRIVATE(dh) +#endif + + +/* + * Classes + */ +VALUE cDH; +VALUE eDHError; + +/* + * Public + */ +static VALUE +dh_instance(VALUE klass, DH *dh) +{ + EVP_PKEY *pkey; + VALUE obj; + + if (!dh) { + return Qfalse; + } + if (!(pkey = EVP_PKEY_new())) { + return Qfalse; + } + if (!EVP_PKEY_assign_DH(pkey, dh)) { + EVP_PKEY_free(pkey); + return Qfalse; + } + WrapPKey(klass, obj, pkey); + + return obj; +} + +VALUE +ossl_dh_new(EVP_PKEY *pkey) +{ + VALUE obj; + + if (!pkey) { + obj = dh_instance(cDH, DH_new()); + } else { + if (EVP_PKEY_type(pkey->type) != EVP_PKEY_DH) { + ossl_raise(rb_eTypeError, "Not a DH key!"); + } + WrapPKey(cDH, obj, pkey); + } + if (obj == Qfalse) { + ossl_raise(eDHError, NULL); + } + + return obj; +} + +/* + * Private + */ +#if defined(HAVE_DH_GENERATE_PARAMETERS_EX) && HAVE_BN_GENCB +struct dh_blocking_gen_arg { + DH *dh; + int size; + int gen; + BN_GENCB *cb; + int result; +}; + +static void * +dh_blocking_gen(void *arg) +{ + struct dh_blocking_gen_arg *gen = (struct dh_blocking_gen_arg *)arg; + gen->result = DH_generate_parameters_ex(gen->dh, gen->size, gen->gen, gen->cb); + return 0; +} +#endif + +static DH * +dh_generate(int size, int gen) +{ +#if defined(HAVE_DH_GENERATE_PARAMETERS_EX) && HAVE_BN_GENCB + BN_GENCB cb; + struct ossl_generate_cb_arg cb_arg; + struct dh_blocking_gen_arg gen_arg; + DH *dh = DH_new(); + + if (!dh) return 0; + + memset(&cb_arg, 0, sizeof(struct ossl_generate_cb_arg)); + if (rb_block_given_p()) + cb_arg.yield = 1; + BN_GENCB_set(&cb, ossl_generate_cb_2, &cb_arg); + gen_arg.dh = dh; + gen_arg.size = size; + gen_arg.gen = gen; + gen_arg.cb = &cb; + if (cb_arg.yield == 1) { + /* we cannot release GVL when callback proc is supplied */ + dh_blocking_gen(&gen_arg); + } else { + /* there's a chance to unblock */ + rb_thread_call_without_gvl(dh_blocking_gen, &gen_arg, ossl_generate_cb_stop, &cb_arg); + } + + if (!gen_arg.result) { + DH_free(dh); + if (cb_arg.state) rb_jump_tag(cb_arg.state); + return 0; + } +#else + DH *dh; + + dh = DH_generate_parameters(size, gen, rb_block_given_p() ? ossl_generate_cb : NULL, NULL); + if (!dh) return 0; +#endif + + if (!DH_generate_key(dh)) { + DH_free(dh); + return 0; + } + + return dh; +} + +/* + * call-seq: + * DH.generate(size [, generator]) -> dh + * + * Creates a new DH instance from scratch by generating the private and public + * components alike. + * + * === Parameters + * * +size+ is an integer representing the desired key size. Keys smaller than 1024 bits should be considered insecure. + * * +generator+ is a small number > 1, typically 2 or 5. + * + */ +static VALUE +ossl_dh_s_generate(int argc, VALUE *argv, VALUE klass) +{ + DH *dh ; + int g = 2; + VALUE size, gen, obj; + + if (rb_scan_args(argc, argv, "11", &size, &gen) == 2) { + g = NUM2INT(gen); + } + dh = dh_generate(NUM2INT(size), g); + obj = dh_instance(klass, dh); + if (obj == Qfalse) { + DH_free(dh); + ossl_raise(eDHError, NULL); + } + + return obj; +} + +/* + * call-seq: + * DH.new([size [, generator] | string]) -> dh + * + * Either generates a DH instance from scratch or by reading already existing + * DH parameters from +string+. Note that when reading a DH instance from + * data that was encoded from a DH instance by using DH#to_pem or DH#to_der + * the result will *not* contain a public/private key pair yet. This needs to + * be generated using DH#generate_key! first. + * + * === Parameters + * * +size+ is an integer representing the desired key size. Keys smaller than 1024 bits should be considered insecure. + * * +generator+ is a small number > 1, typically 2 or 5. + * * +string+ contains the DER or PEM encoded key. + * + * === Examples + * DH.new # -> dh + * DH.new(1024) # -> dh + * DH.new(1024, 5) # -> dh + * #Reading DH parameters + * dh = DH.new(File.read('parameters.pem')) # -> dh, but no public/private key yet + * dh.generate_key! # -> dh with public and private key + */ +static VALUE +ossl_dh_initialize(int argc, VALUE *argv, VALUE self) +{ + EVP_PKEY *pkey; + DH *dh; + int g = 2; + BIO *in; + VALUE arg, gen; + + GetPKey(self, pkey); + if(rb_scan_args(argc, argv, "02", &arg, &gen) == 0) { + dh = DH_new(); + } + else if (FIXNUM_P(arg)) { + if (!NIL_P(gen)) { + g = NUM2INT(gen); + } + if (!(dh = dh_generate(FIX2INT(arg), g))) { + ossl_raise(eDHError, NULL); + } + } + else { + arg = ossl_to_der_if_possible(arg); + in = ossl_obj2bio(arg); + dh = PEM_read_bio_DHparams(in, NULL, NULL, NULL); + if (!dh){ + OSSL_BIO_reset(in); + dh = d2i_DHparams_bio(in, NULL); + } + BIO_free(in); + if (!dh) { + ossl_raise(eDHError, NULL); + } + } + if (!EVP_PKEY_assign_DH(pkey, dh)) { + DH_free(dh); + ossl_raise(eDHError, NULL); + } + return self; +} + +/* + * call-seq: + * dh.public? -> true | false + * + * Indicates whether this DH instance has a public key associated with it or + * not. The public key may be retrieved with DH#pub_key. + */ +static VALUE +ossl_dh_is_public(VALUE self) +{ + EVP_PKEY *pkey; + + GetPKeyDH(self, pkey); + + return (pkey->pkey.dh->pub_key) ? Qtrue : Qfalse; +} + +/* + * call-seq: + * dh.private? -> true | false + * + * Indicates whether this DH instance has a private key associated with it or + * not. The private key may be retrieved with DH#priv_key. + */ +static VALUE +ossl_dh_is_private(VALUE self) +{ + EVP_PKEY *pkey; + + GetPKeyDH(self, pkey); + + return (DH_PRIVATE(pkey->pkey.dh)) ? Qtrue : Qfalse; +} + +/* + * call-seq: + * dh.export -> aString + * dh.to_pem -> aString + * dh.to_s -> aString + * + * Encodes this DH to its PEM encoding. Note that any existing per-session + * public/private keys will *not* get encoded, just the Diffie-Hellman + * parameters will be encoded. + */ +static VALUE +ossl_dh_export(VALUE self) +{ + EVP_PKEY *pkey; + BIO *out; + VALUE str; + + GetPKeyDH(self, pkey); + if (!(out = BIO_new(BIO_s_mem()))) { + ossl_raise(eDHError, NULL); + } + if (!PEM_write_bio_DHparams(out, pkey->pkey.dh)) { + BIO_free(out); + ossl_raise(eDHError, NULL); + } + str = ossl_membio2str(out); + + return str; +} + +/* + * call-seq: + * dh.to_der -> aString + * + * Encodes this DH to its DER encoding. Note that any existing per-session + * public/private keys will *not* get encoded, just the Diffie-Hellman + * parameters will be encoded. + + */ +static VALUE +ossl_dh_to_der(VALUE self) +{ + EVP_PKEY *pkey; + unsigned char *p; + long len; + VALUE str; + + GetPKeyDH(self, pkey); + if((len = i2d_DHparams(pkey->pkey.dh, NULL)) <= 0) + ossl_raise(eDHError, NULL); + str = rb_str_new(0, len); + p = (unsigned char *)RSTRING_PTR(str); + if(i2d_DHparams(pkey->pkey.dh, &p) < 0) + ossl_raise(eDHError, NULL); + ossl_str_adjust(str, p); + + return str; +} + +/* + * call-seq: + * dh.params -> hash + * + * Stores all parameters of key to the hash + * INSECURE: PRIVATE INFORMATIONS CAN LEAK OUT!!! + * Don't use :-)) (I's up to you) + */ +static VALUE +ossl_dh_get_params(VALUE self) +{ + EVP_PKEY *pkey; + VALUE hash; + + GetPKeyDH(self, pkey); + + hash = rb_hash_new(); + + rb_hash_aset(hash, rb_str_new2("p"), ossl_bn_new(pkey->pkey.dh->p)); + rb_hash_aset(hash, rb_str_new2("g"), ossl_bn_new(pkey->pkey.dh->g)); + rb_hash_aset(hash, rb_str_new2("pub_key"), ossl_bn_new(pkey->pkey.dh->pub_key)); + rb_hash_aset(hash, rb_str_new2("priv_key"), ossl_bn_new(pkey->pkey.dh->priv_key)); + + return hash; +} + +/* + * call-seq: + * dh.to_text -> aString + * + * Prints all parameters of key to buffer + * INSECURE: PRIVATE INFORMATIONS CAN LEAK OUT!!! + * Don't use :-)) (I's up to you) + */ +static VALUE +ossl_dh_to_text(VALUE self) +{ + EVP_PKEY *pkey; + BIO *out; + VALUE str; + + GetPKeyDH(self, pkey); + if (!(out = BIO_new(BIO_s_mem()))) { + ossl_raise(eDHError, NULL); + } + if (!DHparams_print(out, pkey->pkey.dh)) { + BIO_free(out); + ossl_raise(eDHError, NULL); + } + str = ossl_membio2str(out); + + return str; +} + +/* + * call-seq: + * dh.public_key -> aDH + * + * Returns a new DH instance that carries just the public information, i.e. + * the prime +p+ and the generator +g+, but no public/private key yet. Such + * a pair may be generated using DH#generate_key!. The "public key" needed + * for a key exchange with DH#compute_key is considered as per-session + * information and may be retrieved with DH#pub_key once a key pair has + * been generated. + * If the current instance already contains private information (and thus a + * valid public/private key pair), this information will no longer be present + * in the new instance generated by DH#public_key. This feature is helpful for + * publishing the Diffie-Hellman parameters without leaking any of the private + * per-session information. + * + * === Example + * dh = OpenSSL::PKey::DH.new(2048) # has public and private key set + * public_key = dh.public_key # contains only prime and generator + * parameters = public_key.to_der # it's safe to publish this + */ +static VALUE +ossl_dh_to_public_key(VALUE self) +{ + EVP_PKEY *pkey; + DH *dh; + VALUE obj; + + GetPKeyDH(self, pkey); + dh = DHparams_dup(pkey->pkey.dh); /* err check perfomed by dh_instance */ + obj = dh_instance(CLASS_OF(self), dh); + if (obj == Qfalse) { + DH_free(dh); + ossl_raise(eDHError, NULL); + } + + return obj; +} + +/* + * call-seq: + * dh.params_ok? -> true | false + * + * Validates the Diffie-Hellman parameters associated with this instance. + * It checks whether a safe prime and a suitable generator are used. If this + * is not the case, +false+ is returned. + */ +static VALUE +ossl_dh_check_params(VALUE self) +{ + DH *dh; + EVP_PKEY *pkey; + int codes; + + GetPKeyDH(self, pkey); + dh = pkey->pkey.dh; + + if (!DH_check(dh, &codes)) { + return Qfalse; + } + + return codes == 0 ? Qtrue : Qfalse; +} + +/* + * call-seq: + * dh.generate_key! -> self + * + * Generates a private and public key unless a private key already exists. + * If this DH instance was generated from public DH parameters (e.g. by + * encoding the result of DH#public_key), then this method needs to be + * called first in order to generate the per-session keys before performing + * the actual key exchange. + * + * === Example + * dh = OpenSSL::PKey::DH.new(2048) + * public_key = dh.public_key #contains no private/public key yet + * public_key.generate_key! + * puts public_key.private? # => true + */ +static VALUE +ossl_dh_generate_key(VALUE self) +{ + DH *dh; + EVP_PKEY *pkey; + + GetPKeyDH(self, pkey); + dh = pkey->pkey.dh; + + if (!DH_generate_key(dh)) + ossl_raise(eDHError, "Failed to generate key"); + return self; +} + +/* + * call-seq: + * dh.compute_key(pub_bn) -> aString + * + * Returns a String containing a shared secret computed from the other party's public value. + * See DH_compute_key() for further information. + * + * === Parameters + * * +pub_bn+ is a OpenSSL::BN, *not* the DH instance returned by + * DH#public_key as that contains the DH parameters only. + */ +static VALUE +ossl_dh_compute_key(VALUE self, VALUE pub) +{ + DH *dh; + EVP_PKEY *pkey; + BIGNUM *pub_key; + VALUE str; + int len; + + GetPKeyDH(self, pkey); + dh = pkey->pkey.dh; + pub_key = GetBNPtr(pub); + len = DH_size(dh); + str = rb_str_new(0, len); + if ((len = DH_compute_key((unsigned char *)RSTRING_PTR(str), pub_key, dh)) < 0) { + ossl_raise(eDHError, NULL); + } + rb_str_set_len(str, len); + + return str; +} + +OSSL_PKEY_BN(dh, p) +OSSL_PKEY_BN(dh, g) +OSSL_PKEY_BN(dh, pub_key) +OSSL_PKEY_BN(dh, priv_key) + +/* + * -----BEGIN DH PARAMETERS----- + * MEYCQQD0zXHljRg/mJ9PYLACLv58Cd8VxBxxY7oEuCeURMiTqEhMym16rhhKgZG2 + * zk2O9uUIBIxSj+NKMURHGaFKyIvLAgEC + * -----END DH PARAMETERS----- + */ +static unsigned char DEFAULT_DH_512_PRIM[] = { + 0xf4, 0xcd, 0x71, 0xe5, 0x8d, 0x18, 0x3f, 0x98, + 0x9f, 0x4f, 0x60, 0xb0, 0x02, 0x2e, 0xfe, 0x7c, + 0x09, 0xdf, 0x15, 0xc4, 0x1c, 0x71, 0x63, 0xba, + 0x04, 0xb8, 0x27, 0x94, 0x44, 0xc8, 0x93, 0xa8, + 0x48, 0x4c, 0xca, 0x6d, 0x7a, 0xae, 0x18, 0x4a, + 0x81, 0x91, 0xb6, 0xce, 0x4d, 0x8e, 0xf6, 0xe5, + 0x08, 0x04, 0x8c, 0x52, 0x8f, 0xe3, 0x4a, 0x31, + 0x44, 0x47, 0x19, 0xa1, 0x4a, 0xc8, 0x8b, 0xcb, +}; +static unsigned char DEFAULT_DH_512_GEN[] = { 0x02 }; +DH *OSSL_DEFAULT_DH_512 = NULL; + +/* + * -----BEGIN DH PARAMETERS----- + * MIGHAoGBAJ0lOVy0VIr/JebWn0zDwY2h+rqITFOpdNr6ugsgvkDXuucdcChhYExJ + * AV/ZD2AWPbrTqV76mGRgJg4EddgT1zG0jq3rnFdMj2XzkBYx3BVvfR0Arnby0RHR + * T4h7KZ/2zmjvV+eF8kBUHBJAojUlzxKj4QeO2x20FP9X5xmNUXeDAgEC + * -----END DH PARAMETERS----- + */ +static unsigned char DEFAULT_DH_1024_PRIM[] = { + 0x9d, 0x25, 0x39, 0x5c, 0xb4, 0x54, 0x8a, 0xff, + 0x25, 0xe6, 0xd6, 0x9f, 0x4c, 0xc3, 0xc1, 0x8d, + 0xa1, 0xfa, 0xba, 0x88, 0x4c, 0x53, 0xa9, 0x74, + 0xda, 0xfa, 0xba, 0x0b, 0x20, 0xbe, 0x40, 0xd7, + 0xba, 0xe7, 0x1d, 0x70, 0x28, 0x61, 0x60, 0x4c, + 0x49, 0x01, 0x5f, 0xd9, 0x0f, 0x60, 0x16, 0x3d, + 0xba, 0xd3, 0xa9, 0x5e, 0xfa, 0x98, 0x64, 0x60, + 0x26, 0x0e, 0x04, 0x75, 0xd8, 0x13, 0xd7, 0x31, + 0xb4, 0x8e, 0xad, 0xeb, 0x9c, 0x57, 0x4c, 0x8f, + 0x65, 0xf3, 0x90, 0x16, 0x31, 0xdc, 0x15, 0x6f, + 0x7d, 0x1d, 0x00, 0xae, 0x76, 0xf2, 0xd1, 0x11, + 0xd1, 0x4f, 0x88, 0x7b, 0x29, 0x9f, 0xf6, 0xce, + 0x68, 0xef, 0x57, 0xe7, 0x85, 0xf2, 0x40, 0x54, + 0x1c, 0x12, 0x40, 0xa2, 0x35, 0x25, 0xcf, 0x12, + 0xa3, 0xe1, 0x07, 0x8e, 0xdb, 0x1d, 0xb4, 0x14, + 0xff, 0x57, 0xe7, 0x19, 0x8d, 0x51, 0x77, 0x83 +}; +static unsigned char DEFAULT_DH_1024_GEN[] = { 0x02 }; +DH *OSSL_DEFAULT_DH_1024 = NULL; + +static DH* +ossl_create_dh(unsigned char *p, size_t plen, unsigned char *g, size_t glen) +{ + DH *dh; + + if ((dh = DH_new()) == NULL) ossl_raise(eDHError, NULL); + dh->p = BN_bin2bn(p, rb_long2int(plen), NULL); + dh->g = BN_bin2bn(g, rb_long2int(glen), NULL); + if (dh->p == NULL || dh->g == NULL){ + DH_free(dh); + ossl_raise(eDHError, NULL); + } + + return dh; +} + +/* + * INIT + */ +void +Init_ossl_dh(void) +{ +#if 0 + mOSSL = rb_define_module("OpenSSL"); /* let rdoc know about mOSSL and mPKey */ + mPKey = rb_define_module_under(mOSSL, "PKey"); +#endif + + /* Document-class: OpenSSL::PKey::DHError + * + * Generic exception that is raised if an operation on a DH PKey + * fails unexpectedly or in case an instantiation of an instance of DH + * fails due to non-conformant input data. + */ + eDHError = rb_define_class_under(mPKey, "DHError", ePKeyError); + /* Document-class: OpenSSL::PKey::DH + * + * An implementation of the Diffie-Hellman key exchange protocol based on + * discrete logarithms in finite fields, the same basis that DSA is built + * on. + * + * === Accessor methods for the Diffie-Hellman parameters + * * DH#p + * The prime (an OpenSSL::BN) of the Diffie-Hellman parameters. + * * DH#g + * The generator (an OpenSSL::BN) g of the Diffie-Hellman parameters. + * * DH#pub_key + * The per-session public key (an OpenSSL::BN) matching the private key. + * This needs to be passed to DH#compute_key. + * * DH#priv_key + * The per-session private key, an OpenSSL::BN. + * + * === Example of a key exchange + * dh1 = OpenSSL::PKey::DH.new(2048) + * der = dh1.public_key.to_der #you may send this publicly to the participating party + * dh2 = OpenSSL::PKey::DH.new(der) + * dh2.generate_key! #generate the per-session key pair + * symm_key1 = dh1.compute_key(dh2.pub_key) + * symm_key2 = dh2.compute_key(dh1.pub_key) + * + * puts symm_key1 == symm_key2 # => true + */ + cDH = rb_define_class_under(mPKey, "DH", cPKey); + rb_define_singleton_method(cDH, "generate", ossl_dh_s_generate, -1); + rb_define_method(cDH, "initialize", ossl_dh_initialize, -1); + rb_define_method(cDH, "public?", ossl_dh_is_public, 0); + rb_define_method(cDH, "private?", ossl_dh_is_private, 0); + rb_define_method(cDH, "to_text", ossl_dh_to_text, 0); + rb_define_method(cDH, "export", ossl_dh_export, 0); + rb_define_alias(cDH, "to_pem", "export"); + rb_define_alias(cDH, "to_s", "export"); + rb_define_method(cDH, "to_der", ossl_dh_to_der, 0); + rb_define_method(cDH, "public_key", ossl_dh_to_public_key, 0); + rb_define_method(cDH, "params_ok?", ossl_dh_check_params, 0); + rb_define_method(cDH, "generate_key!", ossl_dh_generate_key, 0); + rb_define_method(cDH, "compute_key", ossl_dh_compute_key, 1); + + DEF_OSSL_PKEY_BN(cDH, dh, p); + DEF_OSSL_PKEY_BN(cDH, dh, g); + DEF_OSSL_PKEY_BN(cDH, dh, pub_key); + DEF_OSSL_PKEY_BN(cDH, dh, priv_key); + rb_define_method(cDH, "params", ossl_dh_get_params, 0); + + OSSL_DEFAULT_DH_512 = ossl_create_dh( + DEFAULT_DH_512_PRIM, sizeof(DEFAULT_DH_512_PRIM), + DEFAULT_DH_512_GEN, sizeof(DEFAULT_DH_512_GEN)); + OSSL_DEFAULT_DH_1024 = ossl_create_dh( + DEFAULT_DH_1024_PRIM, sizeof(DEFAULT_DH_1024_PRIM), + DEFAULT_DH_1024_GEN, sizeof(DEFAULT_DH_1024_GEN)); +} + +#else /* defined NO_DH */ +void +Init_ossl_dh(void) +{ +} +#endif /* NO_DH */ diff --git a/ext/openssl/ossl_pkey_dsa.c b/ext/openssl/ossl_pkey_dsa.c new file mode 100644 index 00000000..979ae154 --- /dev/null +++ b/ext/openssl/ossl_pkey_dsa.c @@ -0,0 +1,623 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2001-2002 Michal Rokos <m.rokos@sh.cvut.cz> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#if !defined(OPENSSL_NO_DSA) + +#include "ossl.h" + +#define GetPKeyDSA(obj, pkey) do { \ + GetPKey((obj), (pkey)); \ + if (EVP_PKEY_type((pkey)->type) != EVP_PKEY_DSA) { /* PARANOIA? */ \ + ossl_raise(rb_eRuntimeError, "THIS IS NOT A DSA!"); \ + } \ +} while (0) + +#define DSA_HAS_PRIVATE(dsa) ((dsa)->priv_key) +#define DSA_PRIVATE(obj,dsa) (DSA_HAS_PRIVATE(dsa)||OSSL_PKEY_IS_PRIVATE(obj)) + +/* + * Classes + */ +VALUE cDSA; +VALUE eDSAError; + +/* + * Public + */ +static VALUE +dsa_instance(VALUE klass, DSA *dsa) +{ + EVP_PKEY *pkey; + VALUE obj; + + if (!dsa) { + return Qfalse; + } + if (!(pkey = EVP_PKEY_new())) { + return Qfalse; + } + if (!EVP_PKEY_assign_DSA(pkey, dsa)) { + EVP_PKEY_free(pkey); + return Qfalse; + } + WrapPKey(klass, obj, pkey); + + return obj; +} + +VALUE +ossl_dsa_new(EVP_PKEY *pkey) +{ + VALUE obj; + + if (!pkey) { + obj = dsa_instance(cDSA, DSA_new()); + } else { + if (EVP_PKEY_type(pkey->type) != EVP_PKEY_DSA) { + ossl_raise(rb_eTypeError, "Not a DSA key!"); + } + WrapPKey(cDSA, obj, pkey); + } + if (obj == Qfalse) { + ossl_raise(eDSAError, NULL); + } + + return obj; +} + +/* + * Private + */ +#if defined(HAVE_DSA_GENERATE_PARAMETERS_EX) && HAVE_BN_GENCB +struct dsa_blocking_gen_arg { + DSA *dsa; + int size; + unsigned char* seed; + int seed_len; + int *counter; + unsigned long *h; + BN_GENCB *cb; + int result; +}; + +static void * +dsa_blocking_gen(void *arg) +{ + struct dsa_blocking_gen_arg *gen = (struct dsa_blocking_gen_arg *)arg; + gen->result = DSA_generate_parameters_ex(gen->dsa, gen->size, gen->seed, gen->seed_len, gen->counter, gen->h, gen->cb); + return 0; +} +#endif + +static DSA * +dsa_generate(int size) +{ +#if defined(HAVE_DSA_GENERATE_PARAMETERS_EX) && HAVE_BN_GENCB + BN_GENCB cb; + struct ossl_generate_cb_arg cb_arg; + struct dsa_blocking_gen_arg gen_arg; + DSA *dsa = DSA_new(); + unsigned char seed[20]; + int seed_len = 20, counter; + unsigned long h; + + if (!dsa) return 0; + if (!RAND_bytes(seed, seed_len)) { + DSA_free(dsa); + return 0; + } + + memset(&cb_arg, 0, sizeof(struct ossl_generate_cb_arg)); + if (rb_block_given_p()) + cb_arg.yield = 1; + BN_GENCB_set(&cb, ossl_generate_cb_2, &cb_arg); + gen_arg.dsa = dsa; + gen_arg.size = size; + gen_arg.seed = seed; + gen_arg.seed_len = seed_len; + gen_arg.counter = &counter; + gen_arg.h = &h; + gen_arg.cb = &cb; + if (cb_arg.yield == 1) { + /* we cannot release GVL when callback proc is supplied */ + dsa_blocking_gen(&gen_arg); + } else { + /* there's a chance to unblock */ + rb_thread_call_without_gvl(dsa_blocking_gen, &gen_arg, ossl_generate_cb_stop, &cb_arg); + } + if (!gen_arg.result) { + DSA_free(dsa); + if (cb_arg.state) rb_jump_tag(cb_arg.state); + return 0; + } +#else + DSA *dsa; + unsigned char seed[20]; + int seed_len = 20, counter; + unsigned long h; + + if (!RAND_bytes(seed, seed_len)) { + return 0; + } + dsa = DSA_generate_parameters(size, seed, seed_len, &counter, &h, + rb_block_given_p() ? ossl_generate_cb : NULL, NULL); + if(!dsa) return 0; +#endif + + if (!DSA_generate_key(dsa)) { + DSA_free(dsa); + return 0; + } + + return dsa; +} + +/* + * call-seq: + * DSA.generate(size) -> dsa + * + * Creates a new DSA instance by generating a private/public key pair + * from scratch. + * + * === Parameters + * * +size+ is an integer representing the desired key size. + * + */ +static VALUE +ossl_dsa_s_generate(VALUE klass, VALUE size) +{ + DSA *dsa = dsa_generate(NUM2INT(size)); /* err handled by dsa_instance */ + VALUE obj = dsa_instance(klass, dsa); + + if (obj == Qfalse) { + DSA_free(dsa); + ossl_raise(eDSAError, NULL); + } + + return obj; +} + +/* + * call-seq: + * DSA.new([size | string [, pass]) -> dsa + * + * Creates a new DSA instance by reading an existing key from +string+. + * + * === Parameters + * * +size+ is an integer representing the desired key size. + * * +string+ contains a DER or PEM encoded key. + * * +pass+ is a string that contains an optional password. + * + * === Examples + * DSA.new -> dsa + * DSA.new(1024) -> dsa + * DSA.new(File.read('dsa.pem')) -> dsa + * DSA.new(File.read('dsa.pem'), 'mypassword') -> dsa + * + */ +static VALUE +ossl_dsa_initialize(int argc, VALUE *argv, VALUE self) +{ + EVP_PKEY *pkey; + DSA *dsa; + BIO *in; + char *passwd = NULL; + VALUE arg, pass; + + GetPKey(self, pkey); + if(rb_scan_args(argc, argv, "02", &arg, &pass) == 0) { + dsa = DSA_new(); + } + else if (FIXNUM_P(arg)) { + if (!(dsa = dsa_generate(FIX2INT(arg)))) { + ossl_raise(eDSAError, NULL); + } + } + else { + if (!NIL_P(pass)) passwd = StringValuePtr(pass); + arg = ossl_to_der_if_possible(arg); + in = ossl_obj2bio(arg); + dsa = PEM_read_bio_DSAPrivateKey(in, NULL, ossl_pem_passwd_cb, passwd); + if (!dsa) { + OSSL_BIO_reset(in); + dsa = PEM_read_bio_DSA_PUBKEY(in, NULL, NULL, NULL); + } + if (!dsa) { + OSSL_BIO_reset(in); + dsa = d2i_DSAPrivateKey_bio(in, NULL); + } + if (!dsa) { + OSSL_BIO_reset(in); + dsa = d2i_DSA_PUBKEY_bio(in, NULL); + } + if (!dsa) { + OSSL_BIO_reset(in); + dsa = PEM_read_bio_DSAPublicKey(in, NULL, NULL, NULL); + } + BIO_free(in); + if (!dsa) { + ERR_clear_error(); + ossl_raise(eDSAError, "Neither PUB key nor PRIV key"); + } + } + if (!EVP_PKEY_assign_DSA(pkey, dsa)) { + DSA_free(dsa); + ossl_raise(eDSAError, NULL); + } + + return self; +} + +/* + * call-seq: + * dsa.public? -> true | false + * + * Indicates whether this DSA instance has a public key associated with it or + * not. The public key may be retrieved with DSA#public_key. + */ +static VALUE +ossl_dsa_is_public(VALUE self) +{ + EVP_PKEY *pkey; + + GetPKeyDSA(self, pkey); + + return (pkey->pkey.dsa->pub_key) ? Qtrue : Qfalse; +} + +/* + * call-seq: + * dsa.private? -> true | false + * + * Indicates whether this DSA instance has a private key associated with it or + * not. The private key may be retrieved with DSA#private_key. + */ +static VALUE +ossl_dsa_is_private(VALUE self) +{ + EVP_PKEY *pkey; + + GetPKeyDSA(self, pkey); + + return (DSA_PRIVATE(self, pkey->pkey.dsa)) ? Qtrue : Qfalse; +} + +/* + * call-seq: + * dsa.export([cipher, password]) -> aString + * dsa.to_pem([cipher, password]) -> aString + * dsa.to_s([cipher, password]) -> aString + * + * Encodes this DSA to its PEM encoding. + * + * === Parameters + * * +cipher+ is an OpenSSL::Cipher. + * * +password+ is a string containing your password. + * + * === Examples + * DSA.to_pem -> aString + * DSA.to_pem(cipher, 'mypassword') -> aString + * + */ +static VALUE +ossl_dsa_export(int argc, VALUE *argv, VALUE self) +{ + EVP_PKEY *pkey; + BIO *out; + const EVP_CIPHER *ciph = NULL; + char *passwd = NULL; + VALUE cipher, pass, str; + + GetPKeyDSA(self, pkey); + rb_scan_args(argc, argv, "02", &cipher, &pass); + if (!NIL_P(cipher)) { + ciph = GetCipherPtr(cipher); + if (!NIL_P(pass)) { + StringValue(pass); + if (RSTRING_LENINT(pass) < OSSL_MIN_PWD_LEN) + ossl_raise(eOSSLError, "OpenSSL requires passwords to be at least four characters long"); + passwd = RSTRING_PTR(pass); + } + } + if (!(out = BIO_new(BIO_s_mem()))) { + ossl_raise(eDSAError, NULL); + } + if (DSA_HAS_PRIVATE(pkey->pkey.dsa)) { + if (!PEM_write_bio_DSAPrivateKey(out, pkey->pkey.dsa, ciph, + NULL, 0, ossl_pem_passwd_cb, passwd)){ + BIO_free(out); + ossl_raise(eDSAError, NULL); + } + } else { + if (!PEM_write_bio_DSA_PUBKEY(out, pkey->pkey.dsa)) { + BIO_free(out); + ossl_raise(eDSAError, NULL); + } + } + str = ossl_membio2str(out); + + return str; +} + +/* + * call-seq: + * dsa.to_der -> aString + * + * Encodes this DSA to its DER encoding. + * + */ +static VALUE +ossl_dsa_to_der(VALUE self) +{ + EVP_PKEY *pkey; + int (*i2d_func)_((DSA*, unsigned char**)); + unsigned char *p; + long len; + VALUE str; + + GetPKeyDSA(self, pkey); + if(DSA_HAS_PRIVATE(pkey->pkey.dsa)) + i2d_func = (int(*)_((DSA*,unsigned char**)))i2d_DSAPrivateKey; + else + i2d_func = i2d_DSA_PUBKEY; + if((len = i2d_func(pkey->pkey.dsa, NULL)) <= 0) + ossl_raise(eDSAError, NULL); + str = rb_str_new(0, len); + p = (unsigned char *)RSTRING_PTR(str); + if(i2d_func(pkey->pkey.dsa, &p) < 0) + ossl_raise(eDSAError, NULL); + ossl_str_adjust(str, p); + + return str; +} + +/* + * call-seq: + * dsa.params -> hash + * + * Stores all parameters of key to the hash + * INSECURE: PRIVATE INFORMATIONS CAN LEAK OUT!!! + * Don't use :-)) (I's up to you) + */ +static VALUE +ossl_dsa_get_params(VALUE self) +{ + EVP_PKEY *pkey; + VALUE hash; + + GetPKeyDSA(self, pkey); + + hash = rb_hash_new(); + + rb_hash_aset(hash, rb_str_new2("p"), ossl_bn_new(pkey->pkey.dsa->p)); + rb_hash_aset(hash, rb_str_new2("q"), ossl_bn_new(pkey->pkey.dsa->q)); + rb_hash_aset(hash, rb_str_new2("g"), ossl_bn_new(pkey->pkey.dsa->g)); + rb_hash_aset(hash, rb_str_new2("pub_key"), ossl_bn_new(pkey->pkey.dsa->pub_key)); + rb_hash_aset(hash, rb_str_new2("priv_key"), ossl_bn_new(pkey->pkey.dsa->priv_key)); + + return hash; +} + +/* + * call-seq: + * dsa.to_text -> aString + * + * Prints all parameters of key to buffer + * INSECURE: PRIVATE INFORMATIONS CAN LEAK OUT!!! + * Don't use :-)) (I's up to you) + */ +static VALUE +ossl_dsa_to_text(VALUE self) +{ + EVP_PKEY *pkey; + BIO *out; + VALUE str; + + GetPKeyDSA(self, pkey); + if (!(out = BIO_new(BIO_s_mem()))) { + ossl_raise(eDSAError, NULL); + } + if (!DSA_print(out, pkey->pkey.dsa, 0)) { /* offset = 0 */ + BIO_free(out); + ossl_raise(eDSAError, NULL); + } + str = ossl_membio2str(out); + + return str; +} + +/* + * call-seq: + * dsa.public_key -> aDSA + * + * Returns a new DSA instance that carries just the public key information. + * If the current instance has also private key information, this will no + * longer be present in the new instance. This feature is helpful for + * publishing the public key information without leaking any of the private + * information. + * + * === Example + * dsa = OpenSSL::PKey::DSA.new(2048) # has public and private information + * pub_key = dsa.public_key # has only the public part available + * pub_key_der = pub_key.to_der # it's safe to publish this + * + * + */ +static VALUE +ossl_dsa_to_public_key(VALUE self) +{ + EVP_PKEY *pkey; + DSA *dsa; + VALUE obj; + + GetPKeyDSA(self, pkey); + /* err check performed by dsa_instance */ + dsa = DSAPublicKey_dup(pkey->pkey.dsa); + obj = dsa_instance(CLASS_OF(self), dsa); + if (obj == Qfalse) { + DSA_free(dsa); + ossl_raise(eDSAError, NULL); + } + return obj; +} + +#define ossl_dsa_buf_size(pkey) (DSA_size((pkey)->pkey.dsa)+16) + +/* + * call-seq: + * dsa.syssign(string) -> aString + * + * Computes and returns the DSA signature of +string+, where +string+ is + * expected to be an already-computed message digest of the original input + * data. The signature is issued using the private key of this DSA instance. + * + * === Parameters + * * +string+ is a message digest of the original input data to be signed + * + * === Example + * dsa = OpenSSL::PKey::DSA.new(2048) + * doc = "Sign me" + * digest = OpenSSL::Digest::SHA1.digest(doc) + * sig = dsa.syssign(digest) + * + * + */ +static VALUE +ossl_dsa_sign(VALUE self, VALUE data) +{ + EVP_PKEY *pkey; + unsigned int buf_len; + VALUE str; + + GetPKeyDSA(self, pkey); + StringValue(data); + if (!DSA_PRIVATE(self, pkey->pkey.dsa)) { + ossl_raise(eDSAError, "Private DSA key needed!"); + } + str = rb_str_new(0, ossl_dsa_buf_size(pkey)); + if (!DSA_sign(0, (unsigned char *)RSTRING_PTR(data), RSTRING_LENINT(data), + (unsigned char *)RSTRING_PTR(str), + &buf_len, pkey->pkey.dsa)) { /* type is ignored (0) */ + ossl_raise(eDSAError, NULL); + } + rb_str_set_len(str, buf_len); + + return str; +} + +/* + * call-seq: + * dsa.sysverify(digest, sig) -> true | false + * + * Verifies whether the signature is valid given the message digest input. It + * does so by validating +sig+ using the public key of this DSA instance. + * + * === Parameters + * * +digest+ is a message digest of the original input data to be signed + * * +sig+ is a DSA signature value + * + * === Example + * dsa = OpenSSL::PKey::DSA.new(2048) + * doc = "Sign me" + * digest = OpenSSL::Digest::SHA1.digest(doc) + * sig = dsa.syssign(digest) + * puts dsa.sysverify(digest, sig) # => true + * + */ +static VALUE +ossl_dsa_verify(VALUE self, VALUE digest, VALUE sig) +{ + EVP_PKEY *pkey; + int ret; + + GetPKeyDSA(self, pkey); + StringValue(digest); + StringValue(sig); + /* type is ignored (0) */ + ret = DSA_verify(0, (unsigned char *)RSTRING_PTR(digest), RSTRING_LENINT(digest), + (unsigned char *)RSTRING_PTR(sig), RSTRING_LENINT(sig), pkey->pkey.dsa); + if (ret < 0) { + ossl_raise(eDSAError, NULL); + } + else if (ret == 1) { + return Qtrue; + } + + return Qfalse; +} + +OSSL_PKEY_BN(dsa, p) +OSSL_PKEY_BN(dsa, q) +OSSL_PKEY_BN(dsa, g) +OSSL_PKEY_BN(dsa, pub_key) +OSSL_PKEY_BN(dsa, priv_key) + +/* + * INIT + */ +void +Init_ossl_dsa(void) +{ +#if 0 + mOSSL = rb_define_module("OpenSSL"); /* let rdoc know about mOSSL and mPKey */ + mPKey = rb_define_module_under(mOSSL, "PKey"); +#endif + + /* Document-class: OpenSSL::PKey::DSAError + * + * Generic exception that is raised if an operation on a DSA PKey + * fails unexpectedly or in case an instantiation of an instance of DSA + * fails due to non-conformant input data. + */ + eDSAError = rb_define_class_under(mPKey, "DSAError", ePKeyError); + + /* Document-class: OpenSSL::PKey::DSA + * + * DSA, the Digital Signature Algorithm, is specified in NIST's + * FIPS 186-3. It is an asymmetric public key algorithm that may be used + * similar to e.g. RSA. + * Please note that for OpenSSL versions prior to 1.0.0 the digest + * algorithms OpenSSL::Digest::DSS (equivalent to SHA) or + * OpenSSL::Digest::DSS1 (equivalent to SHA-1) must be used for issuing + * signatures with a DSA key using OpenSSL::PKey#sign. + * Starting with OpenSSL 1.0.0, digest algorithms are no longer restricted, + * any Digest may be used for signing. + */ + cDSA = rb_define_class_under(mPKey, "DSA", cPKey); + + rb_define_singleton_method(cDSA, "generate", ossl_dsa_s_generate, 1); + rb_define_method(cDSA, "initialize", ossl_dsa_initialize, -1); + + rb_define_method(cDSA, "public?", ossl_dsa_is_public, 0); + rb_define_method(cDSA, "private?", ossl_dsa_is_private, 0); + rb_define_method(cDSA, "to_text", ossl_dsa_to_text, 0); + rb_define_method(cDSA, "export", ossl_dsa_export, -1); + rb_define_alias(cDSA, "to_pem", "export"); + rb_define_alias(cDSA, "to_s", "export"); + rb_define_method(cDSA, "to_der", ossl_dsa_to_der, 0); + rb_define_method(cDSA, "public_key", ossl_dsa_to_public_key, 0); + rb_define_method(cDSA, "syssign", ossl_dsa_sign, 1); + rb_define_method(cDSA, "sysverify", ossl_dsa_verify, 2); + + DEF_OSSL_PKEY_BN(cDSA, dsa, p); + DEF_OSSL_PKEY_BN(cDSA, dsa, q); + DEF_OSSL_PKEY_BN(cDSA, dsa, g); + DEF_OSSL_PKEY_BN(cDSA, dsa, pub_key); + DEF_OSSL_PKEY_BN(cDSA, dsa, priv_key); + + rb_define_method(cDSA, "params", ossl_dsa_get_params, 0); +} + +#else /* defined NO_DSA */ +void +Init_ossl_dsa(void) +{ +} +#endif /* NO_DSA */ diff --git a/ext/openssl/ossl_pkey_ec.c b/ext/openssl/ossl_pkey_ec.c new file mode 100644 index 00000000..cec00597 --- /dev/null +++ b/ext/openssl/ossl_pkey_ec.c @@ -0,0 +1,1683 @@ +/* + * Copyright (C) 2006-2007 Technorama Ltd. <oss-ruby@technorama.net> + */ + +#include "ossl.h" + +#if !defined(OPENSSL_NO_EC) && (OPENSSL_VERSION_NUMBER >= 0x0090802fL) + +typedef struct { + EC_GROUP *group; + int dont_free; +} ossl_ec_group; + +typedef struct { + EC_POINT *point; + int dont_free; +} ossl_ec_point; + + +#define EXPORT_PEM 0 +#define EXPORT_DER 1 + + +#define GetPKeyEC(obj, pkey) do { \ + GetPKey((obj), (pkey)); \ + if (EVP_PKEY_type((pkey)->type) != EVP_PKEY_EC) { \ + ossl_raise(rb_eRuntimeError, "THIS IS NOT A EC PKEY!"); \ + } \ +} while (0) + +#define SafeGet_ec_group(obj, group) do { \ + OSSL_Check_Kind((obj), cEC_GROUP); \ + Data_Get_Struct((obj), ossl_ec_group, (group)); \ +} while(0) + +#define Get_EC_KEY(obj, key) do { \ + EVP_PKEY *pkey; \ + GetPKeyEC((obj), pkey); \ + (key) = pkey->pkey.ec; \ +} while(0) + +#define Require_EC_KEY(obj, key) do { \ + Get_EC_KEY((obj), (key)); \ + if ((key) == NULL) \ + ossl_raise(eECError, "EC_KEY is not initialized"); \ +} while(0) + +#define SafeRequire_EC_KEY(obj, key) do { \ + OSSL_Check_Kind((obj), cEC); \ + Require_EC_KEY((obj), (key)); \ +} while (0) + +#define Get_EC_GROUP(obj, g) do { \ + ossl_ec_group *ec_group; \ + Data_Get_Struct((obj), ossl_ec_group, ec_group); \ + if (ec_group == NULL) \ + ossl_raise(eEC_GROUP, "missing ossl_ec_group structure"); \ + (g) = ec_group->group; \ +} while(0) + +#define Require_EC_GROUP(obj, group) do { \ + Get_EC_GROUP((obj), (group)); \ + if ((group) == NULL) \ + ossl_raise(eEC_GROUP, "EC_GROUP is not initialized"); \ +} while(0) + +#define SafeRequire_EC_GROUP(obj, group) do { \ + OSSL_Check_Kind((obj), cEC_GROUP); \ + Require_EC_GROUP((obj), (group)); \ +} while(0) + +#define Get_EC_POINT(obj, p) do { \ + ossl_ec_point *ec_point; \ + Data_Get_Struct((obj), ossl_ec_point, ec_point); \ + if (ec_point == NULL) \ + ossl_raise(eEC_POINT, "missing ossl_ec_point structure"); \ + (p) = ec_point->point; \ +} while(0) + +#define Require_EC_POINT(obj, point) do { \ + Get_EC_POINT((obj), (point)); \ + if ((point) == NULL) \ + ossl_raise(eEC_POINT, "EC_POINT is not initialized"); \ +} while(0) + +#define SafeRequire_EC_POINT(obj, point) do { \ + OSSL_Check_Kind((obj), cEC_POINT); \ + Require_EC_POINT((obj), (point)); \ +} while(0) + +VALUE cEC; +VALUE eECError; +VALUE cEC_GROUP; +VALUE eEC_GROUP; +VALUE cEC_POINT; +VALUE eEC_POINT; + +static ID s_GFp; +static ID s_GFp_simple; +static ID s_GFp_mont; +static ID s_GFp_nist; +static ID s_GF2m; +static ID s_GF2m_simple; + +static ID ID_uncompressed; +static ID ID_compressed; +static ID ID_hybrid; + +static VALUE ec_instance(VALUE klass, EC_KEY *ec) +{ + EVP_PKEY *pkey; + VALUE obj; + + if (!ec) { + return Qfalse; + } + if (!(pkey = EVP_PKEY_new())) { + return Qfalse; + } + if (!EVP_PKEY_assign_EC_KEY(pkey, ec)) { + EVP_PKEY_free(pkey); + return Qfalse; + } + WrapPKey(klass, obj, pkey); + + return obj; +} + +VALUE ossl_ec_new(EVP_PKEY *pkey) +{ + VALUE obj; + + if (!pkey) { + obj = ec_instance(cEC, EC_KEY_new()); + } else { + if (EVP_PKEY_type(pkey->type) != EVP_PKEY_EC) { + ossl_raise(rb_eTypeError, "Not a EC key!"); + } + WrapPKey(cEC, obj, pkey); + } + if (obj == Qfalse) { + ossl_raise(eECError, NULL); + } + + return obj; +} + + +/* call-seq: + * OpenSSL::PKey::EC.new() + * OpenSSL::PKey::EC.new(ec_key) + * OpenSSL::PKey::EC.new(ec_group) + * OpenSSL::PKey::EC.new("secp112r1") + * OpenSSL::PKey::EC.new(pem_string) + * OpenSSL::PKey::EC.new(pem_string [, pwd]) + * OpenSSL::PKey::EC.new(der_string) + * + * See the OpenSSL documentation for: + * EC_KEY_* + */ +static VALUE ossl_ec_key_initialize(int argc, VALUE *argv, VALUE self) +{ + EVP_PKEY *pkey; + EC_KEY *ec = NULL; + VALUE arg, pass; + VALUE group = Qnil; + char *passwd = NULL; + + GetPKey(self, pkey); + if (pkey->pkey.ec) + ossl_raise(eECError, "EC_KEY already initialized"); + + rb_scan_args(argc, argv, "02", &arg, &pass); + + if (NIL_P(arg)) { + ec = EC_KEY_new(); + } else { + if (rb_obj_is_kind_of(arg, cEC)) { + EC_KEY *other_ec = NULL; + + SafeRequire_EC_KEY(arg, other_ec); + ec = EC_KEY_dup(other_ec); + } else if (rb_obj_is_kind_of(arg, cEC_GROUP)) { + ec = EC_KEY_new(); + group = arg; + } else { + BIO *in = ossl_obj2bio(arg); + + if (!NIL_P(pass)) { + passwd = StringValuePtr(pass); + } + ec = PEM_read_bio_ECPrivateKey(in, NULL, ossl_pem_passwd_cb, passwd); + if (!ec) { + OSSL_BIO_reset(in); + ec = PEM_read_bio_EC_PUBKEY(in, NULL, ossl_pem_passwd_cb, passwd); + } + if (!ec) { + OSSL_BIO_reset(in); + ec = d2i_ECPrivateKey_bio(in, NULL); + } + if (!ec) { + OSSL_BIO_reset(in); + ec = d2i_EC_PUBKEY_bio(in, NULL); + } + + BIO_free(in); + + if (ec == NULL) { + const char *name = StringValueCStr(arg); + int nid = OBJ_sn2nid(name); + + (void)ERR_get_error(); + if (nid == NID_undef) + ossl_raise(eECError, "unknown curve name (%s)\n", name); + + if ((ec = EC_KEY_new_by_curve_name(nid)) == NULL) + ossl_raise(eECError, "unable to create curve (%s)\n", name); + + EC_KEY_set_asn1_flag(ec, OPENSSL_EC_NAMED_CURVE); + EC_KEY_set_conv_form(ec, POINT_CONVERSION_UNCOMPRESSED); + } + } + } + + if (ec == NULL) + ossl_raise(eECError, NULL); + + if (!EVP_PKEY_assign_EC_KEY(pkey, ec)) { + EC_KEY_free(ec); + ossl_raise(eECError, "EVP_PKEY_assign_EC_KEY"); + } + + rb_iv_set(self, "@group", Qnil); + + if (!NIL_P(group)) + rb_funcall(self, rb_intern("group="), 1, arg); + + return self; +} + +/* + * call-seq: + * key.group => group + * + * Returns a constant <code>OpenSSL::EC::Group</code> that is tied to the key. + * Modifying the returned group can make the key invalid. + */ +static VALUE ossl_ec_key_get_group(VALUE self) +{ + VALUE group_v; + EC_KEY *ec; + ossl_ec_group *ec_group; + EC_GROUP *group; + + Require_EC_KEY(self, ec); + + group_v = rb_iv_get(self, "@group"); + if (!NIL_P(group_v)) + return group_v; + + if ((group = (EC_GROUP *)EC_KEY_get0_group(ec)) != NULL) { + group_v = rb_obj_alloc(cEC_GROUP); + SafeGet_ec_group(group_v, ec_group); + ec_group->group = group; + ec_group->dont_free = 1; + rb_iv_set(group_v, "@key", self); + rb_iv_set(self, "@group", group_v); + return group_v; + } + + return Qnil; +} + +/* + * call-seq: + * key.group = group => group + * + * Returns the same object passed, not the group object associated with the key. + * If you wish to access the group object tied to the key call key.group after setting + * the group. + * + * Setting the group will immediately destroy any previously assigned group object. + * The group is internally copied by OpenSSL. Modifying the original group after + * assignment will not effect the internal key structure. + * (your changes may be lost). BE CAREFUL. + * + * EC_KEY_set_group calls EC_GROUP_free(key->group) then EC_GROUP_dup(), not EC_GROUP_copy. + * This documentation is accurate for OpenSSL 0.9.8b. + */ +static VALUE ossl_ec_key_set_group(VALUE self, VALUE group_v) +{ + VALUE old_group_v; + EC_KEY *ec; + EC_GROUP *group; + + Require_EC_KEY(self, ec); + SafeRequire_EC_GROUP(group_v, group); + + old_group_v = rb_iv_get(self, "@group"); + if (!NIL_P(old_group_v)) { + ossl_ec_group *old_ec_group; + SafeGet_ec_group(old_group_v, old_ec_group); + + old_ec_group->group = NULL; + old_ec_group->dont_free = 0; + rb_iv_set(old_group_v, "@key", Qnil); + } + + rb_iv_set(self, "@group", Qnil); + + if (EC_KEY_set_group(ec, group) != 1) + ossl_raise(eECError, "EC_KEY_set_group"); + + return group_v; +} + +/* + * call-seq: + * key.private_key => OpenSSL::BN + * + * See the OpenSSL documentation for EC_KEY_get0_private_key() + */ +static VALUE ossl_ec_key_get_private_key(VALUE self) +{ + EC_KEY *ec; + const BIGNUM *bn; + + Require_EC_KEY(self, ec); + + if ((bn = EC_KEY_get0_private_key(ec)) == NULL) + return Qnil; + + return ossl_bn_new(bn); +} + +/* + * call-seq: + * key.private_key = openssl_bn + * + * See the OpenSSL documentation for EC_KEY_set_private_key() + */ +static VALUE ossl_ec_key_set_private_key(VALUE self, VALUE private_key) +{ + EC_KEY *ec; + BIGNUM *bn = NULL; + + Require_EC_KEY(self, ec); + if (!NIL_P(private_key)) + bn = GetBNPtr(private_key); + + switch (EC_KEY_set_private_key(ec, bn)) { + case 1: + break; + case 0: + if (bn == NULL) + break; + default: + ossl_raise(eECError, "EC_KEY_set_private_key"); + } + + return private_key; +} + + +static VALUE ossl_ec_point_dup(const EC_POINT *point, VALUE group_v) +{ + VALUE obj; + const EC_GROUP *group; + ossl_ec_point *new_point; + + obj = rb_obj_alloc(cEC_POINT); + Data_Get_Struct(obj, ossl_ec_point, new_point); + + SafeRequire_EC_GROUP(group_v, group); + + new_point->point = EC_POINT_dup(point, group); + if (new_point->point == NULL) + ossl_raise(eEC_POINT, "EC_POINT_dup"); + rb_iv_set(obj, "@group", group_v); + + return obj; +} + +/* + * call-seq: + * key.public_key => OpenSSL::PKey::EC::Point + * + * See the OpenSSL documentation for EC_KEY_get0_public_key() + */ +static VALUE ossl_ec_key_get_public_key(VALUE self) +{ + EC_KEY *ec; + const EC_POINT *point; + VALUE group; + + Require_EC_KEY(self, ec); + + if ((point = EC_KEY_get0_public_key(ec)) == NULL) + return Qnil; + + group = rb_funcall(self, rb_intern("group"), 0); + if (NIL_P(group)) + ossl_raise(eECError, "EC_KEY_get0_get0_group (has public_key but no group???"); + + return ossl_ec_point_dup(point, group); +} + +/* + * call-seq: + * key.public_key = ec_point + * + * See the OpenSSL documentation for EC_KEY_set_public_key() + */ +static VALUE ossl_ec_key_set_public_key(VALUE self, VALUE public_key) +{ + EC_KEY *ec; + EC_POINT *point = NULL; + + Require_EC_KEY(self, ec); + if (!NIL_P(public_key)) + SafeRequire_EC_POINT(public_key, point); + + switch (EC_KEY_set_public_key(ec, point)) { + case 1: + break; + case 0: + if (point == NULL) + break; + default: + ossl_raise(eECError, "EC_KEY_set_public_key"); + } + + return public_key; +} + +/* + * call-seq: + * key.public_key? => true or false + * + * Both public_key? and private_key? may return false at the same time unlike other PKey classes. + */ +static VALUE ossl_ec_key_is_public_key(VALUE self) +{ + EC_KEY *ec; + + Require_EC_KEY(self, ec); + + return (EC_KEY_get0_public_key(ec) ? Qtrue : Qfalse); +} + +/* + * call-seq: + * key.private_key? => true or false + * + * Both public_key? and private_key? may return false at the same time unlike other PKey classes. + */ +static VALUE ossl_ec_key_is_private_key(VALUE self) +{ + EC_KEY *ec; + + Require_EC_KEY(self, ec); + + return (EC_KEY_get0_private_key(ec) ? Qtrue : Qfalse); +} + +static VALUE ossl_ec_key_to_string(VALUE self, VALUE ciph, VALUE pass, int format) +{ + EC_KEY *ec; + BIO *out; + int i = -1; + int private = 0; + char *password = NULL; + VALUE str; + + Require_EC_KEY(self, ec); + + if (EC_KEY_get0_public_key(ec) == NULL) + ossl_raise(eECError, "can't export - no public key set"); + + if (EC_KEY_check_key(ec) != 1) + ossl_raise(eECError, "can't export - EC_KEY_check_key failed"); + + if (EC_KEY_get0_private_key(ec)) + private = 1; + + if (!(out = BIO_new(BIO_s_mem()))) + ossl_raise(eECError, "BIO_new(BIO_s_mem())"); + + switch(format) { + case EXPORT_PEM: + if (private) { + const EVP_CIPHER *cipher; + if (!NIL_P(ciph)) { + cipher = GetCipherPtr(ciph); + if (!NIL_P(pass)) { + StringValue(pass); + if (RSTRING_LENINT(pass) < OSSL_MIN_PWD_LEN) + ossl_raise(eOSSLError, "OpenSSL requires passwords to be at least four characters long"); + password = RSTRING_PTR(pass); + } + } + else { + cipher = NULL; + } + i = PEM_write_bio_ECPrivateKey(out, ec, cipher, NULL, 0, NULL, password); + } else { + i = PEM_write_bio_EC_PUBKEY(out, ec); + } + + break; + case EXPORT_DER: + if (private) { + i = i2d_ECPrivateKey_bio(out, ec); + } else { + i = i2d_EC_PUBKEY_bio(out, ec); + } + + break; + default: + BIO_free(out); + ossl_raise(rb_eRuntimeError, "unknown format (internal error)"); + } + + if (i != 1) { + BIO_free(out); + ossl_raise(eECError, "outlen=%d", i); + } + + str = ossl_membio2str(out); + + return str; +} + +/* + * call-seq: + * key.export([cipher, pass_phrase]) => String + * key.to_pem([cipher, pass_phrase]) => String + * + * Outputs the EC key in PEM encoding. If +cipher+ and +pass_phrase+ are + * given they will be used to encrypt the key. +cipher+ must be an + * OpenSSL::Cipher::Cipher instance. Note that encryption will only be + * effective for a private key, public keys will always be encoded in plain + * text. + * + */ +static VALUE ossl_ec_key_export(int argc, VALUE *argv, VALUE self) +{ + VALUE cipher, passwd; + rb_scan_args(argc, argv, "02", &cipher, &passwd); + return ossl_ec_key_to_string(self, cipher, passwd, EXPORT_PEM); +} + +/* + * call-seq: + * key.to_der => String + * + * See the OpenSSL documentation for i2d_ECPrivateKey_bio() + */ +static VALUE ossl_ec_key_to_der(VALUE self) +{ + return ossl_ec_key_to_string(self, Qnil, Qnil, EXPORT_DER); +} + +/* + * call-seq: + * key.to_text => String + * + * See the OpenSSL documentation for EC_KEY_print() + */ +static VALUE ossl_ec_key_to_text(VALUE self) +{ + EC_KEY *ec; + BIO *out; + VALUE str; + + Require_EC_KEY(self, ec); + if (!(out = BIO_new(BIO_s_mem()))) { + ossl_raise(eECError, "BIO_new(BIO_s_mem())"); + } + if (!EC_KEY_print(out, ec, 0)) { + BIO_free(out); + ossl_raise(eECError, "EC_KEY_print"); + } + str = ossl_membio2str(out); + + return str; +} + +/* + * call-seq: + * key.generate_key => self + * + * See the OpenSSL documentation for EC_KEY_generate_key() + */ +static VALUE ossl_ec_key_generate_key(VALUE self) +{ + EC_KEY *ec; + + Require_EC_KEY(self, ec); + + if (EC_KEY_generate_key(ec) != 1) + ossl_raise(eECError, "EC_KEY_generate_key"); + + return self; +} + +/* + * call-seq: + * key.check_key => true + * + * Raises an exception if the key is invalid. + * + * See the OpenSSL documentation for EC_KEY_check_key() + */ +static VALUE ossl_ec_key_check_key(VALUE self) +{ + EC_KEY *ec; + + Require_EC_KEY(self, ec); + + if (EC_KEY_check_key(ec) != 1) + ossl_raise(eECError, "EC_KEY_check_key"); + + return Qtrue; +} + +/* + * call-seq: + * key.dh_compute_key(pubkey) => String + * + * See the OpenSSL documentation for ECDH_compute_key() + */ +static VALUE ossl_ec_key_dh_compute_key(VALUE self, VALUE pubkey) +{ + EC_KEY *ec; + EC_POINT *point; + int buf_len; + VALUE str; + + Require_EC_KEY(self, ec); + SafeRequire_EC_POINT(pubkey, point); + +/* BUG: need a way to figure out the maximum string size */ + buf_len = 1024; + str = rb_str_new(0, buf_len); +/* BUG: take KDF as a block */ + buf_len = ECDH_compute_key(RSTRING_PTR(str), buf_len, point, ec, NULL); + if (buf_len < 0) + ossl_raise(eECError, "ECDH_compute_key"); + + rb_str_resize(str, buf_len); + + return str; +} + +/* sign_setup */ + +/* + * call-seq: + * key.dsa_sign_asn1(data) => String + * + * See the OpenSSL documentation for ECDSA_sign() + */ +static VALUE ossl_ec_key_dsa_sign_asn1(VALUE self, VALUE data) +{ + EC_KEY *ec; + unsigned int buf_len; + VALUE str; + + Require_EC_KEY(self, ec); + StringValue(data); + + if (EC_KEY_get0_private_key(ec) == NULL) + ossl_raise(eECError, "Private EC key needed!"); + + str = rb_str_new(0, ECDSA_size(ec) + 16); + if (ECDSA_sign(0, (unsigned char *) RSTRING_PTR(data), RSTRING_LENINT(data), (unsigned char *) RSTRING_PTR(str), &buf_len, ec) != 1) + ossl_raise(eECError, "ECDSA_sign"); + + rb_str_resize(str, buf_len); + + return str; +} + +/* + * call-seq: + * key.dsa_verify_asn1(data, sig) => true or false + * + * See the OpenSSL documentation for ECDSA_verify() + */ +static VALUE ossl_ec_key_dsa_verify_asn1(VALUE self, VALUE data, VALUE sig) +{ + EC_KEY *ec; + + Require_EC_KEY(self, ec); + StringValue(data); + StringValue(sig); + + switch (ECDSA_verify(0, (unsigned char *) RSTRING_PTR(data), RSTRING_LENINT(data), (unsigned char *) RSTRING_PTR(sig), (int)RSTRING_LEN(sig), ec)) { + case 1: return Qtrue; + case 0: return Qfalse; + default: break; + } + + ossl_raise(eECError, "ECDSA_verify"); + + UNREACHABLE; +} + +static void ossl_ec_group_free(ossl_ec_group *ec_group) +{ + if (!ec_group->dont_free && ec_group->group) + EC_GROUP_clear_free(ec_group->group); + ruby_xfree(ec_group); +} + +static VALUE ossl_ec_group_alloc(VALUE klass) +{ + ossl_ec_group *ec_group; + VALUE obj; + + obj = Data_Make_Struct(klass, ossl_ec_group, 0, ossl_ec_group_free, ec_group); + + return obj; +} + +/* call-seq: + * OpenSSL::PKey::EC::Group.new("secp112r1") + * OpenSSL::PKey::EC::Group.new(ec_group) + * OpenSSL::PKey::EC::Group.new(pem_string) + * OpenSSL::PKey::EC::Group.new(der_string) + * OpenSSL::PKey::EC::Group.new(pem_file) + * OpenSSL::PKey::EC::Group.new(der_file) + * OpenSSL::PKey::EC::Group.new(:GFp_simple) + * OpenSSL::PKey::EC::Group.new(:GFp_mult) + * OpenSSL::PKey::EC::Group.new(:GFp_nist) + * OpenSSL::PKey::EC::Group.new(:GF2m_simple) + * OpenSSL::PKey::EC::Group.new(:GFp, bignum_p, bignum_a, bignum_b) + * OpenSSL::PKey::EC::Group.new(:GF2m, bignum_p, bignum_a, bignum_b) + * + * See the OpenSSL documentation for EC_GROUP_* + */ +static VALUE ossl_ec_group_initialize(int argc, VALUE *argv, VALUE self) +{ + VALUE arg1, arg2, arg3, arg4; + ossl_ec_group *ec_group; + EC_GROUP *group = NULL; + + Data_Get_Struct(self, ossl_ec_group, ec_group); + if (ec_group->group != NULL) + ossl_raise(rb_eRuntimeError, "EC_GROUP is already initialized"); + + switch (rb_scan_args(argc, argv, "13", &arg1, &arg2, &arg3, &arg4)) { + case 1: + if (SYMBOL_P(arg1)) { + const EC_METHOD *method = NULL; + ID id = SYM2ID(arg1); + + if (id == s_GFp_simple) { + method = EC_GFp_simple_method(); + } else if (id == s_GFp_mont) { + method = EC_GFp_mont_method(); + } else if (id == s_GFp_nist) { + method = EC_GFp_nist_method(); +#if !defined(OPENSSL_NO_EC2M) + } else if (id == s_GF2m_simple) { + method = EC_GF2m_simple_method(); +#endif + } + + if (method) { + if ((group = EC_GROUP_new(method)) == NULL) + ossl_raise(eEC_GROUP, "EC_GROUP_new"); + } else { + ossl_raise(rb_eArgError, "unknown symbol, must be :GFp_simple, :GFp_mont, :GFp_nist or :GF2m_simple"); + } + } else if (rb_obj_is_kind_of(arg1, cEC_GROUP)) { + const EC_GROUP *arg1_group; + + SafeRequire_EC_GROUP(arg1, arg1_group); + if ((group = EC_GROUP_dup(arg1_group)) == NULL) + ossl_raise(eEC_GROUP, "EC_GROUP_dup"); + } else { + BIO *in = ossl_obj2bio(arg1); + + group = PEM_read_bio_ECPKParameters(in, NULL, NULL, NULL); + if (!group) { + OSSL_BIO_reset(in); + group = d2i_ECPKParameters_bio(in, NULL); + } + + BIO_free(in); + + if (!group) { + const char *name = StringValueCStr(arg1); + int nid = OBJ_sn2nid(name); + + (void)ERR_get_error(); + if (nid == NID_undef) + ossl_raise(eEC_GROUP, "unknown curve name (%s)", name); + + group = EC_GROUP_new_by_curve_name(nid); + if (group == NULL) + ossl_raise(eEC_GROUP, "unable to create curve (%s)", name); + + EC_GROUP_set_asn1_flag(group, OPENSSL_EC_NAMED_CURVE); + EC_GROUP_set_point_conversion_form(group, POINT_CONVERSION_UNCOMPRESSED); + } + } + + break; + case 4: + if (SYMBOL_P(arg1)) { + ID id = SYM2ID(arg1); + EC_GROUP *(*new_curve)(const BIGNUM *, const BIGNUM *, const BIGNUM *, BN_CTX *) = NULL; + const BIGNUM *p = GetBNPtr(arg2); + const BIGNUM *a = GetBNPtr(arg3); + const BIGNUM *b = GetBNPtr(arg4); + + if (id == s_GFp) { + new_curve = EC_GROUP_new_curve_GFp; +#if !defined(OPENSSL_NO_EC2M) + } else if (id == s_GF2m) { + new_curve = EC_GROUP_new_curve_GF2m; +#endif + } else { + ossl_raise(rb_eArgError, "unknown symbol, must be :GFp or :GF2m"); + } + + if ((group = new_curve(p, a, b, ossl_bn_ctx)) == NULL) + ossl_raise(eEC_GROUP, "EC_GROUP_new_by_GF*"); + } else { + ossl_raise(rb_eArgError, "unknown argument, must be :GFp or :GF2m"); + } + + break; + default: + ossl_raise(rb_eArgError, "wrong number of arguments"); + } + + if (group == NULL) + ossl_raise(eEC_GROUP, ""); + + ec_group->group = group; + + return self; +} + +/* call-seq: + * group1.eql?(group2) => true | false + * group1 == group2 => true | false + * + */ +static VALUE ossl_ec_group_eql(VALUE a, VALUE b) +{ + EC_GROUP *group1 = NULL, *group2 = NULL; + + Require_EC_GROUP(a, group1); + SafeRequire_EC_GROUP(b, group2); + + if (EC_GROUP_cmp(group1, group2, ossl_bn_ctx) == 1) + return Qfalse; + + return Qtrue; +} + +/* call-seq: + * group.generator => ec_point + * + * See the OpenSSL documentation for EC_GROUP_get0_generator() + */ +static VALUE ossl_ec_group_get_generator(VALUE self) +{ + VALUE point_obj; + EC_GROUP *group = NULL; + + Require_EC_GROUP(self, group); + + point_obj = ossl_ec_point_dup(EC_GROUP_get0_generator(group), self); + + return point_obj; +} + +/* call-seq: + * group.set_generator(generator, order, cofactor) => self + * + * See the OpenSSL documentation for EC_GROUP_set_generator() + */ +static VALUE ossl_ec_group_set_generator(VALUE self, VALUE generator, VALUE order, VALUE cofactor) +{ + EC_GROUP *group = NULL; + const EC_POINT *point; + const BIGNUM *o, *co; + + Require_EC_GROUP(self, group); + SafeRequire_EC_POINT(generator, point); + o = GetBNPtr(order); + co = GetBNPtr(cofactor); + + if (EC_GROUP_set_generator(group, point, o, co) != 1) + ossl_raise(eEC_GROUP, "EC_GROUP_set_generator"); + + return self; +} + +/* call-seq: + * group.get_order => order_bn + * + * See the OpenSSL documentation for EC_GROUP_get_order() + */ +static VALUE ossl_ec_group_get_order(VALUE self) +{ + VALUE bn_obj; + BIGNUM *bn; + EC_GROUP *group = NULL; + + Require_EC_GROUP(self, group); + + bn_obj = ossl_bn_new(NULL); + bn = GetBNPtr(bn_obj); + + if (EC_GROUP_get_order(group, bn, ossl_bn_ctx) != 1) + ossl_raise(eEC_GROUP, "EC_GROUP_get_order"); + + return bn_obj; +} + +/* call-seq: + * group.get_cofactor => cofactor_bn + * + * See the OpenSSL documentation for EC_GROUP_get_cofactor() + */ +static VALUE ossl_ec_group_get_cofactor(VALUE self) +{ + VALUE bn_obj; + BIGNUM *bn; + EC_GROUP *group = NULL; + + Require_EC_GROUP(self, group); + + bn_obj = ossl_bn_new(NULL); + bn = GetBNPtr(bn_obj); + + if (EC_GROUP_get_cofactor(group, bn, ossl_bn_ctx) != 1) + ossl_raise(eEC_GROUP, "EC_GROUP_get_cofactor"); + + return bn_obj; +} + +/* call-seq: + * group.curve_name => String + * + * See the OpenSSL documentation for EC_GROUP_get_curve_name() + */ +static VALUE ossl_ec_group_get_curve_name(VALUE self) +{ + EC_GROUP *group = NULL; + int nid; + + Get_EC_GROUP(self, group); + if (group == NULL) + return Qnil; + + nid = EC_GROUP_get_curve_name(group); + +/* BUG: an nid or asn1 object should be returned, maybe. */ + return rb_str_new2(OBJ_nid2sn(nid)); +} + +/* call-seq: + * EC.builtin_curves => [[name, comment], ...] + * + * See the OpenSSL documentation for EC_builtin_curves() + */ +static VALUE ossl_s_builtin_curves(VALUE self) +{ + EC_builtin_curve *curves = NULL; + int n; + int crv_len = rb_long2int(EC_get_builtin_curves(NULL, 0)); + VALUE ary, ret; + + curves = ALLOCA_N(EC_builtin_curve, crv_len); + if (curves == NULL) + return Qnil; + if (!EC_get_builtin_curves(curves, crv_len)) + ossl_raise(rb_eRuntimeError, "EC_get_builtin_curves"); + + ret = rb_ary_new2(crv_len); + + for (n = 0; n < crv_len; n++) { + const char *sname = OBJ_nid2sn(curves[n].nid); + const char *comment = curves[n].comment; + + ary = rb_ary_new2(2); + rb_ary_push(ary, rb_str_new2(sname)); + rb_ary_push(ary, comment ? rb_str_new2(comment) : Qnil); + rb_ary_push(ret, ary); + } + + return ret; +} + +/* call-seq: + * group.asn1_flag => Fixnum + * + * See the OpenSSL documentation for EC_GROUP_get_asn1_flag() + */ +static VALUE ossl_ec_group_get_asn1_flag(VALUE self) +{ + EC_GROUP *group = NULL; + int flag; + + Require_EC_GROUP(self, group); + + flag = EC_GROUP_get_asn1_flag(group); + + return INT2FIX(flag); +} + +/* call-seq: + * group.asn1_flag = Fixnum => Fixnum + * + * See the OpenSSL documentation for EC_GROUP_set_asn1_flag() + */ +static VALUE ossl_ec_group_set_asn1_flag(VALUE self, VALUE flag_v) +{ + EC_GROUP *group = NULL; + + Require_EC_GROUP(self, group); + + EC_GROUP_set_asn1_flag(group, NUM2INT(flag_v)); + + return flag_v; +} + +/* call-seq: + * group.point_conversion_form => :uncompressed | :compressed | :hybrid + * + * See the OpenSSL documentation for EC_GROUP_get_point_conversion_form() + */ +static VALUE ossl_ec_group_get_point_conversion_form(VALUE self) +{ + EC_GROUP *group = NULL; + point_conversion_form_t form; + VALUE ret; + + Require_EC_GROUP(self, group); + + form = EC_GROUP_get_point_conversion_form(group); + + switch (form) { + case POINT_CONVERSION_UNCOMPRESSED: ret = ID_uncompressed; break; + case POINT_CONVERSION_COMPRESSED: ret = ID_compressed; break; + case POINT_CONVERSION_HYBRID: ret = ID_hybrid; break; + default: ossl_raise(eEC_GROUP, "unsupported point conversion form: %d, this module should be updated", form); + } + + return ID2SYM(ret); +} + +/* call-seq: + * group.point_conversion_form = form => form + * + * See the OpenSSL documentation for EC_GROUP_set_point_conversion_form() + */ +static VALUE ossl_ec_group_set_point_conversion_form(VALUE self, VALUE form_v) +{ + EC_GROUP *group = NULL; + point_conversion_form_t form; + ID form_id = SYM2ID(form_v); + + Require_EC_GROUP(self, group); + + if (form_id == ID_uncompressed) { + form = POINT_CONVERSION_UNCOMPRESSED; + } else if (form_id == ID_compressed) { + form = POINT_CONVERSION_COMPRESSED; + } else if (form_id == ID_hybrid) { + form = POINT_CONVERSION_HYBRID; + } else { + ossl_raise(rb_eArgError, "form must be :compressed, :uncompressed, or :hybrid"); + } + + EC_GROUP_set_point_conversion_form(group, form); + + return form_v; +} + +/* call-seq: + * group.seed => String or nil + * + * See the OpenSSL documentation for EC_GROUP_get0_seed() + */ +static VALUE ossl_ec_group_get_seed(VALUE self) +{ + EC_GROUP *group = NULL; + size_t seed_len; + + Require_EC_GROUP(self, group); + + seed_len = EC_GROUP_get_seed_len(group); + + if (seed_len == 0) + return Qnil; + + return rb_str_new((const char *)EC_GROUP_get0_seed(group), seed_len); +} + +/* call-seq: + * group.seed = seed => seed + * + * See the OpenSSL documentation for EC_GROUP_set_seed() + */ +static VALUE ossl_ec_group_set_seed(VALUE self, VALUE seed) +{ + EC_GROUP *group = NULL; + + Require_EC_GROUP(self, group); + StringValue(seed); + + if (EC_GROUP_set_seed(group, (unsigned char *)RSTRING_PTR(seed), RSTRING_LEN(seed)) != (size_t)RSTRING_LEN(seed)) + ossl_raise(eEC_GROUP, "EC_GROUP_set_seed"); + + return seed; +} + +/* get/set curve GFp, GF2m */ + +/* call-seq: + * group.degree => Fixnum + * + * See the OpenSSL documentation for EC_GROUP_get_degree() + */ +static VALUE ossl_ec_group_get_degree(VALUE self) +{ + EC_GROUP *group = NULL; + + Require_EC_GROUP(self, group); + + return INT2NUM(EC_GROUP_get_degree(group)); +} + +static VALUE ossl_ec_group_to_string(VALUE self, int format) +{ + EC_GROUP *group; + BIO *out; + int i = -1; + VALUE str; + + Get_EC_GROUP(self, group); + + if (!(out = BIO_new(BIO_s_mem()))) + ossl_raise(eEC_GROUP, "BIO_new(BIO_s_mem())"); + + switch(format) { + case EXPORT_PEM: + i = PEM_write_bio_ECPKParameters(out, group); + break; + case EXPORT_DER: + i = i2d_ECPKParameters_bio(out, group); + break; + default: + BIO_free(out); + ossl_raise(rb_eRuntimeError, "unknown format (internal error)"); + } + + if (i != 1) { + BIO_free(out); + ossl_raise(eECError, NULL); + } + + str = ossl_membio2str(out); + + return str; +} + +/* call-seq: + * group.to_pem => String + * + * See the OpenSSL documentation for PEM_write_bio_ECPKParameters() + */ +static VALUE ossl_ec_group_to_pem(VALUE self) +{ + return ossl_ec_group_to_string(self, EXPORT_PEM); +} + +/* call-seq: + * group.to_der => String + * + * See the OpenSSL documentation for i2d_ECPKParameters_bio() + */ +static VALUE ossl_ec_group_to_der(VALUE self) +{ + return ossl_ec_group_to_string(self, EXPORT_DER); +} + +/* call-seq: + * group.to_text => String + * + * See the OpenSSL documentation for ECPKParameters_print() + */ +static VALUE ossl_ec_group_to_text(VALUE self) +{ + EC_GROUP *group; + BIO *out; + VALUE str; + + Require_EC_GROUP(self, group); + if (!(out = BIO_new(BIO_s_mem()))) { + ossl_raise(eEC_GROUP, "BIO_new(BIO_s_mem())"); + } + if (!ECPKParameters_print(out, group, 0)) { + BIO_free(out); + ossl_raise(eEC_GROUP, NULL); + } + str = ossl_membio2str(out); + + return str; +} + + +static void ossl_ec_point_free(ossl_ec_point *ec_point) +{ + if (!ec_point->dont_free && ec_point->point) + EC_POINT_clear_free(ec_point->point); + ruby_xfree(ec_point); +} + +static VALUE ossl_ec_point_alloc(VALUE klass) +{ + ossl_ec_point *ec_point; + VALUE obj; + + obj = Data_Make_Struct(klass, ossl_ec_point, 0, ossl_ec_point_free, ec_point); + + return obj; +} + +/* + * call-seq: + * OpenSSL::PKey::EC::Point.new(point) + * OpenSSL::PKey::EC::Point.new(group) + * OpenSSL::PKey::EC::Point.new(group, bn) + * + * See the OpenSSL documentation for EC_POINT_* + */ +static VALUE ossl_ec_point_initialize(int argc, VALUE *argv, VALUE self) +{ + ossl_ec_point *ec_point; + EC_POINT *point = NULL; + VALUE arg1, arg2; + VALUE group_v = Qnil; + const EC_GROUP *group = NULL; + + Data_Get_Struct(self, ossl_ec_point, ec_point); + if (ec_point->point) + ossl_raise(eEC_POINT, "EC_POINT already initialized"); + + switch (rb_scan_args(argc, argv, "11", &arg1, &arg2)) { + case 1: + if (rb_obj_is_kind_of(arg1, cEC_POINT)) { + const EC_POINT *arg_point; + + group_v = rb_iv_get(arg1, "@group"); + SafeRequire_EC_GROUP(group_v, group); + SafeRequire_EC_POINT(arg1, arg_point); + + point = EC_POINT_dup(arg_point, group); + } else if (rb_obj_is_kind_of(arg1, cEC_GROUP)) { + group_v = arg1; + SafeRequire_EC_GROUP(group_v, group); + + point = EC_POINT_new(group); + } else { + ossl_raise(eEC_POINT, "wrong argument type: must be OpenSSL::PKey::EC::Point or OpenSSL::Pkey::EC::Group"); + } + + break; + case 2: + if (!rb_obj_is_kind_of(arg1, cEC_GROUP)) + ossl_raise(rb_eArgError, "1st argument must be OpenSSL::PKey::EC::Group"); + group_v = arg1; + SafeRequire_EC_GROUP(group_v, group); + + if (rb_obj_is_kind_of(arg2, cBN)) { + const BIGNUM *bn = GetBNPtr(arg2); + + point = EC_POINT_bn2point(group, bn, NULL, ossl_bn_ctx); + } else { + BIO *in = ossl_obj2bio(arg1); + +/* BUG: finish me */ + + BIO_free(in); + + if (point == NULL) { + ossl_raise(eEC_POINT, "unknown type for 2nd arg"); + } + } + break; + default: + ossl_raise(rb_eArgError, "wrong number of arguments"); + } + + if (point == NULL) + ossl_raise(eEC_POINT, NULL); + + if (NIL_P(group_v)) + ossl_raise(rb_eRuntimeError, "missing group (internal error)"); + + ec_point->point = point; + + rb_iv_set(self, "@group", group_v); + + return self; +} + +/* + * call-seq: + * point1.eql?(point2) => true | false + * point1 == point2 => true | false + * + */ +static VALUE ossl_ec_point_eql(VALUE a, VALUE b) +{ + EC_POINT *point1, *point2; + VALUE group_v1 = rb_iv_get(a, "@group"); + VALUE group_v2 = rb_iv_get(b, "@group"); + const EC_GROUP *group; + + if (ossl_ec_group_eql(group_v1, group_v2) == Qfalse) + return Qfalse; + + Require_EC_POINT(a, point1); + SafeRequire_EC_POINT(b, point2); + SafeRequire_EC_GROUP(group_v1, group); + + if (EC_POINT_cmp(group, point1, point2, ossl_bn_ctx) == 1) + return Qfalse; + + return Qtrue; +} + +/* + * call-seq: + * point.infinity? => true | false + * + */ +static VALUE ossl_ec_point_is_at_infinity(VALUE self) +{ + EC_POINT *point; + VALUE group_v = rb_iv_get(self, "@group"); + const EC_GROUP *group; + + Require_EC_POINT(self, point); + SafeRequire_EC_GROUP(group_v, group); + + switch (EC_POINT_is_at_infinity(group, point)) { + case 1: return Qtrue; + case 0: return Qfalse; + default: ossl_raise(cEC_POINT, "EC_POINT_is_at_infinity"); + } + + UNREACHABLE; +} + +/* + * call-seq: + * point.on_curve? => true | false + * + */ +static VALUE ossl_ec_point_is_on_curve(VALUE self) +{ + EC_POINT *point; + VALUE group_v = rb_iv_get(self, "@group"); + const EC_GROUP *group; + + Require_EC_POINT(self, point); + SafeRequire_EC_GROUP(group_v, group); + + switch (EC_POINT_is_on_curve(group, point, ossl_bn_ctx)) { + case 1: return Qtrue; + case 0: return Qfalse; + default: ossl_raise(cEC_POINT, "EC_POINT_is_on_curve"); + } + + UNREACHABLE; +} + +/* + * call-seq: + * point.make_affine! => self + * + */ +static VALUE ossl_ec_point_make_affine(VALUE self) +{ + EC_POINT *point; + VALUE group_v = rb_iv_get(self, "@group"); + const EC_GROUP *group; + + Require_EC_POINT(self, point); + SafeRequire_EC_GROUP(group_v, group); + + if (EC_POINT_make_affine(group, point, ossl_bn_ctx) != 1) + ossl_raise(cEC_POINT, "EC_POINT_make_affine"); + + return self; +} + +/* + * call-seq: + * point.invert! => self + * + */ +static VALUE ossl_ec_point_invert(VALUE self) +{ + EC_POINT *point; + VALUE group_v = rb_iv_get(self, "@group"); + const EC_GROUP *group; + + Require_EC_POINT(self, point); + SafeRequire_EC_GROUP(group_v, group); + + if (EC_POINT_invert(group, point, ossl_bn_ctx) != 1) + ossl_raise(cEC_POINT, "EC_POINT_invert"); + + return self; +} + +/* + * call-seq: + * point.set_to_infinity! => self + * + */ +static VALUE ossl_ec_point_set_to_infinity(VALUE self) +{ + EC_POINT *point; + VALUE group_v = rb_iv_get(self, "@group"); + const EC_GROUP *group; + + Require_EC_POINT(self, point); + SafeRequire_EC_GROUP(group_v, group); + + if (EC_POINT_set_to_infinity(group, point) != 1) + ossl_raise(cEC_POINT, "EC_POINT_set_to_infinity"); + + return self; +} + +/* + * call-seq: + * point.to_bn => OpenSSL::BN + * + * See the OpenSSL documentation for EC_POINT_point2bn() + */ +static VALUE ossl_ec_point_to_bn(VALUE self) +{ + EC_POINT *point; + VALUE bn_obj; + VALUE group_v = rb_iv_get(self, "@group"); + const EC_GROUP *group; + point_conversion_form_t form; + BIGNUM *bn; + + Require_EC_POINT(self, point); + SafeRequire_EC_GROUP(group_v, group); + + form = EC_GROUP_get_point_conversion_form(group); + + bn_obj = rb_obj_alloc(cBN); + bn = GetBNPtr(bn_obj); + + if (EC_POINT_point2bn(group, point, form, bn, ossl_bn_ctx) == NULL) + ossl_raise(eEC_POINT, "EC_POINT_point2bn"); + + return bn_obj; +} + +/* + * call-seq: + * point.mul(bn) => point + * point.mul(bn, bn) => point + * point.mul([bn], [point]) => point + * point.mul([bn], [point], bn) => point + */ +static VALUE ossl_ec_point_mul(int argc, VALUE *argv, VALUE self) +{ + EC_POINT *point1, *point2; + const EC_GROUP *group; + VALUE group_v = rb_iv_get(self, "@group"); + VALUE bn_v1, bn_v2, r, points_v; + BIGNUM *bn1 = NULL, *bn2 = NULL; + + Require_EC_POINT(self, point1); + SafeRequire_EC_GROUP(group_v, group); + + r = rb_obj_alloc(cEC_POINT); + ossl_ec_point_initialize(1, &group_v, r); + Require_EC_POINT(r, point2); + + argc = rb_scan_args(argc, argv, "12", &bn_v1, &points_v, &bn_v2); + + if (rb_obj_is_kind_of(bn_v1, cBN)) { + bn1 = GetBNPtr(bn_v1); + if (argc >= 2) { + bn2 = GetBNPtr(points_v); + } + if (EC_POINT_mul(group, point2, bn2, point1, bn1, ossl_bn_ctx) != 1) + ossl_raise(eEC_POINT, "Multiplication failed"); + } else { + size_t i, points_len, bignums_len; + const EC_POINT **points; + const BIGNUM **bignums; + + Check_Type(bn_v1, T_ARRAY); + bignums_len = RARRAY_LEN(bn_v1); + bignums = (const BIGNUM **)OPENSSL_malloc(bignums_len * (int)sizeof(BIGNUM *)); + + for (i = 0; i < bignums_len; ++i) { + bignums[i] = GetBNPtr(rb_ary_entry(bn_v1, i)); + } + + if (!rb_obj_is_kind_of(points_v, rb_cArray)) { + OPENSSL_free((void *)bignums); + rb_raise(rb_eTypeError, "Argument2 must be an array"); + } + + rb_ary_unshift(points_v, self); + points_len = RARRAY_LEN(points_v); + points = (const EC_POINT **)OPENSSL_malloc(points_len * (int)sizeof(EC_POINT *)); + + for (i = 0; i < points_len; ++i) { + Get_EC_POINT(rb_ary_entry(points_v, i), points[i]); + } + + if (argc >= 3) { + bn2 = GetBNPtr(bn_v2); + } + if (EC_POINTs_mul(group, point2, bn2, points_len, points, bignums, ossl_bn_ctx) != 1) { + OPENSSL_free((void *)bignums); + OPENSSL_free((void *)points); + ossl_raise(eEC_POINT, "Multiplication failed"); + } + OPENSSL_free((void *)bignums); + OPENSSL_free((void *)points); + } + + return r; +} + +static void no_copy(VALUE klass) +{ + rb_undef_method(klass, "copy"); + rb_undef_method(klass, "clone"); + rb_undef_method(klass, "dup"); + rb_undef_method(klass, "initialize_copy"); +} + +void Init_ossl_ec(void) +{ +#ifdef DONT_NEED_RDOC_WORKAROUND + mOSSL = rb_define_module("OpenSSL"); + mPKey = rb_define_module_under(mOSSL, "PKey"); +#endif + + eECError = rb_define_class_under(mPKey, "ECError", ePKeyError); + + cEC = rb_define_class_under(mPKey, "EC", cPKey); + cEC_GROUP = rb_define_class_under(cEC, "Group", rb_cObject); + cEC_POINT = rb_define_class_under(cEC, "Point", rb_cObject); + eEC_GROUP = rb_define_class_under(cEC_GROUP, "Error", eOSSLError); + eEC_POINT = rb_define_class_under(cEC_POINT, "Error", eOSSLError); + + s_GFp = rb_intern("GFp"); + s_GF2m = rb_intern("GF2m"); + s_GFp_simple = rb_intern("GFp_simple"); + s_GFp_mont = rb_intern("GFp_mont"); + s_GFp_nist = rb_intern("GFp_nist"); + s_GF2m_simple = rb_intern("GF2m_simple"); + + ID_uncompressed = rb_intern("uncompressed"); + ID_compressed = rb_intern("compressed"); + ID_hybrid = rb_intern("hybrid"); + +#ifdef OPENSSL_EC_NAMED_CURVE + rb_define_const(cEC, "NAMED_CURVE", ULONG2NUM(OPENSSL_EC_NAMED_CURVE)); +#endif + + rb_define_singleton_method(cEC, "builtin_curves", ossl_s_builtin_curves, 0); + + rb_define_method(cEC, "initialize", ossl_ec_key_initialize, -1); +/* copy/dup/cmp */ + + rb_define_method(cEC, "group", ossl_ec_key_get_group, 0); + rb_define_method(cEC, "group=", ossl_ec_key_set_group, 1); + rb_define_method(cEC, "private_key", ossl_ec_key_get_private_key, 0); + rb_define_method(cEC, "private_key=", ossl_ec_key_set_private_key, 1); + rb_define_method(cEC, "public_key", ossl_ec_key_get_public_key, 0); + rb_define_method(cEC, "public_key=", ossl_ec_key_set_public_key, 1); + rb_define_method(cEC, "private_key?", ossl_ec_key_is_private_key, 0); + rb_define_method(cEC, "public_key?", ossl_ec_key_is_public_key, 0); +/* rb_define_method(cEC, "", ossl_ec_key_get_, 0); + rb_define_method(cEC, "=", ossl_ec_key_set_ 1); + set/get enc_flags + set/get _conv_from + set/get asn1_flag (can use ruby to call self.group.asn1_flag) + set/get precompute_mult +*/ + rb_define_method(cEC, "generate_key", ossl_ec_key_generate_key, 0); + rb_define_method(cEC, "check_key", ossl_ec_key_check_key, 0); + + rb_define_method(cEC, "dh_compute_key", ossl_ec_key_dh_compute_key, 1); + rb_define_method(cEC, "dsa_sign_asn1", ossl_ec_key_dsa_sign_asn1, 1); + rb_define_method(cEC, "dsa_verify_asn1", ossl_ec_key_dsa_verify_asn1, 2); +/* do_sign/do_verify */ + + rb_define_method(cEC, "export", ossl_ec_key_export, -1); + rb_define_alias(cEC, "to_pem", "export"); + rb_define_method(cEC, "to_der", ossl_ec_key_to_der, 0); + rb_define_method(cEC, "to_text", ossl_ec_key_to_text, 0); + + + rb_define_alloc_func(cEC_GROUP, ossl_ec_group_alloc); + rb_define_method(cEC_GROUP, "initialize", ossl_ec_group_initialize, -1); + rb_define_method(cEC_GROUP, "eql?", ossl_ec_group_eql, 1); + rb_define_alias(cEC_GROUP, "==", "eql?"); +/* copy/dup/cmp */ + + rb_define_method(cEC_GROUP, "generator", ossl_ec_group_get_generator, 0); + rb_define_method(cEC_GROUP, "set_generator", ossl_ec_group_set_generator, 3); + rb_define_method(cEC_GROUP, "order", ossl_ec_group_get_order, 0); + rb_define_method(cEC_GROUP, "cofactor", ossl_ec_group_get_cofactor, 0); + + rb_define_method(cEC_GROUP, "curve_name", ossl_ec_group_get_curve_name, 0); +/* rb_define_method(cEC_GROUP, "curve_name=", ossl_ec_group_set_curve_name, 1); */ + + rb_define_method(cEC_GROUP, "asn1_flag", ossl_ec_group_get_asn1_flag, 0); + rb_define_method(cEC_GROUP, "asn1_flag=", ossl_ec_group_set_asn1_flag, 1); + + rb_define_method(cEC_GROUP, "point_conversion_form", ossl_ec_group_get_point_conversion_form, 0); + rb_define_method(cEC_GROUP, "point_conversion_form=", ossl_ec_group_set_point_conversion_form, 1); + + rb_define_method(cEC_GROUP, "seed", ossl_ec_group_get_seed, 0); + rb_define_method(cEC_GROUP, "seed=", ossl_ec_group_set_seed, 1); + +/* get/set GFp, GF2m */ + + rb_define_method(cEC_GROUP, "degree", ossl_ec_group_get_degree, 0); + +/* check* */ + + + rb_define_method(cEC_GROUP, "to_pem", ossl_ec_group_to_pem, 0); + rb_define_method(cEC_GROUP, "to_der", ossl_ec_group_to_der, 0); + rb_define_method(cEC_GROUP, "to_text", ossl_ec_group_to_text, 0); + + + rb_define_alloc_func(cEC_POINT, ossl_ec_point_alloc); + rb_define_method(cEC_POINT, "initialize", ossl_ec_point_initialize, -1); + rb_attr(cEC_POINT, rb_intern("group"), 1, 0, 0); + rb_define_method(cEC_POINT, "eql?", ossl_ec_point_eql, 1); + rb_define_alias(cEC_POINT, "==", "eql?"); + + rb_define_method(cEC_POINT, "infinity?", ossl_ec_point_is_at_infinity, 0); + rb_define_method(cEC_POINT, "on_curve?", ossl_ec_point_is_on_curve, 0); + rb_define_method(cEC_POINT, "make_affine!", ossl_ec_point_make_affine, 0); + rb_define_method(cEC_POINT, "invert!", ossl_ec_point_invert, 0); + rb_define_method(cEC_POINT, "set_to_infinity!", ossl_ec_point_set_to_infinity, 0); +/* all the other methods */ + + rb_define_method(cEC_POINT, "to_bn", ossl_ec_point_to_bn, 0); + rb_define_method(cEC_POINT, "mul", ossl_ec_point_mul, -1); + + no_copy(cEC); + no_copy(cEC_GROUP); + no_copy(cEC_POINT); +} + +#else /* defined NO_EC */ +void Init_ossl_ec(void) +{ +} +#endif /* NO_EC */ diff --git a/ext/openssl/ossl_pkey_rsa.c b/ext/openssl/ossl_pkey_rsa.c new file mode 100644 index 00000000..0fef10a0 --- /dev/null +++ b/ext/openssl/ossl_pkey_rsa.c @@ -0,0 +1,701 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2001-2002 Michal Rokos <m.rokos@sh.cvut.cz> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#if !defined(OPENSSL_NO_RSA) + +#include "ossl.h" + +#define GetPKeyRSA(obj, pkey) do { \ + GetPKey((obj), (pkey)); \ + if (EVP_PKEY_type((pkey)->type) != EVP_PKEY_RSA) { /* PARANOIA? */ \ + ossl_raise(rb_eRuntimeError, "THIS IS NOT A RSA!") ; \ + } \ +} while (0) + +#define RSA_HAS_PRIVATE(rsa) ((rsa)->p && (rsa)->q) +#define RSA_PRIVATE(obj,rsa) (RSA_HAS_PRIVATE(rsa)||OSSL_PKEY_IS_PRIVATE(obj)) + +/* + * Classes + */ +VALUE cRSA; +VALUE eRSAError; + +/* + * Public + */ +static VALUE +rsa_instance(VALUE klass, RSA *rsa) +{ + EVP_PKEY *pkey; + VALUE obj; + + if (!rsa) { + return Qfalse; + } + if (!(pkey = EVP_PKEY_new())) { + return Qfalse; + } + if (!EVP_PKEY_assign_RSA(pkey, rsa)) { + EVP_PKEY_free(pkey); + return Qfalse; + } + WrapPKey(klass, obj, pkey); + + return obj; +} + +VALUE +ossl_rsa_new(EVP_PKEY *pkey) +{ + VALUE obj; + + if (!pkey) { + obj = rsa_instance(cRSA, RSA_new()); + } + else { + if (EVP_PKEY_type(pkey->type) != EVP_PKEY_RSA) { + ossl_raise(rb_eTypeError, "Not a RSA key!"); + } + WrapPKey(cRSA, obj, pkey); + } + if (obj == Qfalse) { + ossl_raise(eRSAError, NULL); + } + + return obj; +} + +/* + * Private + */ +#if defined(HAVE_RSA_GENERATE_KEY_EX) && HAVE_BN_GENCB +struct rsa_blocking_gen_arg { + RSA *rsa; + BIGNUM *e; + int size; + BN_GENCB *cb; + int result; +}; + +static void * +rsa_blocking_gen(void *arg) +{ + struct rsa_blocking_gen_arg *gen = (struct rsa_blocking_gen_arg *)arg; + gen->result = RSA_generate_key_ex(gen->rsa, gen->size, gen->e, gen->cb); + return 0; +} +#endif + +static RSA * +rsa_generate(int size, unsigned long exp) +{ +#if defined(HAVE_RSA_GENERATE_KEY_EX) && HAVE_BN_GENCB + int i; + BN_GENCB cb; + struct ossl_generate_cb_arg cb_arg; + struct rsa_blocking_gen_arg gen_arg; + RSA *rsa = RSA_new(); + BIGNUM *e = BN_new(); + + if (!rsa || !e) { + if (e) BN_free(e); + if (rsa) RSA_free(rsa); + return 0; + } + for (i = 0; i < (int)sizeof(exp) * 8; ++i) { + if (exp & (1UL << i)) { + if (BN_set_bit(e, i) == 0) { + BN_free(e); + RSA_free(rsa); + return 0; + } + } + } + + memset(&cb_arg, 0, sizeof(struct ossl_generate_cb_arg)); + if (rb_block_given_p()) + cb_arg.yield = 1; + BN_GENCB_set(&cb, ossl_generate_cb_2, &cb_arg); + gen_arg.rsa = rsa; + gen_arg.e = e; + gen_arg.size = size; + gen_arg.cb = &cb; + if (cb_arg.yield == 1) { + /* we cannot release GVL when callback proc is supplied */ + rsa_blocking_gen(&gen_arg); + } else { + /* there's a chance to unblock */ + rb_thread_call_without_gvl(rsa_blocking_gen, &gen_arg, ossl_generate_cb_stop, &cb_arg); + } + if (!gen_arg.result) { + BN_free(e); + RSA_free(rsa); + if (cb_arg.state) rb_jump_tag(cb_arg.state); + return 0; + } + + BN_free(e); + return rsa; +#else + return RSA_generate_key(size, exp, rb_block_given_p() ? ossl_generate_cb : NULL, NULL); +#endif +} + +/* + * call-seq: + * RSA.generate(size) => RSA instance + * RSA.generate(size, exponent) => RSA instance + * + * Generates an RSA keypair. +size+ is an integer representing the desired key + * size. Keys smaller than 1024 should be considered insecure. +exponent+ is + * an odd number normally 3, 17, or 65537. + */ +static VALUE +ossl_rsa_s_generate(int argc, VALUE *argv, VALUE klass) +{ +/* why does this method exist? why can't initialize take an optional exponent? */ + RSA *rsa; + VALUE size, exp; + VALUE obj; + + rb_scan_args(argc, argv, "11", &size, &exp); + + rsa = rsa_generate(NUM2INT(size), NIL_P(exp) ? RSA_F4 : NUM2ULONG(exp)); /* err handled by rsa_instance */ + obj = rsa_instance(klass, rsa); + + if (obj == Qfalse) { + RSA_free(rsa); + ossl_raise(eRSAError, NULL); + } + + return obj; +} + +/* + * call-seq: + * RSA.new(key_size) => RSA instance + * RSA.new(encoded_key) => RSA instance + * RSA.new(encoded_key, pass_phrase) => RSA instance + * + * Generates or loads an RSA keypair. If an integer +key_size+ is given it + * represents the desired key size. Keys less than 1024 bits should be + * considered insecure. + * + * A key can instead be loaded from an +encoded_key+ which must be PEM or DER + * encoded. A +pass_phrase+ can be used to decrypt the key. If none is given + * OpenSSL will prompt for the pass phrase. + * + * = Examples + * + * OpenSSL::PKey::RSA.new 2048 + * OpenSSL::PKey::RSA.new File.read 'rsa.pem' + * OpenSSL::PKey::RSA.new File.read('rsa.pem'), 'my pass phrase' + */ +static VALUE +ossl_rsa_initialize(int argc, VALUE *argv, VALUE self) +{ + EVP_PKEY *pkey; + RSA *rsa; + BIO *in; + char *passwd = NULL; + VALUE arg, pass; + + GetPKey(self, pkey); + if(rb_scan_args(argc, argv, "02", &arg, &pass) == 0) { + rsa = RSA_new(); + } + else if (FIXNUM_P(arg)) { + rsa = rsa_generate(FIX2INT(arg), NIL_P(pass) ? RSA_F4 : NUM2ULONG(pass)); + if (!rsa) ossl_raise(eRSAError, NULL); + } + else { + if (!NIL_P(pass)) passwd = StringValuePtr(pass); + arg = ossl_to_der_if_possible(arg); + in = ossl_obj2bio(arg); + rsa = PEM_read_bio_RSAPrivateKey(in, NULL, ossl_pem_passwd_cb, passwd); + if (!rsa) { + OSSL_BIO_reset(in); + rsa = PEM_read_bio_RSA_PUBKEY(in, NULL, NULL, NULL); + } + if (!rsa) { + OSSL_BIO_reset(in); + rsa = d2i_RSAPrivateKey_bio(in, NULL); + } + if (!rsa) { + OSSL_BIO_reset(in); + rsa = d2i_RSA_PUBKEY_bio(in, NULL); + } + if (!rsa) { + OSSL_BIO_reset(in); + rsa = PEM_read_bio_RSAPublicKey(in, NULL, NULL, NULL); + } + if (!rsa) { + OSSL_BIO_reset(in); + rsa = d2i_RSAPublicKey_bio(in, NULL); + } + BIO_free(in); + if (!rsa) { + ossl_raise(eRSAError, "Neither PUB key nor PRIV key"); + } + } + if (!EVP_PKEY_assign_RSA(pkey, rsa)) { + RSA_free(rsa); + ossl_raise(eRSAError, NULL); + } + + return self; +} + +/* + * call-seq: + * rsa.public? => true + * + * The return value is always true since every private key is also a public + * key. + */ +static VALUE +ossl_rsa_is_public(VALUE self) +{ + EVP_PKEY *pkey; + + GetPKeyRSA(self, pkey); + /* + * This method should check for n and e. BUG. + */ + return Qtrue; +} + +/* + * call-seq: + * rsa.private? => true | false + * + * Does this keypair contain a private key? + */ +static VALUE +ossl_rsa_is_private(VALUE self) +{ + EVP_PKEY *pkey; + + GetPKeyRSA(self, pkey); + + return (RSA_PRIVATE(self, pkey->pkey.rsa)) ? Qtrue : Qfalse; +} + +/* + * call-seq: + * rsa.export([cipher, pass_phrase]) => PEM-format String + * rsa.to_pem([cipher, pass_phrase]) => PEM-format String + * rsa.to_s([cipher, pass_phrase]) => PEM-format String + * + * Outputs this keypair in PEM encoding. If +cipher+ and +pass_phrase+ are + * given they will be used to encrypt the key. +cipher+ must be an + * OpenSSL::Cipher::Cipher instance. + */ +static VALUE +ossl_rsa_export(int argc, VALUE *argv, VALUE self) +{ + EVP_PKEY *pkey; + BIO *out; + const EVP_CIPHER *ciph = NULL; + char *passwd = NULL; + VALUE cipher, pass, str; + + GetPKeyRSA(self, pkey); + + rb_scan_args(argc, argv, "02", &cipher, &pass); + + if (!NIL_P(cipher)) { + ciph = GetCipherPtr(cipher); + if (!NIL_P(pass)) { + StringValue(pass); + if (RSTRING_LENINT(pass) < OSSL_MIN_PWD_LEN) + ossl_raise(eOSSLError, "OpenSSL requires passwords to be at least four characters long"); + passwd = RSTRING_PTR(pass); + } + } + if (!(out = BIO_new(BIO_s_mem()))) { + ossl_raise(eRSAError, NULL); + } + if (RSA_HAS_PRIVATE(pkey->pkey.rsa)) { + if (!PEM_write_bio_RSAPrivateKey(out, pkey->pkey.rsa, ciph, + NULL, 0, ossl_pem_passwd_cb, passwd)) { + BIO_free(out); + ossl_raise(eRSAError, NULL); + } + } else { + if (!PEM_write_bio_RSA_PUBKEY(out, pkey->pkey.rsa)) { + BIO_free(out); + ossl_raise(eRSAError, NULL); + } + } + str = ossl_membio2str(out); + + return str; +} + +/* + * call-seq: + * rsa.to_der => DER-format String + * + * Outputs this keypair in DER encoding. + */ +static VALUE +ossl_rsa_to_der(VALUE self) +{ + EVP_PKEY *pkey; + int (*i2d_func)_((const RSA*, unsigned char**)); + unsigned char *p; + long len; + VALUE str; + + GetPKeyRSA(self, pkey); + if(RSA_HAS_PRIVATE(pkey->pkey.rsa)) + i2d_func = i2d_RSAPrivateKey; + else + i2d_func = (int (*)(const RSA*, unsigned char**))i2d_RSA_PUBKEY; + if((len = i2d_func(pkey->pkey.rsa, NULL)) <= 0) + ossl_raise(eRSAError, NULL); + str = rb_str_new(0, len); + p = (unsigned char *)RSTRING_PTR(str); + if(i2d_func(pkey->pkey.rsa, &p) < 0) + ossl_raise(eRSAError, NULL); + ossl_str_adjust(str, p); + + return str; +} + +#define ossl_rsa_buf_size(pkey) (RSA_size((pkey)->pkey.rsa)+16) + +/* + * call-seq: + * rsa.public_encrypt(string) => String + * rsa.public_encrypt(string, padding) => String + * + * Encrypt +string+ with the public key. +padding+ defaults to PKCS1_PADDING. + * The encrypted string output can be decrypted using #private_decrypt. + */ +static VALUE +ossl_rsa_public_encrypt(int argc, VALUE *argv, VALUE self) +{ + EVP_PKEY *pkey; + int buf_len, pad; + VALUE str, buffer, padding; + + GetPKeyRSA(self, pkey); + rb_scan_args(argc, argv, "11", &buffer, &padding); + pad = (argc == 1) ? RSA_PKCS1_PADDING : NUM2INT(padding); + StringValue(buffer); + str = rb_str_new(0, ossl_rsa_buf_size(pkey)); + buf_len = RSA_public_encrypt(RSTRING_LENINT(buffer), (unsigned char *)RSTRING_PTR(buffer), + (unsigned char *)RSTRING_PTR(str), pkey->pkey.rsa, + pad); + if (buf_len < 0) ossl_raise(eRSAError, NULL); + rb_str_set_len(str, buf_len); + + return str; +} + +/* + * call-seq: + * rsa.public_decrypt(string) => String + * rsa.public_decrypt(string, padding) => String + * + * Decrypt +string+, which has been encrypted with the private key, with the + * public key. +padding+ defaults to PKCS1_PADDING. + */ +static VALUE +ossl_rsa_public_decrypt(int argc, VALUE *argv, VALUE self) +{ + EVP_PKEY *pkey; + int buf_len, pad; + VALUE str, buffer, padding; + + GetPKeyRSA(self, pkey); + rb_scan_args(argc, argv, "11", &buffer, &padding); + pad = (argc == 1) ? RSA_PKCS1_PADDING : NUM2INT(padding); + StringValue(buffer); + str = rb_str_new(0, ossl_rsa_buf_size(pkey)); + buf_len = RSA_public_decrypt(RSTRING_LENINT(buffer), (unsigned char *)RSTRING_PTR(buffer), + (unsigned char *)RSTRING_PTR(str), pkey->pkey.rsa, + pad); + if (buf_len < 0) ossl_raise(eRSAError, NULL); + rb_str_set_len(str, buf_len); + + return str; +} + +/* + * call-seq: + * rsa.private_encrypt(string) => String + * rsa.private_encrypt(string, padding) => String + * + * Encrypt +string+ with the private key. +padding+ defaults to PKCS1_PADDING. + * The encrypted string output can be decrypted using #public_decrypt. + */ +static VALUE +ossl_rsa_private_encrypt(int argc, VALUE *argv, VALUE self) +{ + EVP_PKEY *pkey; + int buf_len, pad; + VALUE str, buffer, padding; + + GetPKeyRSA(self, pkey); + if (!RSA_PRIVATE(self, pkey->pkey.rsa)) { + ossl_raise(eRSAError, "private key needed."); + } + rb_scan_args(argc, argv, "11", &buffer, &padding); + pad = (argc == 1) ? RSA_PKCS1_PADDING : NUM2INT(padding); + StringValue(buffer); + str = rb_str_new(0, ossl_rsa_buf_size(pkey)); + buf_len = RSA_private_encrypt(RSTRING_LENINT(buffer), (unsigned char *)RSTRING_PTR(buffer), + (unsigned char *)RSTRING_PTR(str), pkey->pkey.rsa, + pad); + if (buf_len < 0) ossl_raise(eRSAError, NULL); + rb_str_set_len(str, buf_len); + + return str; +} + +/* + * call-seq: + * rsa.private_decrypt(string) => String + * rsa.private_decrypt(string, padding) => String + * + * Decrypt +string+, which has been encrypted with the public key, with the + * private key. +padding+ defaults to PKCS1_PADDING. + */ +static VALUE +ossl_rsa_private_decrypt(int argc, VALUE *argv, VALUE self) +{ + EVP_PKEY *pkey; + int buf_len, pad; + VALUE str, buffer, padding; + + GetPKeyRSA(self, pkey); + if (!RSA_PRIVATE(self, pkey->pkey.rsa)) { + ossl_raise(eRSAError, "private key needed."); + } + rb_scan_args(argc, argv, "11", &buffer, &padding); + pad = (argc == 1) ? RSA_PKCS1_PADDING : NUM2INT(padding); + StringValue(buffer); + str = rb_str_new(0, ossl_rsa_buf_size(pkey)); + buf_len = RSA_private_decrypt(RSTRING_LENINT(buffer), (unsigned char *)RSTRING_PTR(buffer), + (unsigned char *)RSTRING_PTR(str), pkey->pkey.rsa, + pad); + if (buf_len < 0) ossl_raise(eRSAError, NULL); + rb_str_set_len(str, buf_len); + + return str; +} + +/* + * call-seq: + * rsa.params => hash + * + * THIS METHOD IS INSECURE, PRIVATE INFORMATION CAN LEAK OUT!!! + * + * Stores all parameters of key to the hash. The hash has keys 'n', 'e', 'd', + * 'p', 'q', 'dmp1', 'dmq1', 'iqmp'. + * + * Don't use :-)) (It's up to you) + */ +static VALUE +ossl_rsa_get_params(VALUE self) +{ + EVP_PKEY *pkey; + VALUE hash; + + GetPKeyRSA(self, pkey); + + hash = rb_hash_new(); + + rb_hash_aset(hash, rb_str_new2("n"), ossl_bn_new(pkey->pkey.rsa->n)); + rb_hash_aset(hash, rb_str_new2("e"), ossl_bn_new(pkey->pkey.rsa->e)); + rb_hash_aset(hash, rb_str_new2("d"), ossl_bn_new(pkey->pkey.rsa->d)); + rb_hash_aset(hash, rb_str_new2("p"), ossl_bn_new(pkey->pkey.rsa->p)); + rb_hash_aset(hash, rb_str_new2("q"), ossl_bn_new(pkey->pkey.rsa->q)); + rb_hash_aset(hash, rb_str_new2("dmp1"), ossl_bn_new(pkey->pkey.rsa->dmp1)); + rb_hash_aset(hash, rb_str_new2("dmq1"), ossl_bn_new(pkey->pkey.rsa->dmq1)); + rb_hash_aset(hash, rb_str_new2("iqmp"), ossl_bn_new(pkey->pkey.rsa->iqmp)); + + return hash; +} + +/* + * call-seq: + * rsa.to_text => String + * + * THIS METHOD IS INSECURE, PRIVATE INFORMATION CAN LEAK OUT!!! + * + * Dumps all parameters of a keypair to a String + * + * Don't use :-)) (It's up to you) + */ +static VALUE +ossl_rsa_to_text(VALUE self) +{ + EVP_PKEY *pkey; + BIO *out; + VALUE str; + + GetPKeyRSA(self, pkey); + if (!(out = BIO_new(BIO_s_mem()))) { + ossl_raise(eRSAError, NULL); + } + if (!RSA_print(out, pkey->pkey.rsa, 0)) { /* offset = 0 */ + BIO_free(out); + ossl_raise(eRSAError, NULL); + } + str = ossl_membio2str(out); + + return str; +} + +/* + * call-seq: + * rsa.public_key -> RSA + * + * Makes new RSA instance containing the public key from the private key. + */ +static VALUE +ossl_rsa_to_public_key(VALUE self) +{ + EVP_PKEY *pkey; + RSA *rsa; + VALUE obj; + + GetPKeyRSA(self, pkey); + /* err check performed by rsa_instance */ + rsa = RSAPublicKey_dup(pkey->pkey.rsa); + obj = rsa_instance(CLASS_OF(self), rsa); + if (obj == Qfalse) { + RSA_free(rsa); + ossl_raise(eRSAError, NULL); + } + return obj; +} + +/* + * TODO: Test me + +static VALUE +ossl_rsa_blinding_on(VALUE self) +{ + EVP_PKEY *pkey; + + GetPKeyRSA(self, pkey); + + if (RSA_blinding_on(pkey->pkey.rsa, ossl_bn_ctx) != 1) { + ossl_raise(eRSAError, NULL); + } + return self; +} + +static VALUE +ossl_rsa_blinding_off(VALUE self) +{ + EVP_PKEY *pkey; + + GetPKeyRSA(self, pkey); + RSA_blinding_off(pkey->pkey.rsa); + + return self; +} + */ + +OSSL_PKEY_BN(rsa, n) +OSSL_PKEY_BN(rsa, e) +OSSL_PKEY_BN(rsa, d) +OSSL_PKEY_BN(rsa, p) +OSSL_PKEY_BN(rsa, q) +OSSL_PKEY_BN(rsa, dmp1) +OSSL_PKEY_BN(rsa, dmq1) +OSSL_PKEY_BN(rsa, iqmp) + +/* + * INIT + */ +#define DefRSAConst(x) rb_define_const(cRSA, #x,INT2FIX(RSA_##x)) + +void +Init_ossl_rsa(void) +{ +#if 0 + mOSSL = rb_define_module("OpenSSL"); /* let rdoc know about mOSSL and mPKey */ + mPKey = rb_define_module_under(mOSSL, "PKey"); +#endif + + /* Document-class: OpenSSL::PKey::RSAError + * + * Generic exception that is raised if an operation on an RSA PKey + * fails unexpectedly or in case an instantiation of an instance of RSA + * fails due to non-conformant input data. + */ + eRSAError = rb_define_class_under(mPKey, "RSAError", ePKeyError); + + /* Document-class: OpenSSL::PKey::RSA + * + * RSA is an asymmetric public key algorithm that has been formalized in + * RFC 3447. It is in widespread use in public key infrastuctures (PKI) + * where certificates (cf. OpenSSL::X509::Certificate) often are issued + * on the basis of a public/private RSA key pair. RSA is used in a wide + * field of applications such as secure (symmetric) key exchange, e.g. + * when establishing a secure TLS/SSL connection. It is also used in + * various digital signature schemes. + */ + cRSA = rb_define_class_under(mPKey, "RSA", cPKey); + + rb_define_singleton_method(cRSA, "generate", ossl_rsa_s_generate, -1); + rb_define_method(cRSA, "initialize", ossl_rsa_initialize, -1); + + rb_define_method(cRSA, "public?", ossl_rsa_is_public, 0); + rb_define_method(cRSA, "private?", ossl_rsa_is_private, 0); + rb_define_method(cRSA, "to_text", ossl_rsa_to_text, 0); + rb_define_method(cRSA, "export", ossl_rsa_export, -1); + rb_define_alias(cRSA, "to_pem", "export"); + rb_define_alias(cRSA, "to_s", "export"); + rb_define_method(cRSA, "to_der", ossl_rsa_to_der, 0); + rb_define_method(cRSA, "public_key", ossl_rsa_to_public_key, 0); + rb_define_method(cRSA, "public_encrypt", ossl_rsa_public_encrypt, -1); + rb_define_method(cRSA, "public_decrypt", ossl_rsa_public_decrypt, -1); + rb_define_method(cRSA, "private_encrypt", ossl_rsa_private_encrypt, -1); + rb_define_method(cRSA, "private_decrypt", ossl_rsa_private_decrypt, -1); + + DEF_OSSL_PKEY_BN(cRSA, rsa, n); + DEF_OSSL_PKEY_BN(cRSA, rsa, e); + DEF_OSSL_PKEY_BN(cRSA, rsa, d); + DEF_OSSL_PKEY_BN(cRSA, rsa, p); + DEF_OSSL_PKEY_BN(cRSA, rsa, q); + DEF_OSSL_PKEY_BN(cRSA, rsa, dmp1); + DEF_OSSL_PKEY_BN(cRSA, rsa, dmq1); + DEF_OSSL_PKEY_BN(cRSA, rsa, iqmp); + + rb_define_method(cRSA, "params", ossl_rsa_get_params, 0); + + DefRSAConst(PKCS1_PADDING); + DefRSAConst(SSLV23_PADDING); + DefRSAConst(NO_PADDING); + DefRSAConst(PKCS1_OAEP_PADDING); + +/* + * TODO: Test it + rb_define_method(cRSA, "blinding_on!", ossl_rsa_blinding_on, 0); + rb_define_method(cRSA, "blinding_off!", ossl_rsa_blinding_off, 0); + */ +} + +#else /* defined NO_RSA */ +void +Init_ossl_rsa(void) +{ +} +#endif /* NO_RSA */ + diff --git a/ext/openssl/ossl_rand.c b/ext/openssl/ossl_rand.c new file mode 100644 index 00000000..29cbf8c3 --- /dev/null +++ b/ext/openssl/ossl_rand.c @@ -0,0 +1,226 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2001-2002 Michal Rokos <m.rokos@sh.cvut.cz> + * + * All rights reserved. + * + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#include "ossl.h" + +VALUE mRandom; +VALUE eRandomError; + +/* + * call-seq: + * seed(str) -> str + * + * ::seed is equivalent to ::add where +entropy+ is length of +str+. + */ +static VALUE +ossl_rand_seed(VALUE self, VALUE str) +{ + StringValue(str); + RAND_seed(RSTRING_PTR(str), RSTRING_LENINT(str)); + + return str; +} + +/* + * call-seq: + * add(str, entropy) -> self + * + * Mixes the bytes from +str+ into the Pseudo Random Number Generator(PRNG) + * state. + * + * Thus, if the data from +str+ are unpredictable to an adversary, this + * increases the uncertainty about the state and makes the PRNG output less + * predictable. + * + * The +entropy+ argument is (the lower bound of) an estimate of how much + * randomness is contained in +str+, measured in bytes. + * + * Example: + * + * pid = $$ + * now = Time.now + * ary = [now.to_i, now.nsec, 1000, pid] + * OpenSSL::Random.add(ary.join("").to_s, 0.0) + * OpenSSL::Random.seed(ary.join("").to_s) + */ +static VALUE +ossl_rand_add(VALUE self, VALUE str, VALUE entropy) +{ + StringValue(str); + RAND_add(RSTRING_PTR(str), RSTRING_LENINT(str), NUM2DBL(entropy)); + + return self; +} + +/* + * call-seq: + * load_random_file(filename) -> true + * + * Reads bytes from +filename+ and adds them to the PRNG. + */ +static VALUE +ossl_rand_load_file(VALUE self, VALUE filename) +{ + SafeStringValue(filename); + + if(!RAND_load_file(RSTRING_PTR(filename), -1)) { + ossl_raise(eRandomError, NULL); + } + return Qtrue; +} + +/* + * call-seq: + * write_random_file(filename) -> true + * + * Writes a number of random generated bytes (currently 1024) to +filename+ + * which can be used to initialize the PRNG by calling ::load_random_file in a + * later session. + */ +static VALUE +ossl_rand_write_file(VALUE self, VALUE filename) +{ + SafeStringValue(filename); + if (RAND_write_file(RSTRING_PTR(filename)) == -1) { + ossl_raise(eRandomError, NULL); + } + return Qtrue; +} + +/* + * call-seq: + * random_bytes(length) -> string + * + * Generates +string+ with +length+ number of cryptographically strong + * pseudo-random bytes. + * + * Example: + * + * OpenSSL::Random.random_bytes(12) + * => "..." + */ +static VALUE +ossl_rand_bytes(VALUE self, VALUE len) +{ + VALUE str; + int n = NUM2INT(len); + + str = rb_str_new(0, n); + if (!RAND_bytes((unsigned char *)RSTRING_PTR(str), n)) { + ossl_raise(eRandomError, NULL); + } + + return str; +} + +/* + * call-seq: + * pseudo_bytes(length) -> string + * + * Generates +string+ with +length+ number of pseudo-random bytes. + * + * Pseudo-random byte sequences generated by ::pseudo_bytes will be unique if + * they are of sufficient length, but are not necessarily unpredictable. + * + * Example: + * + * OpenSSL::Random.pseudo_bytes(12) + * => "..." + */ +static VALUE +ossl_rand_pseudo_bytes(VALUE self, VALUE len) +{ + VALUE str; + int n = NUM2INT(len); + + str = rb_str_new(0, n); + if (!RAND_pseudo_bytes((unsigned char *)RSTRING_PTR(str), n)) { + ossl_raise(eRandomError, NULL); + } + + return str; +} + +/* + * call-seq: + * egd(filename) -> true + * + * Same as ::egd_bytes but queries 255 bytes by default. + */ +static VALUE +ossl_rand_egd(VALUE self, VALUE filename) +{ + SafeStringValue(filename); + + if(!RAND_egd(RSTRING_PTR(filename))) { + ossl_raise(eRandomError, NULL); + } + return Qtrue; +} + +/* + * call-seq: + * egd_bytes(filename, length) -> true + * + * Queries the entropy gathering daemon EGD on socket path given by +filename+. + * + * Fetches +length+ number of bytes and uses ::add to seed the OpenSSL built-in + * PRNG. + */ +static VALUE +ossl_rand_egd_bytes(VALUE self, VALUE filename, VALUE len) +{ + int n = NUM2INT(len); + + SafeStringValue(filename); + + if (!RAND_egd_bytes(RSTRING_PTR(filename), n)) { + ossl_raise(eRandomError, NULL); + } + return Qtrue; +} + +/* + * call-seq: + * status? => true | false + * + * Return true if the PRNG has been seeded with enough data, false otherwise. + */ +static VALUE +ossl_rand_status(VALUE self) +{ + return RAND_status() ? Qtrue : Qfalse; +} + +/* + * INIT + */ +void +Init_ossl_rand(void) +{ +#if 0 + mOSSL = rb_define_module("OpenSSL"); /* let rdoc know about mOSSL */ +#endif + + mRandom = rb_define_module_under(mOSSL, "Random"); + + eRandomError = rb_define_class_under(mRandom, "RandomError", eOSSLError); + + rb_define_module_function(mRandom, "seed", ossl_rand_seed, 1); + rb_define_module_function(mRandom, "random_add", ossl_rand_add, 2); + rb_define_module_function(mRandom, "load_random_file", ossl_rand_load_file, 1); + rb_define_module_function(mRandom, "write_random_file", ossl_rand_write_file, 1); + rb_define_module_function(mRandom, "random_bytes", ossl_rand_bytes, 1); + rb_define_module_function(mRandom, "pseudo_bytes", ossl_rand_pseudo_bytes, 1); + rb_define_module_function(mRandom, "egd", ossl_rand_egd, 1); + rb_define_module_function(mRandom, "egd_bytes", ossl_rand_egd_bytes, 2); + rb_define_module_function(mRandom, "status?", ossl_rand_status, 0); +} + diff --git a/ext/openssl/ossl_rand.h b/ext/openssl/ossl_rand.h new file mode 100644 index 00000000..ce2ae0d1 --- /dev/null +++ b/ext/openssl/ossl_rand.h @@ -0,0 +1,20 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2001-2002 Michal Rokos <m.rokos@sh.cvut.cz> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#if !defined(_OSSL_RAND_H_) +#define _OSSL_RAND_H_ + +extern VALUE mRandom; +extern VALUE eRandomError; + +void Init_ossl_rand(void); + +#endif /* _OSSL_RAND_H_ */ + diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c new file mode 100644 index 00000000..ccfd72dd --- /dev/null +++ b/ext/openssl/ossl_ssl.c @@ -0,0 +1,2281 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2000-2002 GOTOU Yuuzou <gotoyuzo@notwork.org> + * Copyright (C) 2001-2002 Michal Rokos <m.rokos@sh.cvut.cz> + * Copyright (C) 2001-2007 Technorama Ltd. <oss-ruby@technorama.net> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#include "ossl.h" + +#if defined(HAVE_UNISTD_H) +# include <unistd.h> /* for read(), and write() */ +#endif + +#define numberof(ary) (int)(sizeof(ary)/sizeof((ary)[0])) + +#ifdef _WIN32 +# define TO_SOCKET(s) _get_osfhandle(s) +#else +# define TO_SOCKET(s) (s) +#endif + +VALUE mSSL; +VALUE eSSLError; +VALUE cSSLContext; +VALUE cSSLSocket; + +static VALUE eSSLErrorWaitReadable; +static VALUE eSSLErrorWaitWritable; + +#define ossl_sslctx_set_cert(o,v) rb_iv_set((o),"@cert",(v)) +#define ossl_sslctx_set_key(o,v) rb_iv_set((o),"@key",(v)) +#define ossl_sslctx_set_client_ca(o,v) rb_iv_set((o),"@client_ca",(v)) +#define ossl_sslctx_set_ca_file(o,v) rb_iv_set((o),"@ca_file",(v)) +#define ossl_sslctx_set_ca_path(o,v) rb_iv_set((o),"@ca_path",(v)) +#define ossl_sslctx_set_timeout(o,v) rb_iv_set((o),"@timeout",(v)) +#define ossl_sslctx_set_verify_mode(o,v) rb_iv_set((o),"@verify_mode",(v)) +#define ossl_sslctx_set_verify_dep(o,v) rb_iv_set((o),"@verify_depth",(v)) +#define ossl_sslctx_set_verify_cb(o,v) rb_iv_set((o),"@verify_callback",(v)) +#define ossl_sslctx_set_options(o,v) rb_iv_set((o),"@options",(v)) +#define ossl_sslctx_set_cert_store(o,v) rb_iv_set((o),"@cert_store",(v)) +#define ossl_sslctx_set_extra_cert(o,v) rb_iv_set((o),"@extra_chain_cert",(v)) +#define ossl_sslctx_set_client_cert_cb(o,v) rb_iv_set((o),"@client_cert_cb",(v)) +#define ossl_sslctx_set_tmp_dh_cb(o,v) rb_iv_set((o),"@tmp_dh_callback",(v)) +#define ossl_sslctx_set_sess_id_ctx(o, v) rb_iv_set((o),"@session_id_context",(v)) + +#define ossl_sslctx_get_cert(o) rb_iv_get((o),"@cert") +#define ossl_sslctx_get_key(o) rb_iv_get((o),"@key") +#define ossl_sslctx_get_client_ca(o) rb_iv_get((o),"@client_ca") +#define ossl_sslctx_get_ca_file(o) rb_iv_get((o),"@ca_file") +#define ossl_sslctx_get_ca_path(o) rb_iv_get((o),"@ca_path") +#define ossl_sslctx_get_timeout(o) rb_iv_get((o),"@timeout") +#define ossl_sslctx_get_verify_mode(o) rb_iv_get((o),"@verify_mode") +#define ossl_sslctx_get_verify_dep(o) rb_iv_get((o),"@verify_depth") +#define ossl_sslctx_get_verify_cb(o) rb_iv_get((o),"@verify_callback") +#define ossl_sslctx_get_options(o) rb_iv_get((o),"@options") +#define ossl_sslctx_get_cert_store(o) rb_iv_get((o),"@cert_store") +#define ossl_sslctx_get_extra_cert(o) rb_iv_get((o),"@extra_chain_cert") +#define ossl_sslctx_get_client_cert_cb(o) rb_iv_get((o),"@client_cert_cb") +#define ossl_sslctx_get_tmp_dh_cb(o) rb_iv_get((o),"@tmp_dh_callback") +#define ossl_sslctx_get_sess_id_ctx(o) rb_iv_get((o),"@session_id_context") + +static const char *ossl_sslctx_attrs[] = { + "cert", "key", "client_ca", "ca_file", "ca_path", + "timeout", "verify_mode", "verify_depth", "renegotiation_cb", + "verify_callback", "options", "cert_store", "extra_chain_cert", + "client_cert_cb", "tmp_dh_callback", "session_id_context", + "session_get_cb", "session_new_cb", "session_remove_cb", +#ifdef HAVE_SSL_SET_TLSEXT_HOST_NAME + "servername_cb", +#endif +#ifdef HAVE_OPENSSL_NPN_NEGOTIATED + "npn_protocols", + "npn_select_cb", +#endif +}; + +#define ossl_ssl_get_io(o) rb_iv_get((o),"@io") +#define ossl_ssl_get_ctx(o) rb_iv_get((o),"@context") +#define ossl_ssl_get_sync_close(o) rb_iv_get((o),"@sync_close") +#define ossl_ssl_get_x509(o) rb_iv_get((o),"@x509") +#define ossl_ssl_get_key(o) rb_iv_get((o),"@key") +#define ossl_ssl_get_tmp_dh(o) rb_iv_get((o),"@tmp_dh") + +#define ossl_ssl_set_io(o,v) rb_iv_set((o),"@io",(v)) +#define ossl_ssl_set_ctx(o,v) rb_iv_set((o),"@context",(v)) +#define ossl_ssl_set_sync_close(o,v) rb_iv_set((o),"@sync_close",(v)) +#define ossl_ssl_set_x509(o,v) rb_iv_set((o),"@x509",(v)) +#define ossl_ssl_set_key(o,v) rb_iv_set((o),"@key",(v)) +#define ossl_ssl_set_tmp_dh(o,v) rb_iv_set((o),"@tmp_dh",(v)) + +static const char *ossl_ssl_attr_readers[] = { "io", "context", }; +static const char *ossl_ssl_attrs[] = { +#ifdef HAVE_SSL_SET_TLSEXT_HOST_NAME + "hostname", +#endif + "sync_close", +}; + +ID ID_callback_state; + +static VALUE sym_exception; + +/* + * SSLContext class + */ +static const struct { + const char *name; + SSL_METHOD *(*func)(void); +} ossl_ssl_method_tab[] = { +#define OSSL_SSL_METHOD_ENTRY(name) { #name, (SSL_METHOD *(*)(void))name##_method } + OSSL_SSL_METHOD_ENTRY(TLSv1), + OSSL_SSL_METHOD_ENTRY(TLSv1_server), + OSSL_SSL_METHOD_ENTRY(TLSv1_client), +#if defined(HAVE_TLSV1_2_METHOD) && defined(HAVE_TLSV1_2_SERVER_METHOD) && \ + defined(HAVE_TLSV1_2_CLIENT_METHOD) + OSSL_SSL_METHOD_ENTRY(TLSv1_2), + OSSL_SSL_METHOD_ENTRY(TLSv1_2_server), + OSSL_SSL_METHOD_ENTRY(TLSv1_2_client), +#endif +#if defined(HAVE_TLSV1_1_METHOD) && defined(HAVE_TLSV1_1_SERVER_METHOD) && \ + defined(HAVE_TLSV1_1_CLIENT_METHOD) + OSSL_SSL_METHOD_ENTRY(TLSv1_1), + OSSL_SSL_METHOD_ENTRY(TLSv1_1_server), + OSSL_SSL_METHOD_ENTRY(TLSv1_1_client), +#endif +#if defined(HAVE_SSLV2_METHOD) && defined(HAVE_SSLV2_SERVER_METHOD) && \ + defined(HAVE_SSLV2_CLIENT_METHOD) + OSSL_SSL_METHOD_ENTRY(SSLv2), + OSSL_SSL_METHOD_ENTRY(SSLv2_server), + OSSL_SSL_METHOD_ENTRY(SSLv2_client), +#endif + OSSL_SSL_METHOD_ENTRY(SSLv3), + OSSL_SSL_METHOD_ENTRY(SSLv3_server), + OSSL_SSL_METHOD_ENTRY(SSLv3_client), + OSSL_SSL_METHOD_ENTRY(SSLv23), + OSSL_SSL_METHOD_ENTRY(SSLv23_server), + OSSL_SSL_METHOD_ENTRY(SSLv23_client), +#undef OSSL_SSL_METHOD_ENTRY +}; + +int ossl_ssl_ex_vcb_idx; +int ossl_ssl_ex_store_p; +int ossl_ssl_ex_ptr_idx; +int ossl_ssl_ex_client_cert_cb_idx; +int ossl_ssl_ex_tmp_dh_callback_idx; + +static void +ossl_sslctx_free(SSL_CTX *ctx) +{ + if(ctx && SSL_CTX_get_ex_data(ctx, ossl_ssl_ex_store_p)== (void*)1) + ctx->cert_store = NULL; + SSL_CTX_free(ctx); +} + +static VALUE +ossl_sslctx_s_alloc(VALUE klass) +{ + SSL_CTX *ctx; + long mode = SSL_MODE_ENABLE_PARTIAL_WRITE; + +#ifdef SSL_MODE_RELEASE_BUFFERS + mode |= SSL_MODE_RELEASE_BUFFERS; +#endif + + ctx = SSL_CTX_new(SSLv23_method()); + if (!ctx) { + ossl_raise(eSSLError, "SSL_CTX_new"); + } + SSL_CTX_set_mode(ctx, mode); + return Data_Wrap_Struct(klass, 0, ossl_sslctx_free, ctx); +} + +/* + * call-seq: + * ctx.ssl_version = :TLSv1 + * ctx.ssl_version = "SSLv23_client" + * + * You can get a list of valid versions with OpenSSL::SSL::SSLContext::METHODS + */ +static VALUE +ossl_sslctx_set_ssl_version(VALUE self, VALUE ssl_method) +{ + SSL_METHOD *method = NULL; + const char *s; + int i; + + SSL_CTX *ctx; + if (RB_TYPE_P(ssl_method, T_SYMBOL)) + s = rb_id2name(SYM2ID(ssl_method)); + else + s = StringValuePtr(ssl_method); + for (i = 0; i < numberof(ossl_ssl_method_tab); i++) { + if (strcmp(ossl_ssl_method_tab[i].name, s) == 0) { + method = ossl_ssl_method_tab[i].func(); + break; + } + } + if (!method) { + ossl_raise(rb_eArgError, "unknown SSL method `%s'.", s); + } + Data_Get_Struct(self, SSL_CTX, ctx); + if (SSL_CTX_set_ssl_version(ctx, method) != 1) { + ossl_raise(eSSLError, "SSL_CTX_set_ssl_version"); + } + + return ssl_method; +} + +/* + * call-seq: + * SSLContext.new => ctx + * SSLContext.new(:TLSv1) => ctx + * SSLContext.new("SSLv23_client") => ctx + * + * You can get a list of valid methods with OpenSSL::SSL::SSLContext::METHODS + */ +static VALUE +ossl_sslctx_initialize(int argc, VALUE *argv, VALUE self) +{ + VALUE ssl_method; + int i; + + for(i = 0; i < numberof(ossl_sslctx_attrs); i++){ + char buf[32]; + snprintf(buf, sizeof(buf), "@%s", ossl_sslctx_attrs[i]); + rb_iv_set(self, buf, Qnil); + } + if (rb_scan_args(argc, argv, "01", &ssl_method) == 0){ + return self; + } + ossl_sslctx_set_ssl_version(self, ssl_method); + + return self; +} + +static VALUE +ossl_call_client_cert_cb(VALUE obj) +{ + VALUE cb, ary, cert, key; + SSL *ssl; + + Data_Get_Struct(obj, SSL, ssl); + cb = (VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_client_cert_cb_idx); + if (NIL_P(cb)) return Qfalse; + ary = rb_funcall(cb, rb_intern("call"), 1, obj); + Check_Type(ary, T_ARRAY); + GetX509CertPtr(cert = rb_ary_entry(ary, 0)); + GetPKeyPtr(key = rb_ary_entry(ary, 1)); + ossl_ssl_set_x509(obj, cert); + ossl_ssl_set_key(obj, key); + + return Qtrue; +} + +static int +ossl_client_cert_cb(SSL *ssl, X509 **x509, EVP_PKEY **pkey) +{ + VALUE obj, success; + + obj = (VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx); + success = rb_protect((VALUE(*)_((VALUE)))ossl_call_client_cert_cb, + obj, NULL); + if (!RTEST(success)) return 0; + *x509 = DupX509CertPtr(ossl_ssl_get_x509(obj)); + *pkey = DupPKeyPtr(ossl_ssl_get_key(obj)); + + return 1; +} + +#if !defined(OPENSSL_NO_DH) +static VALUE +ossl_call_tmp_dh_callback(VALUE *args) +{ + SSL *ssl; + VALUE cb, dh; + EVP_PKEY *pkey; + + Data_Get_Struct(args[0], SSL, ssl); + cb = (VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_tmp_dh_callback_idx); + if (NIL_P(cb)) return Qfalse; + dh = rb_funcall(cb, rb_intern("call"), 3, args[0], args[1], args[2]); + pkey = GetPKeyPtr(dh); + if (EVP_PKEY_type(pkey->type) != EVP_PKEY_DH) return Qfalse; + ossl_ssl_set_tmp_dh(args[0], dh); + + return Qtrue; +} + +static DH* +ossl_tmp_dh_callback(SSL *ssl, int is_export, int keylength) +{ + VALUE args[3], success; + + args[0] = (VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx); + args[1] = INT2FIX(is_export); + args[2] = INT2FIX(keylength); + success = rb_protect((VALUE(*)_((VALUE)))ossl_call_tmp_dh_callback, + (VALUE)args, NULL); + if (!RTEST(success)) return NULL; + + return GetPKeyPtr(ossl_ssl_get_tmp_dh(args[0]))->pkey.dh; +} + +static DH* +ossl_default_tmp_dh_callback(SSL *ssl, int is_export, int keylength) +{ + rb_warning("using default DH parameters."); + + switch(keylength){ + case 512: + return OSSL_DEFAULT_DH_512; + case 1024: + return OSSL_DEFAULT_DH_1024; + } + return NULL; +} +#endif /* OPENSSL_NO_DH */ + +static int +ossl_ssl_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) +{ + VALUE cb; + SSL *ssl; + + ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); + cb = (VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_vcb_idx); + X509_STORE_CTX_set_ex_data(ctx, ossl_verify_cb_idx, (void*)cb); + return ossl_verify_cb(preverify_ok, ctx); +} + +static VALUE +ossl_call_session_get_cb(VALUE ary) +{ + VALUE ssl_obj, sslctx_obj, cb; + + Check_Type(ary, T_ARRAY); + ssl_obj = rb_ary_entry(ary, 0); + + sslctx_obj = rb_iv_get(ssl_obj, "@context"); + if (NIL_P(sslctx_obj)) return Qnil; + cb = rb_iv_get(sslctx_obj, "@session_get_cb"); + if (NIL_P(cb)) return Qnil; + + return rb_funcall(cb, rb_intern("call"), 1, ary); +} + +/* this method is currently only called for servers (in OpenSSL <= 0.9.8e) */ +static SSL_SESSION * +ossl_sslctx_session_get_cb(SSL *ssl, unsigned char *buf, int len, int *copy) +{ + VALUE ary, ssl_obj, ret_obj; + SSL_SESSION *sess; + void *ptr; + int state = 0; + + OSSL_Debug("SSL SESSION get callback entered"); + if ((ptr = SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx)) == NULL) + return NULL; + ssl_obj = (VALUE)ptr; + ary = rb_ary_new2(2); + rb_ary_push(ary, ssl_obj); + rb_ary_push(ary, rb_str_new((const char *)buf, len)); + + ret_obj = rb_protect((VALUE(*)_((VALUE)))ossl_call_session_get_cb, ary, &state); + if (state) { + rb_ivar_set(ssl_obj, ID_callback_state, INT2NUM(state)); + return NULL; + } + if (!rb_obj_is_instance_of(ret_obj, cSSLSession)) + return NULL; + + SafeGetSSLSession(ret_obj, sess); + *copy = 1; + + return sess; +} + +static VALUE +ossl_call_session_new_cb(VALUE ary) +{ + VALUE ssl_obj, sslctx_obj, cb; + + Check_Type(ary, T_ARRAY); + ssl_obj = rb_ary_entry(ary, 0); + + sslctx_obj = rb_iv_get(ssl_obj, "@context"); + if (NIL_P(sslctx_obj)) return Qnil; + cb = rb_iv_get(sslctx_obj, "@session_new_cb"); + if (NIL_P(cb)) return Qnil; + + return rb_funcall(cb, rb_intern("call"), 1, ary); +} + +/* return 1 normal. return 0 removes the session */ +static int +ossl_sslctx_session_new_cb(SSL *ssl, SSL_SESSION *sess) +{ + VALUE ary, ssl_obj, sess_obj; + void *ptr; + int state = 0; + + OSSL_Debug("SSL SESSION new callback entered"); + + if ((ptr = SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx)) == NULL) + return 1; + ssl_obj = (VALUE)ptr; + sess_obj = rb_obj_alloc(cSSLSession); + CRYPTO_add(&sess->references, 1, CRYPTO_LOCK_SSL_SESSION); + DATA_PTR(sess_obj) = sess; + + ary = rb_ary_new2(2); + rb_ary_push(ary, ssl_obj); + rb_ary_push(ary, sess_obj); + + rb_protect((VALUE(*)_((VALUE)))ossl_call_session_new_cb, ary, &state); + if (state) { + rb_ivar_set(ssl_obj, ID_callback_state, INT2NUM(state)); + } + + /* + * return 0 which means to OpenSSL that the session is still + * valid (since we created Ruby Session object) and was not freed by us + * with SSL_SESSION_free(). Call SSLContext#remove_session(sess) in + * session_get_cb block if you don't want OpenSSL to cache the session + * internally. + */ + return 0; +} + +static VALUE +ossl_call_session_remove_cb(VALUE ary) +{ + VALUE sslctx_obj, cb; + + Check_Type(ary, T_ARRAY); + sslctx_obj = rb_ary_entry(ary, 0); + + cb = rb_iv_get(sslctx_obj, "@session_remove_cb"); + if (NIL_P(cb)) return Qnil; + + return rb_funcall(cb, rb_intern("call"), 1, ary); +} + +static void +ossl_sslctx_session_remove_cb(SSL_CTX *ctx, SSL_SESSION *sess) +{ + VALUE ary, sslctx_obj, sess_obj; + void *ptr; + int state = 0; + + OSSL_Debug("SSL SESSION remove callback entered"); + + if ((ptr = SSL_CTX_get_ex_data(ctx, ossl_ssl_ex_ptr_idx)) == NULL) + return; + sslctx_obj = (VALUE)ptr; + sess_obj = rb_obj_alloc(cSSLSession); + CRYPTO_add(&sess->references, 1, CRYPTO_LOCK_SSL_SESSION); + DATA_PTR(sess_obj) = sess; + + ary = rb_ary_new2(2); + rb_ary_push(ary, sslctx_obj); + rb_ary_push(ary, sess_obj); + + rb_protect((VALUE(*)_((VALUE)))ossl_call_session_remove_cb, ary, &state); + if (state) { +/* + the SSL_CTX is frozen, nowhere to save state. + there is no common accessor method to check it either. + rb_ivar_set(sslctx_obj, ID_callback_state, INT2NUM(state)); +*/ + } +} + +static VALUE +ossl_sslctx_add_extra_chain_cert_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, arg)) +{ + X509 *x509; + SSL_CTX *ctx; + + Data_Get_Struct(arg, SSL_CTX, ctx); + x509 = DupX509CertPtr(i); + if(!SSL_CTX_add_extra_chain_cert(ctx, x509)){ + ossl_raise(eSSLError, NULL); + } + + return i; +} + +static VALUE ossl_sslctx_setup(VALUE self); + +#ifdef HAVE_SSL_SET_TLSEXT_HOST_NAME +static VALUE +ossl_call_servername_cb(VALUE ary) +{ + VALUE ssl_obj, sslctx_obj, cb, ret_obj; + + Check_Type(ary, T_ARRAY); + ssl_obj = rb_ary_entry(ary, 0); + + sslctx_obj = rb_iv_get(ssl_obj, "@context"); + if (NIL_P(sslctx_obj)) return Qnil; + cb = rb_iv_get(sslctx_obj, "@servername_cb"); + if (NIL_P(cb)) return Qnil; + + ret_obj = rb_funcall(cb, rb_intern("call"), 1, ary); + if (rb_obj_is_kind_of(ret_obj, cSSLContext)) { + SSL *ssl; + SSL_CTX *ctx2; + + ossl_sslctx_setup(ret_obj); + Data_Get_Struct(ssl_obj, SSL, ssl); + Data_Get_Struct(ret_obj, SSL_CTX, ctx2); + SSL_set_SSL_CTX(ssl, ctx2); + } else if (!NIL_P(ret_obj)) { + ossl_raise(rb_eArgError, "servername_cb must return an OpenSSL::SSL::SSLContext object or nil"); + } + + return ret_obj; +} + +static int +ssl_servername_cb(SSL *ssl, int *ad, void *arg) +{ + VALUE ary, ssl_obj; + void *ptr; + int state = 0; + const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + + if (!servername) + return SSL_TLSEXT_ERR_OK; + + if ((ptr = SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx)) == NULL) + return SSL_TLSEXT_ERR_ALERT_FATAL; + ssl_obj = (VALUE)ptr; + ary = rb_ary_new2(2); + rb_ary_push(ary, ssl_obj); + rb_ary_push(ary, rb_str_new2(servername)); + + rb_protect((VALUE(*)_((VALUE)))ossl_call_servername_cb, ary, &state); + if (state) { + rb_ivar_set(ssl_obj, ID_callback_state, INT2NUM(state)); + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + + return SSL_TLSEXT_ERR_OK; +} +#endif + +static void +ssl_renegotiation_cb(const SSL *ssl) +{ + VALUE ssl_obj, sslctx_obj, cb; + void *ptr; + + if ((ptr = SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx)) == NULL) + ossl_raise(eSSLError, "SSL object could not be retrieved"); + ssl_obj = (VALUE)ptr; + + sslctx_obj = rb_iv_get(ssl_obj, "@context"); + if (NIL_P(sslctx_obj)) return; + cb = rb_iv_get(sslctx_obj, "@renegotiation_cb"); + if (NIL_P(cb)) return; + + (void) rb_funcall(cb, rb_intern("call"), 1, ssl_obj); +} + +#ifdef HAVE_OPENSSL_NPN_NEGOTIATED +static VALUE +ssl_npn_encode_protocol_i(VALUE cur, VALUE encoded) +{ + int len = RSTRING_LENINT(cur); + char len_byte; + if (len < 1 || len > 255) + ossl_raise(eSSLError, "Advertised protocol must have length 1..255"); + /* Encode the length byte */ + len_byte = len; + rb_str_buf_cat(encoded, &len_byte, 1); + rb_str_buf_cat(encoded, RSTRING_PTR(cur), len); + return Qnil; +} + +static void +ssl_npn_encode_protocols(VALUE sslctx, VALUE protocols) +{ + VALUE encoded = rb_str_new2(""); + rb_iterate(rb_each, protocols, ssl_npn_encode_protocol_i, encoded); + StringValueCStr(encoded); + rb_iv_set(sslctx, "@_protocols", encoded); +} + +static int +ssl_npn_advertise_cb(SSL *ssl, const unsigned char **out, unsigned int *outlen, void *arg) +{ + VALUE sslctx_obj = (VALUE) arg; + VALUE protocols = rb_iv_get(sslctx_obj, "@_protocols"); + + *out = (const unsigned char *) RSTRING_PTR(protocols); + *outlen = RSTRING_LENINT(protocols); + + return SSL_TLSEXT_ERR_OK; +} + +static int +ssl_npn_select_cb(SSL *s, unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg) +{ + int i = 0; + VALUE sslctx_obj, cb, protocols, selected; + + sslctx_obj = (VALUE) arg; + cb = rb_iv_get(sslctx_obj, "@npn_select_cb"); + protocols = rb_ary_new(); + + /* The format is len_1|proto_1|...|len_n|proto_n\0 */ + while (in[i]) { + VALUE protocol = rb_str_new((const char *) &in[i + 1], in[i]); + rb_ary_push(protocols, protocol); + i += in[i] + 1; + } + + selected = rb_funcall(cb, rb_intern("call"), 1, protocols); + StringValue(selected); + *out = (unsigned char *) StringValuePtr(selected); + *outlen = RSTRING_LENINT(selected); + + return SSL_TLSEXT_ERR_OK; +} +#endif + +/* This function may serve as the entry point to support further + * callbacks. */ +static void +ssl_info_cb(const SSL *ssl, int where, int val) +{ + int state = SSL_state(ssl); + + if ((where & SSL_CB_HANDSHAKE_START) && + (state & SSL_ST_ACCEPT)) { + ssl_renegotiation_cb(ssl); + } +} + +/* + * call-seq: + * ctx.setup => Qtrue # first time + * ctx.setup => nil # thereafter + * + * This method is called automatically when a new SSLSocket is created. + * Normally you do not need to call this method (unless you are writing an + * extension in C). + */ +static VALUE +ossl_sslctx_setup(VALUE self) +{ + SSL_CTX *ctx; + X509 *cert = NULL, *client_ca = NULL; + X509_STORE *store; + EVP_PKEY *key = NULL; + char *ca_path = NULL, *ca_file = NULL; + int i, verify_mode; + VALUE val; + + if(OBJ_FROZEN(self)) return Qnil; + Data_Get_Struct(self, SSL_CTX, ctx); + +#if !defined(OPENSSL_NO_DH) + if (RTEST(ossl_sslctx_get_tmp_dh_cb(self))){ + SSL_CTX_set_tmp_dh_callback(ctx, ossl_tmp_dh_callback); + } + else{ + SSL_CTX_set_tmp_dh_callback(ctx, ossl_default_tmp_dh_callback); + } +#endif + SSL_CTX_set_ex_data(ctx, ossl_ssl_ex_ptr_idx, (void*)self); + + val = ossl_sslctx_get_cert_store(self); + if(!NIL_P(val)){ + /* + * WORKAROUND: + * X509_STORE can count references, but + * X509_STORE_free() doesn't care it. + * So we won't increment it but mark it by ex_data. + */ + store = GetX509StorePtr(val); /* NO NEED TO DUP */ + SSL_CTX_set_cert_store(ctx, store); + SSL_CTX_set_ex_data(ctx, ossl_ssl_ex_store_p, (void*)1); + } + + val = ossl_sslctx_get_extra_cert(self); + if(!NIL_P(val)){ + rb_block_call(val, rb_intern("each"), 0, 0, ossl_sslctx_add_extra_chain_cert_i, self); + } + + /* private key may be bundled in certificate file. */ + val = ossl_sslctx_get_cert(self); + cert = NIL_P(val) ? NULL : GetX509CertPtr(val); /* NO DUP NEEDED */ + val = ossl_sslctx_get_key(self); + key = NIL_P(val) ? NULL : GetPKeyPtr(val); /* NO DUP NEEDED */ + if (cert && key) { + if (!SSL_CTX_use_certificate(ctx, cert)) { + /* Adds a ref => Safe to FREE */ + ossl_raise(eSSLError, "SSL_CTX_use_certificate"); + } + if (!SSL_CTX_use_PrivateKey(ctx, key)) { + /* Adds a ref => Safe to FREE */ + ossl_raise(eSSLError, "SSL_CTX_use_PrivateKey"); + } + if (!SSL_CTX_check_private_key(ctx)) { + ossl_raise(eSSLError, "SSL_CTX_check_private_key"); + } + } + + val = ossl_sslctx_get_client_ca(self); + if(!NIL_P(val)){ + if (RB_TYPE_P(val, T_ARRAY)) { + for(i = 0; i < RARRAY_LEN(val); i++){ + client_ca = GetX509CertPtr(RARRAY_PTR(val)[i]); + if (!SSL_CTX_add_client_CA(ctx, client_ca)){ + /* Copies X509_NAME => FREE it. */ + ossl_raise(eSSLError, "SSL_CTX_add_client_CA"); + } + } + } + else{ + client_ca = GetX509CertPtr(val); /* NO DUP NEEDED. */ + if (!SSL_CTX_add_client_CA(ctx, client_ca)){ + /* Copies X509_NAME => FREE it. */ + ossl_raise(eSSLError, "SSL_CTX_add_client_CA"); + } + } + } + + val = ossl_sslctx_get_ca_file(self); + ca_file = NIL_P(val) ? NULL : StringValuePtr(val); + val = ossl_sslctx_get_ca_path(self); + ca_path = NIL_P(val) ? NULL : StringValuePtr(val); + if(ca_file || ca_path){ + if (!SSL_CTX_load_verify_locations(ctx, ca_file, ca_path)) + rb_warning("can't set verify locations"); + } + + val = ossl_sslctx_get_verify_mode(self); + verify_mode = NIL_P(val) ? SSL_VERIFY_NONE : NUM2INT(val); + SSL_CTX_set_verify(ctx, verify_mode, ossl_ssl_verify_callback); + if (RTEST(ossl_sslctx_get_client_cert_cb(self))) + SSL_CTX_set_client_cert_cb(ctx, ossl_client_cert_cb); + + val = ossl_sslctx_get_timeout(self); + if(!NIL_P(val)) SSL_CTX_set_timeout(ctx, NUM2LONG(val)); + + val = ossl_sslctx_get_verify_dep(self); + if(!NIL_P(val)) SSL_CTX_set_verify_depth(ctx, NUM2INT(val)); + + val = ossl_sslctx_get_options(self); + if(!NIL_P(val)) { + SSL_CTX_set_options(ctx, NUM2LONG(val)); + } else { + SSL_CTX_set_options(ctx, SSL_OP_ALL); + } + +#ifdef HAVE_OPENSSL_NPN_NEGOTIATED + val = rb_iv_get(self, "@npn_protocols"); + if (!NIL_P(val)) { + ssl_npn_encode_protocols(self, val); + SSL_CTX_set_next_protos_advertised_cb(ctx, ssl_npn_advertise_cb, (void *) self); + OSSL_Debug("SSL NPN advertise callback added"); + } + if (RTEST(rb_iv_get(self, "@npn_select_cb"))) { + SSL_CTX_set_next_proto_select_cb(ctx, ssl_npn_select_cb, (void *) self); + OSSL_Debug("SSL NPN select callback added"); + } +#endif + + rb_obj_freeze(self); + + val = ossl_sslctx_get_sess_id_ctx(self); + if (!NIL_P(val)){ + StringValue(val); + if (!SSL_CTX_set_session_id_context(ctx, (unsigned char *)RSTRING_PTR(val), + RSTRING_LENINT(val))){ + ossl_raise(eSSLError, "SSL_CTX_set_session_id_context"); + } + } + + if (RTEST(rb_iv_get(self, "@session_get_cb"))) { + SSL_CTX_sess_set_get_cb(ctx, ossl_sslctx_session_get_cb); + OSSL_Debug("SSL SESSION get callback added"); + } + if (RTEST(rb_iv_get(self, "@session_new_cb"))) { + SSL_CTX_sess_set_new_cb(ctx, ossl_sslctx_session_new_cb); + OSSL_Debug("SSL SESSION new callback added"); + } + if (RTEST(rb_iv_get(self, "@session_remove_cb"))) { + SSL_CTX_sess_set_remove_cb(ctx, ossl_sslctx_session_remove_cb); + OSSL_Debug("SSL SESSION remove callback added"); + } + +#ifdef HAVE_SSL_SET_TLSEXT_HOST_NAME + val = rb_iv_get(self, "@servername_cb"); + if (!NIL_P(val)) { + SSL_CTX_set_tlsext_servername_callback(ctx, ssl_servername_cb); + OSSL_Debug("SSL TLSEXT servername callback added"); + } +#endif + + return Qtrue; +} + +static VALUE +ossl_ssl_cipher_to_ary(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; +} + +/* + * call-seq: + * ctx.ciphers => [[name, version, bits, alg_bits], ...] + * + * The list of ciphers configured for this context. + */ +static VALUE +ossl_sslctx_get_ciphers(VALUE self) +{ + SSL_CTX *ctx; + STACK_OF(SSL_CIPHER) *ciphers; + SSL_CIPHER *cipher; + VALUE ary; + int i, num; + + Data_Get_Struct(self, SSL_CTX, ctx); + if(!ctx){ + rb_warning("SSL_CTX is not initialized."); + return Qnil; + } + ciphers = ctx->cipher_list; + + if (!ciphers) + return rb_ary_new(); + + num = sk_SSL_CIPHER_num(ciphers); + ary = rb_ary_new2(num); + for(i = 0; i < num; i++){ + cipher = sk_SSL_CIPHER_value(ciphers, i); + rb_ary_push(ary, ossl_ssl_cipher_to_ary(cipher)); + } + return ary; +} + +/* + * call-seq: + * ctx.ciphers = "cipher1:cipher2:..." + * ctx.ciphers = [name, ...] + * ctx.ciphers = [[name, version, bits, alg_bits], ...] + * + * Sets the list of available ciphers for this context. Note in a server + * context some ciphers require the appropriate certificates. For example, an + * RSA cipher can only be chosen when an RSA certificate is available. + * + * See also OpenSSL::Cipher and OpenSSL::Cipher::ciphers + */ +static VALUE +ossl_sslctx_set_ciphers(VALUE self, VALUE v) +{ + SSL_CTX *ctx; + VALUE str, elem; + int i; + + rb_check_frozen(self); + if (NIL_P(v)) + return v; + else if (RB_TYPE_P(v, T_ARRAY)) { + str = rb_str_new(0, 0); + for (i = 0; i < RARRAY_LEN(v); i++) { + elem = rb_ary_entry(v, i); + if (RB_TYPE_P(elem, T_ARRAY)) elem = rb_ary_entry(elem, 0); + elem = rb_String(elem); + rb_str_append(str, elem); + if (i < RARRAY_LEN(v)-1) rb_str_cat2(str, ":"); + } + } else { + str = v; + StringValue(str); + } + + Data_Get_Struct(self, SSL_CTX, ctx); + if(!ctx){ + ossl_raise(eSSLError, "SSL_CTX is not initialized."); + return Qnil; + } + if (!SSL_CTX_set_cipher_list(ctx, RSTRING_PTR(str))) { + ossl_raise(eSSLError, "SSL_CTX_set_cipher_list"); + } + + return v; +} + +/* + * call-seq: + * ctx.session_add(session) -> true | false + * + * Adds +session+ to the session cache + */ +static VALUE +ossl_sslctx_session_add(VALUE self, VALUE arg) +{ + SSL_CTX *ctx; + SSL_SESSION *sess; + + Data_Get_Struct(self, SSL_CTX, ctx); + SafeGetSSLSession(arg, sess); + + return SSL_CTX_add_session(ctx, sess) == 1 ? Qtrue : Qfalse; +} + +/* + * call-seq: + * ctx.session_remove(session) -> true | false + * + * Removes +session+ from the session cache + */ +static VALUE +ossl_sslctx_session_remove(VALUE self, VALUE arg) +{ + SSL_CTX *ctx; + SSL_SESSION *sess; + + Data_Get_Struct(self, SSL_CTX, ctx); + SafeGetSSLSession(arg, sess); + + return SSL_CTX_remove_session(ctx, sess) == 1 ? Qtrue : Qfalse; +} + +/* + * call-seq: + * ctx.session_cache_mode -> Integer + * + * The current session cache mode. + */ +static VALUE +ossl_sslctx_get_session_cache_mode(VALUE self) +{ + SSL_CTX *ctx; + + Data_Get_Struct(self, SSL_CTX, ctx); + + return LONG2NUM(SSL_CTX_get_session_cache_mode(ctx)); +} + +/* + * call-seq: + * ctx.session_cache_mode=(integer) -> Integer + * + * Sets the SSL session cache mode. Bitwise-or together the desired + * SESSION_CACHE_* constants to set. See SSL_CTX_set_session_cache_mode(3) for + * details. + */ +static VALUE +ossl_sslctx_set_session_cache_mode(VALUE self, VALUE arg) +{ + SSL_CTX *ctx; + + Data_Get_Struct(self, SSL_CTX, ctx); + + SSL_CTX_set_session_cache_mode(ctx, NUM2LONG(arg)); + + return arg; +} + +/* + * call-seq: + * ctx.session_cache_size -> Integer + * + * Returns the current session cache size. Zero is used to represent an + * unlimited cache size. + */ +static VALUE +ossl_sslctx_get_session_cache_size(VALUE self) +{ + SSL_CTX *ctx; + + Data_Get_Struct(self, SSL_CTX, ctx); + + return LONG2NUM(SSL_CTX_sess_get_cache_size(ctx)); +} + +/* + * call-seq: + * ctx.session_cache_size=(integer) -> Integer + * + * Sets the session cache size. Returns the previously valid session cache + * size. Zero is used to represent an unlimited session cache size. + */ +static VALUE +ossl_sslctx_set_session_cache_size(VALUE self, VALUE arg) +{ + SSL_CTX *ctx; + + Data_Get_Struct(self, SSL_CTX, ctx); + + SSL_CTX_sess_set_cache_size(ctx, NUM2LONG(arg)); + + return arg; +} + +/* + * call-seq: + * ctx.session_cache_stats -> Hash + * + * Returns a Hash containing the following keys: + * + * :accept:: Number of started SSL/TLS handshakes in server mode + * :accept_good:: Number of established SSL/TLS sessions in server mode + * :accept_renegotiate:: Number of start renegotiations in server mode + * :cache_full:: Number of sessions that were removed due to cache overflow + * :cache_hits:: Number of successfully reused connections + * :cache_misses:: Number of sessions proposed by clients that were not found + * in the cache + * :cache_num:: Number of sessions in the internal session cache + * :cb_hits:: Number of sessions retrieved from the external cache in server + * mode + * :connect:: Number of started SSL/TLS handshakes in client mode + * :connect_good:: Number of established SSL/TLS sessions in client mode + * :connect_renegotiate:: Number of start renegotiations in client mode + * :timeouts:: Number of sessions proposed by clients that were found in the + * cache but had expired due to timeouts + */ +static VALUE +ossl_sslctx_get_session_cache_stats(VALUE self) +{ + SSL_CTX *ctx; + VALUE hash; + + Data_Get_Struct(self, SSL_CTX, ctx); + + hash = rb_hash_new(); + rb_hash_aset(hash, ID2SYM(rb_intern("cache_num")), LONG2NUM(SSL_CTX_sess_number(ctx))); + rb_hash_aset(hash, ID2SYM(rb_intern("connect")), LONG2NUM(SSL_CTX_sess_connect(ctx))); + rb_hash_aset(hash, ID2SYM(rb_intern("connect_good")), LONG2NUM(SSL_CTX_sess_connect_good(ctx))); + rb_hash_aset(hash, ID2SYM(rb_intern("connect_renegotiate")), LONG2NUM(SSL_CTX_sess_connect_renegotiate(ctx))); + rb_hash_aset(hash, ID2SYM(rb_intern("accept")), LONG2NUM(SSL_CTX_sess_accept(ctx))); + rb_hash_aset(hash, ID2SYM(rb_intern("accept_good")), LONG2NUM(SSL_CTX_sess_accept_good(ctx))); + rb_hash_aset(hash, ID2SYM(rb_intern("accept_renegotiate")), LONG2NUM(SSL_CTX_sess_accept_renegotiate(ctx))); + rb_hash_aset(hash, ID2SYM(rb_intern("cache_hits")), LONG2NUM(SSL_CTX_sess_hits(ctx))); + rb_hash_aset(hash, ID2SYM(rb_intern("cb_hits")), LONG2NUM(SSL_CTX_sess_cb_hits(ctx))); + rb_hash_aset(hash, ID2SYM(rb_intern("cache_misses")), LONG2NUM(SSL_CTX_sess_misses(ctx))); + rb_hash_aset(hash, ID2SYM(rb_intern("cache_full")), LONG2NUM(SSL_CTX_sess_cache_full(ctx))); + rb_hash_aset(hash, ID2SYM(rb_intern("timeouts")), LONG2NUM(SSL_CTX_sess_timeouts(ctx))); + + return hash; +} + + +/* + * call-seq: + * ctx.flush_sessions(time | nil) -> self + * + * Removes sessions in the internal cache that have expired at +time+. + */ +static VALUE +ossl_sslctx_flush_sessions(int argc, VALUE *argv, VALUE self) +{ + VALUE arg1; + SSL_CTX *ctx; + time_t tm = 0; + + rb_scan_args(argc, argv, "01", &arg1); + + Data_Get_Struct(self, SSL_CTX, ctx); + + if (NIL_P(arg1)) { + tm = time(0); + } else if (rb_obj_is_instance_of(arg1, rb_cTime)) { + tm = NUM2LONG(rb_funcall(arg1, rb_intern("to_i"), 0)); + } else { + ossl_raise(rb_eArgError, "arg must be Time or nil"); + } + + SSL_CTX_flush_sessions(ctx, (long)tm); + + return self; +} + +/* + * SSLSocket class + */ +#ifndef OPENSSL_NO_SOCK +static void +ossl_ssl_shutdown(SSL *ssl) +{ + int i, rc; + + if (ssl) { + /* 4 is from SSL_smart_shutdown() of mod_ssl.c (v2.2.19) */ + /* It says max 2x pending + 2x data = 4 */ + for (i = 0; i < 4; ++i) { + /* + * Ignore the case SSL_shutdown returns -1. Empty handshake_func + * must not happen. + */ + if (rc = SSL_shutdown(ssl)) + break; + } + SSL_clear(ssl); + ERR_clear_error(); + } +} + +static void +ossl_ssl_free(SSL *ssl) +{ + SSL_free(ssl); +} + +static VALUE +ossl_ssl_s_alloc(VALUE klass) +{ + return Data_Wrap_Struct(klass, 0, ossl_ssl_free, NULL); +} + +/* + * call-seq: + * SSLSocket.new(io) => aSSLSocket + * SSLSocket.new(io, ctx) => aSSLSocket + * + * Creates a new SSL socket from +io+ which must be a real ruby object (not an + * IO-like object that responds to read/write). + * + * If +ctx+ is provided the SSL Sockets initial params will be taken from + * the context. + * + * The OpenSSL::Buffering module provides additional IO methods. + * + * This method will freeze the SSLContext if one is provided; + * however, session management is still allowed in the frozen SSLContext. + */ +static VALUE +ossl_ssl_initialize(int argc, VALUE *argv, VALUE self) +{ + VALUE io, ctx; + + if (rb_scan_args(argc, argv, "11", &io, &ctx) == 1) { + ctx = rb_funcall(cSSLContext, rb_intern("new"), 0); + } + OSSL_Check_Kind(ctx, cSSLContext); + Check_Type(io, T_FILE); + ossl_ssl_set_io(self, io); + ossl_ssl_set_ctx(self, ctx); + ossl_ssl_set_sync_close(self, Qfalse); + ossl_sslctx_setup(ctx); + + rb_iv_set(self, "@hostname", Qnil); + + rb_call_super(0, 0); + + return self; +} + +static VALUE +ossl_ssl_setup(VALUE self) +{ + VALUE io, v_ctx, cb; + SSL_CTX *ctx; + SSL *ssl; + rb_io_t *fptr; + + Data_Get_Struct(self, SSL, ssl); + if(!ssl){ +#ifdef HAVE_SSL_SET_TLSEXT_HOST_NAME + VALUE hostname = rb_iv_get(self, "@hostname"); +#endif + + v_ctx = ossl_ssl_get_ctx(self); + Data_Get_Struct(v_ctx, SSL_CTX, ctx); + + ssl = SSL_new(ctx); + if (!ssl) { + ossl_raise(eSSLError, "SSL_new"); + } + DATA_PTR(self) = ssl; + +#ifdef HAVE_SSL_SET_TLSEXT_HOST_NAME + if (!NIL_P(hostname)) { + if (SSL_set_tlsext_host_name(ssl, StringValuePtr(hostname)) != 1) + ossl_raise(eSSLError, "SSL_set_tlsext_host_name"); + } +#endif + io = ossl_ssl_get_io(self); + GetOpenFile(io, fptr); + rb_io_check_readable(fptr); + rb_io_check_writable(fptr); + SSL_set_fd(ssl, TO_SOCKET(FPTR_TO_FD(fptr))); + SSL_set_ex_data(ssl, ossl_ssl_ex_ptr_idx, (void*)self); + cb = ossl_sslctx_get_verify_cb(v_ctx); + SSL_set_ex_data(ssl, ossl_ssl_ex_vcb_idx, (void*)cb); + cb = ossl_sslctx_get_client_cert_cb(v_ctx); + SSL_set_ex_data(ssl, ossl_ssl_ex_client_cert_cb_idx, (void*)cb); + cb = ossl_sslctx_get_tmp_dh_cb(v_ctx); + SSL_set_ex_data(ssl, ossl_ssl_ex_tmp_dh_callback_idx, (void*)cb); + SSL_set_info_callback(ssl, ssl_info_cb); + } + + return Qtrue; +} + +#ifdef _WIN32 +#define ssl_get_error(ssl, ret) (errno = rb_w32_map_errno(WSAGetLastError()), SSL_get_error((ssl), (ret))) +#else +#define ssl_get_error(ssl, ret) SSL_get_error((ssl), (ret)) +#endif + +#define ossl_ssl_data_get_struct(v, ssl) \ +do { \ + Data_Get_Struct((v), SSL, (ssl)); \ + if (!(ssl)) { \ + rb_warning("SSL session is not started yet."); \ + return Qnil; \ + } \ +} while (0) + +static void +write_would_block(int nonblock) +{ + if (nonblock) { + VALUE exc = ossl_exc_new(eSSLErrorWaitWritable, "write would block"); + rb_exc_raise(exc); + } +} + +static void +read_would_block(int nonblock) +{ + if (nonblock) { + VALUE exc = ossl_exc_new(eSSLErrorWaitReadable, "read would block"); + rb_exc_raise(exc); + } +} + +static VALUE +ossl_start_ssl(VALUE self, int (*func)(), const char *funcname, int nonblock) +{ + SSL *ssl; + rb_io_t *fptr; + int ret, ret2; + VALUE cb_state; + + rb_ivar_set(self, ID_callback_state, Qnil); + + ossl_ssl_data_get_struct(self, ssl); + + GetOpenFile(ossl_ssl_get_io(self), fptr); + for(;;){ + ret = func(ssl); + + cb_state = rb_ivar_get(self, ID_callback_state); + if (!NIL_P(cb_state)) + rb_jump_tag(NUM2INT(cb_state)); + + if (ret > 0) + break; + + switch((ret2 = ssl_get_error(ssl, ret))){ + case SSL_ERROR_WANT_WRITE: + write_would_block(nonblock); + rb_io_wait_writable(FPTR_TO_FD(fptr)); + continue; + case SSL_ERROR_WANT_READ: + read_would_block(nonblock); + rb_io_wait_readable(FPTR_TO_FD(fptr)); + continue; + case SSL_ERROR_SYSCALL: + if (errno) rb_sys_fail(funcname); + ossl_raise(eSSLError, "%s SYSCALL returned=%d errno=%d state=%s", funcname, ret2, errno, SSL_state_string_long(ssl)); + default: + ossl_raise(eSSLError, "%s returned=%d errno=%d state=%s", funcname, ret2, errno, SSL_state_string_long(ssl)); + } + } + + return self; +} + +/* + * call-seq: + * ssl.connect => self + * + * Initiates an SSL/TLS handshake with a server. The handshake may be started + * after unencrypted data has been sent over the socket. + */ +static VALUE +ossl_ssl_connect(VALUE self) +{ + ossl_ssl_setup(self); + return ossl_start_ssl(self, SSL_connect, "SSL_connect", 0); +} + +/* + * call-seq: + * ssl.connect_nonblock => self + * + * Initiates the SSL/TLS handshake as a client in non-blocking manner. + * + * # emulates blocking connect + * begin + * ssl.connect_nonblock + * rescue IO::WaitReadable + * IO.select([s2]) + * retry + * rescue IO::WaitWritable + * IO.select(nil, [s2]) + * retry + * end + * + */ +static VALUE +ossl_ssl_connect_nonblock(VALUE self) +{ + ossl_ssl_setup(self); + return ossl_start_ssl(self, SSL_connect, "SSL_connect", 1); +} + +/* + * call-seq: + * ssl.accept => self + * + * Waits for a SSL/TLS client to initiate a handshake. The handshake may be + * started after unencrypted data has been sent over the socket. + */ +static VALUE +ossl_ssl_accept(VALUE self) +{ + ossl_ssl_setup(self); + return ossl_start_ssl(self, SSL_accept, "SSL_accept", 0); +} + +/* + * call-seq: + * ssl.accept_nonblock => self + * + * Initiates the SSL/TLS handshake as a server in non-blocking manner. + * + * # emulates blocking accept + * begin + * ssl.accept_nonblock + * rescue IO::WaitReadable + * IO.select([s2]) + * retry + * rescue IO::WaitWritable + * IO.select(nil, [s2]) + * retry + * end + * + */ +static VALUE +ossl_ssl_accept_nonblock(VALUE self) +{ + ossl_ssl_setup(self); + return ossl_start_ssl(self, SSL_accept, "SSL_accept", 1); +} + +static VALUE +ossl_ssl_read_internal(int argc, VALUE *argv, VALUE self, int nonblock) +{ + SSL *ssl; + int ilen, nread = 0; + int no_exception = 0; + VALUE len, str; + rb_io_t *fptr; + VALUE opts = Qnil; + + rb_scan_args(argc, argv, "11:", &len, &str, &opts); + + if (!NIL_P(opts) && Qfalse == rb_hash_aref(opts, sym_exception)) + no_exception = 1; + + ilen = NUM2INT(len); + if(NIL_P(str)) str = rb_str_new(0, ilen); + else{ + StringValue(str); + rb_str_modify(str); + rb_str_resize(str, ilen); + } + if(ilen == 0) return str; + + Data_Get_Struct(self, SSL, ssl); + GetOpenFile(ossl_ssl_get_io(self), fptr); + if (ssl) { + if(!nonblock && SSL_pending(ssl) <= 0) + rb_thread_wait_fd(FPTR_TO_FD(fptr)); + for (;;){ + nread = SSL_read(ssl, RSTRING_PTR(str), RSTRING_LENINT(str)); + switch(ssl_get_error(ssl, nread)){ + case SSL_ERROR_NONE: + goto end; + case SSL_ERROR_ZERO_RETURN: + if (no_exception) { return Qnil; } + rb_eof_error(); + case SSL_ERROR_WANT_WRITE: + if (no_exception) { return ID2SYM(rb_intern("wait_writable")); } + write_would_block(nonblock); + rb_io_wait_writable(FPTR_TO_FD(fptr)); + continue; + case SSL_ERROR_WANT_READ: + if (no_exception) { return ID2SYM(rb_intern("wait_readable")); } + read_would_block(nonblock); + rb_io_wait_readable(FPTR_TO_FD(fptr)); + continue; + case SSL_ERROR_SYSCALL: + if(ERR_peek_error() == 0 && nread == 0) { + if (no_exception) { return Qnil; } + rb_eof_error(); + } + rb_sys_fail(0); + default: + ossl_raise(eSSLError, "SSL_read"); + } + } + } + else { + ID meth = nonblock ? rb_intern("read_nonblock") : rb_intern("sysread"); + rb_warning("SSL session is not started yet."); + if (nonblock) { + return rb_funcall(ossl_ssl_get_io(self), meth, 3, len, str, opts); + } else { + return rb_funcall(ossl_ssl_get_io(self), meth, 2, len, str); + } + } + + end: + rb_str_set_len(str, nread); + OBJ_TAINT(str); + + return str; +} + +/* + * call-seq: + * ssl.sysread(length) => string + * ssl.sysread(length, buffer) => buffer + * + * Reads +length+ bytes from the SSL connection. If a pre-allocated +buffer+ + * is provided the data will be written into it. + */ +static VALUE +ossl_ssl_read(int argc, VALUE *argv, VALUE self) +{ + return ossl_ssl_read_internal(argc, argv, self, 0); +} + +/* + * call-seq: + * ssl.sysread_nonblock(length) => string + * ssl.sysread_nonblock(length, buffer) => buffer + * ssl.sysread_nonblock(length[, buffer [, opts]) => buffer + * + * A non-blocking version of #sysread. Raises an SSLError if reading would + * block. If "exception: false" is passed, this method returns a symbol of + * :wait_readable, :wait_writable, or nil, rather than raising an exception. + * + * Reads +length+ bytes from the SSL connection. If a pre-allocated +buffer+ + * is provided the data will be written into it. + */ +static VALUE +ossl_ssl_read_nonblock(int argc, VALUE *argv, VALUE self) +{ + return ossl_ssl_read_internal(argc, argv, self, 1); +} + +static VALUE +ossl_ssl_write_internal(VALUE self, VALUE str, int nonblock, int no_exception) +{ + SSL *ssl; + int nwrite = 0; + rb_io_t *fptr; + + StringValue(str); + Data_Get_Struct(self, SSL, ssl); + GetOpenFile(ossl_ssl_get_io(self), fptr); + + if (ssl) { + for (;;){ + nwrite = SSL_write(ssl, RSTRING_PTR(str), RSTRING_LENINT(str)); + switch(ssl_get_error(ssl, nwrite)){ + case SSL_ERROR_NONE: + goto end; + case SSL_ERROR_WANT_WRITE: + if (no_exception) { return ID2SYM(rb_intern("wait_writable")); } + write_would_block(nonblock); + rb_io_wait_writable(FPTR_TO_FD(fptr)); + continue; + case SSL_ERROR_WANT_READ: + if (no_exception) { return ID2SYM(rb_intern("wait_readable")); } + read_would_block(nonblock); + rb_io_wait_readable(FPTR_TO_FD(fptr)); + continue; + case SSL_ERROR_SYSCALL: + if (errno) rb_sys_fail(0); + default: + ossl_raise(eSSLError, "SSL_write"); + } + } + } + else { + ID id_syswrite = rb_intern("syswrite"); + rb_warning("SSL session is not started yet."); + return rb_funcall(ossl_ssl_get_io(self), id_syswrite, 1, str); + } + + end: + return INT2NUM(nwrite); +} + +/* + * call-seq: + * ssl.syswrite(string) => Integer + * + * Writes +string+ to the SSL connection. + */ +static VALUE +ossl_ssl_write(VALUE self, VALUE str) +{ + return ossl_ssl_write_internal(self, str, 0, 0); +} + +/* + * call-seq: + * ssl.syswrite_nonblock(string) => Integer + * + * Writes +string+ to the SSL connection in a non-blocking manner. Raises an + * SSLError if writing would block. + */ +static VALUE +ossl_ssl_write_nonblock(int argc, VALUE *argv, VALUE self) +{ + VALUE str; + VALUE opts = Qnil; + int no_exception = 0; + + rb_scan_args(argc, argv, "1:", &str, &opts); + + if (!NIL_P(opts) && Qfalse == rb_hash_aref(opts, sym_exception)) + no_exception = 1; + + return ossl_ssl_write_internal(self, str, 1, no_exception); +} + +/* + * call-seq: + * ssl.sysclose => nil + * + * Shuts down the SSL connection and prepares it for another connection. + */ +static VALUE +ossl_ssl_close(VALUE self) +{ + SSL *ssl; + VALUE io; + + /* ossl_ssl_data_get_struct() is not usable here because it may return + * from this function; */ + + Data_Get_Struct(self, SSL, ssl); + + io = ossl_ssl_get_io(self); + if (!RTEST(rb_funcall(io, rb_intern("closed?"), 0))) { + if (ssl) { + ossl_ssl_shutdown(ssl); + SSL_free(ssl); + } + DATA_PTR(self) = NULL; + if (RTEST(ossl_ssl_get_sync_close(self))) + rb_funcall(io, rb_intern("close"), 0); + } + + return Qnil; +} + +/* + * call-seq: + * ssl.cert => cert or nil + * + * The X509 certificate for this socket endpoint. + */ +static VALUE +ossl_ssl_get_cert(VALUE self) +{ + SSL *ssl; + X509 *cert = NULL; + + ossl_ssl_data_get_struct(self, ssl); + + /* + * Is this OpenSSL bug? Should add a ref? + * TODO: Ask for. + */ + cert = SSL_get_certificate(ssl); /* NO DUPs => DON'T FREE. */ + + if (!cert) { + return Qnil; + } + return ossl_x509_new(cert); +} + +/* + * call-seq: + * ssl.peer_cert => cert or nil + * + * The X509 certificate for this socket's peer. + */ +static VALUE +ossl_ssl_get_peer_cert(VALUE self) +{ + SSL *ssl; + X509 *cert = NULL; + VALUE obj; + + ossl_ssl_data_get_struct(self, ssl); + + cert = SSL_get_peer_certificate(ssl); /* Adds a ref => Safe to FREE. */ + + if (!cert) { + return Qnil; + } + obj = ossl_x509_new(cert); + X509_free(cert); + + return obj; +} + +/* + * call-seq: + * ssl.peer_cert_chain => [cert, ...] or nil + * + * The X509 certificate chain for this socket's peer. + */ +static VALUE +ossl_ssl_get_peer_cert_chain(VALUE self) +{ + SSL *ssl; + STACK_OF(X509) *chain; + X509 *cert; + VALUE ary; + int i, num; + + ossl_ssl_data_get_struct(self, ssl); + + chain = SSL_get_peer_cert_chain(ssl); + if(!chain) return Qnil; + num = sk_X509_num(chain); + ary = rb_ary_new2(num); + for (i = 0; i < num; i++){ + cert = sk_X509_value(chain, i); + rb_ary_push(ary, ossl_x509_new(cert)); + } + + return ary; +} + +/* +* call-seq: +* ssl.ssl_version => String +* +* Returns a String representing the SSL/TLS version that was negotiated +* for the connection, for example "TLSv1.2". +*/ +static VALUE +ossl_ssl_get_version(VALUE self) +{ + SSL *ssl; + + ossl_ssl_data_get_struct(self, ssl); + + return rb_str_new2(SSL_get_version(ssl)); +} + +/* +* call-seq: +* ssl.cipher => [name, version, bits, alg_bits] +* +* The cipher being used for the current connection +*/ +static VALUE +ossl_ssl_get_cipher(VALUE self) +{ + SSL *ssl; + SSL_CIPHER *cipher; + + ossl_ssl_data_get_struct(self, ssl); + + cipher = (SSL_CIPHER *)SSL_get_current_cipher(ssl); + + return ossl_ssl_cipher_to_ary(cipher); +} + +/* + * call-seq: + * ssl.state => string + * + * A description of the current connection state. + */ +static VALUE +ossl_ssl_get_state(VALUE self) +{ + SSL *ssl; + VALUE ret; + + ossl_ssl_data_get_struct(self, ssl); + + ret = rb_str_new2(SSL_state_string(ssl)); + if (ruby_verbose) { + rb_str_cat2(ret, ": "); + rb_str_cat2(ret, SSL_state_string_long(ssl)); + } + return ret; +} + +/* + * call-seq: + * ssl.pending => Integer + * + * The number of bytes that are immediately available for reading + */ +static VALUE +ossl_ssl_pending(VALUE self) +{ + SSL *ssl; + + ossl_ssl_data_get_struct(self, ssl); + + return INT2NUM(SSL_pending(ssl)); +} + +/* + * call-seq: + * ssl.session_reused? -> true | false + * + * Returns true if a reused session was negotiated during the handshake. + */ +static VALUE +ossl_ssl_session_reused(VALUE self) +{ + SSL *ssl; + + ossl_ssl_data_get_struct(self, ssl); + + switch(SSL_session_reused(ssl)) { + case 1: return Qtrue; + case 0: return Qfalse; + default: ossl_raise(eSSLError, "SSL_session_reused"); + } + + UNREACHABLE; +} + +/* + * call-seq: + * ssl.session = session -> session + * + * Sets the Session to be used when the connection is established. + */ +static VALUE +ossl_ssl_set_session(VALUE self, VALUE arg1) +{ + SSL *ssl; + SSL_SESSION *sess; + +/* why is ossl_ssl_setup delayed? */ + ossl_ssl_setup(self); + + ossl_ssl_data_get_struct(self, ssl); + + SafeGetSSLSession(arg1, sess); + + if (SSL_set_session(ssl, sess) != 1) + ossl_raise(eSSLError, "SSL_set_session"); + + return arg1; +} + +/* + * call-seq: + * ssl.verify_result => Integer + * + * Returns the result of the peer certificates verification. See verify(1) + * for error values and descriptions. + * + * If no peer certificate was presented X509_V_OK is returned. + */ +static VALUE +ossl_ssl_get_verify_result(VALUE self) +{ + SSL *ssl; + + ossl_ssl_data_get_struct(self, ssl); + + return INT2FIX(SSL_get_verify_result(ssl)); +} + +/* + * call-seq: + * ssl.client_ca => [x509name, ...] + * + * Returns the list of client CAs. Please note that in contrast to + * SSLContext#client_ca= no array of X509::Certificate is returned but + * X509::Name instances of the CA's subject distinguished name. + * + * In server mode, returns the list set by SSLContext#client_ca=. + * In client mode, returns the list of client CAs sent from the server. + */ +static VALUE +ossl_ssl_get_client_ca_list(VALUE self) +{ + SSL *ssl; + STACK_OF(X509_NAME) *ca; + + ossl_ssl_data_get_struct(self, ssl); + + ca = SSL_get_client_CA_list(ssl); + return ossl_x509name_sk2ary(ca); +} + +# ifdef HAVE_OPENSSL_NPN_NEGOTIATED +/* + * call-seq: + * ssl.npn_protocol => String + * + * Returns the protocol string that was finally selected by the client + * during the handshake. + */ +static VALUE +ossl_ssl_npn_protocol(VALUE self) +{ + SSL *ssl; + const unsigned char *out; + unsigned int outlen; + + ossl_ssl_data_get_struct(self, ssl); + + SSL_get0_next_proto_negotiated(ssl, &out, &outlen); + if (!outlen) + return Qnil; + else + return rb_str_new((const char *) out, outlen); +} +# endif +#endif /* !defined(OPENSSL_NO_SOCK) */ + +void +Init_ossl_ssl(void) +{ + int i; + VALUE ary; + +#if 0 + mOSSL = rb_define_module("OpenSSL"); /* let rdoc know about mOSSL */ +#endif + + ID_callback_state = rb_intern("@callback_state"); + + ossl_ssl_ex_vcb_idx = SSL_get_ex_new_index(0,(void *)"ossl_ssl_ex_vcb_idx",0,0,0); + ossl_ssl_ex_store_p = SSL_get_ex_new_index(0,(void *)"ossl_ssl_ex_store_p",0,0,0); + ossl_ssl_ex_ptr_idx = SSL_get_ex_new_index(0,(void *)"ossl_ssl_ex_ptr_idx",0,0,0); + ossl_ssl_ex_client_cert_cb_idx = + SSL_get_ex_new_index(0,(void *)"ossl_ssl_ex_client_cert_cb_idx",0,0,0); + ossl_ssl_ex_tmp_dh_callback_idx = + SSL_get_ex_new_index(0,(void *)"ossl_ssl_ex_tmp_dh_callback_idx",0,0,0); + + /* Document-module: OpenSSL::SSL + * + * Use SSLContext to set up the parameters for a TLS (former SSL) + * connection. Both client and server TLS connections are supported, + * SSLSocket and SSLServer may be used in conjunction with an instance + * of SSLContext to set up connections. + */ + mSSL = rb_define_module_under(mOSSL, "SSL"); + /* Document-class: OpenSSL::SSL::SSLError + * + * Generic error class raised by SSLSocket and SSLContext. + */ + eSSLError = rb_define_class_under(mSSL, "SSLError", eOSSLError); + eSSLErrorWaitReadable = rb_define_class_under(mSSL, "SSLErrorWaitReadable", eSSLError); + rb_include_module(eSSLErrorWaitReadable, rb_mWaitReadable); + eSSLErrorWaitWritable = rb_define_class_under(mSSL, "SSLErrorWaitWritable", eSSLError); + rb_include_module(eSSLErrorWaitWritable, rb_mWaitWritable); + + Init_ossl_ssl_session(); + + /* Document-class: OpenSSL::SSL::SSLContext + * + * An SSLContext is used to set various options regarding certificates, + * algorithms, verification, session caching, etc. The SSLContext is + * used to create an SSLSocket. + * + * All attributes must be set before creating an SSLSocket as the + * SSLContext will be frozen afterward. + * + * The following attributes are available but don't show up in rdoc: + * * ssl_version, cert, key, client_ca, ca_file, ca_path, timeout, + * * verify_mode, verify_depth client_cert_cb, tmp_dh_callback, + * * session_id_context, session_add_cb, session_new_cb, session_remove_cb + */ + cSSLContext = rb_define_class_under(mSSL, "SSLContext", rb_cObject); + rb_define_alloc_func(cSSLContext, ossl_sslctx_s_alloc); + + /* + * Context certificate + */ + rb_attr(cSSLContext, rb_intern("cert"), 1, 1, Qfalse); + + /* + * Context private key + */ + rb_attr(cSSLContext, rb_intern("key"), 1, 1, Qfalse); + + /* + * A certificate or Array of certificates that will be sent to the client. + */ + rb_attr(cSSLContext, rb_intern("client_ca"), 1, 1, Qfalse); + + /* + * The path to a file containing a PEM-format CA certificate + */ + rb_attr(cSSLContext, rb_intern("ca_file"), 1, 1, Qfalse); + + /* + * The path to a directory containing CA certificates in PEM format. + * + * Files are looked up by subject's X509 name's hash value. + */ + rb_attr(cSSLContext, rb_intern("ca_path"), 1, 1, Qfalse); + + /* + * Maximum session lifetime. + */ + rb_attr(cSSLContext, rb_intern("timeout"), 1, 1, Qfalse); + + /* + * Session verification mode. + * + * Valid modes are VERIFY_NONE, VERIFY_PEER, VERIFY_CLIENT_ONCE, + * VERIFY_FAIL_IF_NO_PEER_CERT and defined on OpenSSL::SSL + */ + rb_attr(cSSLContext, rb_intern("verify_mode"), 1, 1, Qfalse); + + /* + * Number of CA certificates to walk when verifying a certificate chain. + */ + rb_attr(cSSLContext, rb_intern("verify_depth"), 1, 1, Qfalse); + + /* + * A callback for additional certificate verification. The callback is + * invoked for each certificate in the chain. + * + * The callback is invoked with two values. +preverify_ok+ indicates + * indicates if the verification was passed (true) or not (false). + * +store_context+ is an OpenSSL::X509::StoreContext containing the + * context used for certificate verification. + * + * If the callback returns false verification is stopped. + */ + rb_attr(cSSLContext, rb_intern("verify_callback"), 1, 1, Qfalse); + + /* + * Sets various OpenSSL options. + */ + rb_attr(cSSLContext, rb_intern("options"), 1, 1, Qfalse); + + /* + * An OpenSSL::X509::Store used for certificate verification + */ + rb_attr(cSSLContext, rb_intern("cert_store"), 1, 1, Qfalse); + + /* + * An Array of extra X509 certificates to be added to the certificate + * chain. + */ + rb_attr(cSSLContext, rb_intern("extra_chain_cert"), 1, 1, Qfalse); + + /* + * A callback invoked when a client certificate is requested by a server + * and no certificate has been set. + * + * The callback is invoked with a Session and must return an Array + * containing an OpenSSL::X509::Certificate and an OpenSSL::PKey. If any + * other value is returned the handshake is suspended. + */ + rb_attr(cSSLContext, rb_intern("client_cert_cb"), 1, 1, Qfalse); + + /* + * A callback invoked when DH parameters are required. + * + * The callback is invoked with the Session for the key exchange, an + * flag indicating the use of an export cipher and the keylength + * required. + * + * The callback must return an OpenSSL::PKey::DH instance of the correct + * key length. + */ + rb_attr(cSSLContext, rb_intern("tmp_dh_callback"), 1, 1, Qfalse); + + /* + * Sets the context in which a session can be reused. This allows + * sessions for multiple applications to be distinguished, for example, by + * name. + */ + rb_attr(cSSLContext, rb_intern("session_id_context"), 1, 1, Qfalse); + + /* + * A callback invoked on a server when a session is proposed by the client + * but the session could not be found in the server's internal cache. + * + * The callback is invoked with the SSLSocket and session id. The + * callback may return a Session from an external cache. + */ + rb_attr(cSSLContext, rb_intern("session_get_cb"), 1, 1, Qfalse); + + /* + * A callback invoked when a new session was negotiated. + * + * The callback is invoked with an SSLSocket. If false is returned the + * session will be removed from the internal cache. + */ + rb_attr(cSSLContext, rb_intern("session_new_cb"), 1, 1, Qfalse); + + /* + * A callback invoked when a session is removed from the internal cache. + * + * The callback is invoked with an SSLContext and a Session. + */ + rb_attr(cSSLContext, rb_intern("session_remove_cb"), 1, 1, Qfalse); + +#ifdef HAVE_SSL_SET_TLSEXT_HOST_NAME + /* + * A callback invoked at connect time to distinguish between multiple + * server names. + * + * The callback is invoked with an SSLSocket and a server name. The + * callback must return an SSLContext for the server name or nil. + */ + rb_attr(cSSLContext, rb_intern("servername_cb"), 1, 1, Qfalse); +#endif + /* + * A callback invoked whenever a new handshake is initiated. May be used + * to disable renegotiation entirely. + * + * The callback is invoked with the active SSLSocket. The callback's + * return value is irrelevant, normal return indicates "approval" of the + * renegotiation and will continue the process. To forbid renegotiation + * and to cancel the process, an Error may be raised within the callback. + * + * === Disable client renegotiation + * + * When running a server, it is often desirable to disable client + * renegotiation entirely. You may use a callback as follows to implement + * this feature: + * + * num_handshakes = 0 + * ctx.renegotiation_cb = lambda do |ssl| + * num_handshakes += 1 + * raise RuntimeError.new("Client renegotiation disabled") if num_handshakes > 1 + * end + */ + rb_attr(cSSLContext, rb_intern("renegotiation_cb"), 1, 1, Qfalse); +#ifdef HAVE_OPENSSL_NPN_NEGOTIATED + /* + * An Enumerable of Strings. Each String represents a protocol to be + * advertised as the list of supported protocols for Next Protocol + * Negotiation. Supported in OpenSSL 1.0.1 and higher. Has no effect + * on the client side. If not set explicitly, the NPN extension will + * not be sent by the server in the handshake. + * + * === Example + * + * ctx.npn_protocols = ["http/1.1", "spdy/2"] + */ + rb_attr(cSSLContext, rb_intern("npn_protocols"), 1, 1, Qfalse); + /* + * A callback invoked on the client side when the client needs to select + * a protocol from the list sent by the server. Supported in OpenSSL 1.0.1 + * and higher. The client MUST select a protocol of those advertised by + * the server. If none is acceptable, raising an error in the callback + * will cause the handshake to fail. Not setting this callback explicitly + * means not supporting the NPN extension on the client - any protocols + * advertised by the server will be ignored. + * + * === Example + * + * ctx.npn_select_cb = lambda do |protocols| + * #inspect the protocols and select one + * protocols.first + * end + */ + rb_attr(cSSLContext, rb_intern("npn_select_cb"), 1, 1, Qfalse); +#endif + + rb_define_alias(cSSLContext, "ssl_timeout", "timeout"); + rb_define_alias(cSSLContext, "ssl_timeout=", "timeout="); + rb_define_method(cSSLContext, "initialize", ossl_sslctx_initialize, -1); + rb_define_method(cSSLContext, "ssl_version=", ossl_sslctx_set_ssl_version, 1); + rb_define_method(cSSLContext, "ciphers", ossl_sslctx_get_ciphers, 0); + rb_define_method(cSSLContext, "ciphers=", ossl_sslctx_set_ciphers, 1); + + rb_define_method(cSSLContext, "setup", ossl_sslctx_setup, 0); + + /* + * No session caching for client or server + */ + rb_define_const(cSSLContext, "SESSION_CACHE_OFF", LONG2FIX(SSL_SESS_CACHE_OFF)); + + /* + * Client sessions are added to the session cache + */ + rb_define_const(cSSLContext, "SESSION_CACHE_CLIENT", LONG2FIX(SSL_SESS_CACHE_CLIENT)); /* doesn't actually do anything in 0.9.8e */ + + /* + * Server sessions are added to the session cache + */ + rb_define_const(cSSLContext, "SESSION_CACHE_SERVER", LONG2FIX(SSL_SESS_CACHE_SERVER)); + + /* + * Both client and server sessions are added to the session cache + */ + rb_define_const(cSSLContext, "SESSION_CACHE_BOTH", LONG2FIX(SSL_SESS_CACHE_BOTH)); /* no different than CACHE_SERVER in 0.9.8e */ + + /* + * Normally the session cache is checked for expired sessions every 255 + * connections. Since this may lead to a delay that cannot be controlled, + * the automatic flushing may be disabled and #flush_sessions can be + * called explicitly. + */ + rb_define_const(cSSLContext, "SESSION_CACHE_NO_AUTO_CLEAR", LONG2FIX(SSL_SESS_CACHE_NO_AUTO_CLEAR)); + + /* + * Always perform external lookups of sessions even if they are in the + * internal cache. + * + * This flag has no effect on clients + */ + rb_define_const(cSSLContext, "SESSION_CACHE_NO_INTERNAL_LOOKUP", LONG2FIX(SSL_SESS_CACHE_NO_INTERNAL_LOOKUP)); + + /* + * Never automatically store sessions in the internal store. + */ + rb_define_const(cSSLContext, "SESSION_CACHE_NO_INTERNAL_STORE", LONG2FIX(SSL_SESS_CACHE_NO_INTERNAL_STORE)); + + /* + * Enables both SESSION_CACHE_NO_INTERNAL_LOOKUP and + * SESSION_CACHE_NO_INTERNAL_STORE. + */ + rb_define_const(cSSLContext, "SESSION_CACHE_NO_INTERNAL", LONG2FIX(SSL_SESS_CACHE_NO_INTERNAL)); + + rb_define_method(cSSLContext, "session_add", ossl_sslctx_session_add, 1); + rb_define_method(cSSLContext, "session_remove", ossl_sslctx_session_remove, 1); + rb_define_method(cSSLContext, "session_cache_mode", ossl_sslctx_get_session_cache_mode, 0); + rb_define_method(cSSLContext, "session_cache_mode=", ossl_sslctx_set_session_cache_mode, 1); + rb_define_method(cSSLContext, "session_cache_size", ossl_sslctx_get_session_cache_size, 0); + rb_define_method(cSSLContext, "session_cache_size=", ossl_sslctx_set_session_cache_size, 1); + rb_define_method(cSSLContext, "session_cache_stats", ossl_sslctx_get_session_cache_stats, 0); + rb_define_method(cSSLContext, "flush_sessions", ossl_sslctx_flush_sessions, -1); + + ary = rb_ary_new2(numberof(ossl_ssl_method_tab)); + for (i = 0; i < numberof(ossl_ssl_method_tab); i++) { + rb_ary_push(ary, ID2SYM(rb_intern(ossl_ssl_method_tab[i].name))); + } + rb_obj_freeze(ary); + /* The list of available SSL/TLS methods */ + rb_define_const(cSSLContext, "METHODS", ary); + + /* + * Document-class: OpenSSL::SSL::SSLSocket + * + * The following attributes are available but don't show up in rdoc. + * * io, context, sync_close + * + */ + cSSLSocket = rb_define_class_under(mSSL, "SSLSocket", rb_cObject); +#ifdef OPENSSL_NO_SOCK + rb_define_method(cSSLSocket, "initialize", rb_notimplement, -1); +#else + rb_define_alloc_func(cSSLSocket, ossl_ssl_s_alloc); + for(i = 0; i < numberof(ossl_ssl_attr_readers); i++) + rb_attr(cSSLSocket, rb_intern(ossl_ssl_attr_readers[i]), 1, 0, Qfalse); + for(i = 0; i < numberof(ossl_ssl_attrs); i++) + rb_attr(cSSLSocket, rb_intern(ossl_ssl_attrs[i]), 1, 1, Qfalse); + rb_define_alias(cSSLSocket, "to_io", "io"); + rb_define_method(cSSLSocket, "initialize", ossl_ssl_initialize, -1); + rb_define_method(cSSLSocket, "connect", ossl_ssl_connect, 0); + rb_define_method(cSSLSocket, "connect_nonblock", ossl_ssl_connect_nonblock, 0); + rb_define_method(cSSLSocket, "accept", ossl_ssl_accept, 0); + rb_define_method(cSSLSocket, "accept_nonblock", ossl_ssl_accept_nonblock, 0); + rb_define_method(cSSLSocket, "sysread", ossl_ssl_read, -1); + rb_define_private_method(cSSLSocket, "sysread_nonblock", ossl_ssl_read_nonblock, -1); + rb_define_method(cSSLSocket, "syswrite", ossl_ssl_write, 1); + rb_define_private_method(cSSLSocket, "syswrite_nonblock", ossl_ssl_write_nonblock, -1); + rb_define_method(cSSLSocket, "sysclose", ossl_ssl_close, 0); + rb_define_method(cSSLSocket, "cert", ossl_ssl_get_cert, 0); + rb_define_method(cSSLSocket, "peer_cert", ossl_ssl_get_peer_cert, 0); + rb_define_method(cSSLSocket, "peer_cert_chain", ossl_ssl_get_peer_cert_chain, 0); + rb_define_method(cSSLSocket, "ssl_version", ossl_ssl_get_version, 0); + rb_define_method(cSSLSocket, "cipher", ossl_ssl_get_cipher, 0); + rb_define_method(cSSLSocket, "state", ossl_ssl_get_state, 0); + rb_define_method(cSSLSocket, "pending", ossl_ssl_pending, 0); + rb_define_method(cSSLSocket, "session_reused?", ossl_ssl_session_reused, 0); + /* implementation of OpenSSL::SSL::SSLSocket#session is in lib/openssl/ssl.rb */ + rb_define_method(cSSLSocket, "session=", ossl_ssl_set_session, 1); + rb_define_method(cSSLSocket, "verify_result", ossl_ssl_get_verify_result, 0); + rb_define_method(cSSLSocket, "client_ca", ossl_ssl_get_client_ca_list, 0); +# ifdef HAVE_OPENSSL_NPN_NEGOTIATED + rb_define_method(cSSLSocket, "npn_protocol", ossl_ssl_npn_protocol, 0); +# endif +#endif + +#define ossl_ssl_def_const(x) rb_define_const(mSSL, #x, INT2NUM(SSL_##x)) + + ossl_ssl_def_const(VERIFY_NONE); + ossl_ssl_def_const(VERIFY_PEER); + ossl_ssl_def_const(VERIFY_FAIL_IF_NO_PEER_CERT); + ossl_ssl_def_const(VERIFY_CLIENT_ONCE); + /* Introduce constants included in OP_ALL. These constants are mostly for + * unset some bits in OP_ALL such as; + * ctx.options = OP_ALL & ~OP_DONT_INSERT_EMPTY_FRAGMENTS + */ + ossl_ssl_def_const(OP_MICROSOFT_SESS_ID_BUG); + ossl_ssl_def_const(OP_NETSCAPE_CHALLENGE_BUG); + ossl_ssl_def_const(OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG); + ossl_ssl_def_const(OP_SSLREF2_REUSE_CERT_TYPE_BUG); + ossl_ssl_def_const(OP_MICROSOFT_BIG_SSLV3_BUFFER); +#if defined(SSL_OP_MSIE_SSLV2_RSA_PADDING) + ossl_ssl_def_const(OP_MSIE_SSLV2_RSA_PADDING); +#endif + ossl_ssl_def_const(OP_SSLEAY_080_CLIENT_DH_BUG); + ossl_ssl_def_const(OP_TLS_D5_BUG); + ossl_ssl_def_const(OP_TLS_BLOCK_PADDING_BUG); + ossl_ssl_def_const(OP_DONT_INSERT_EMPTY_FRAGMENTS); + ossl_ssl_def_const(OP_ALL); +#if defined(SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION) + ossl_ssl_def_const(OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); +#endif +#if defined(SSL_OP_SINGLE_ECDH_USE) + ossl_ssl_def_const(OP_SINGLE_ECDH_USE); +#endif + ossl_ssl_def_const(OP_SINGLE_DH_USE); + ossl_ssl_def_const(OP_EPHEMERAL_RSA); +#if defined(SSL_OP_CIPHER_SERVER_PREFERENCE) + ossl_ssl_def_const(OP_CIPHER_SERVER_PREFERENCE); +#endif + ossl_ssl_def_const(OP_TLS_ROLLBACK_BUG); + ossl_ssl_def_const(OP_NO_SSLv2); + ossl_ssl_def_const(OP_NO_SSLv3); + ossl_ssl_def_const(OP_NO_TLSv1); +#if defined(SSL_OP_NO_TLSv1_1) + ossl_ssl_def_const(OP_NO_TLSv1_1); +#endif +#if defined(SSL_OP_NO_TLSv1_2) + ossl_ssl_def_const(OP_NO_TLSv1_2); +#endif +#if defined(SSL_OP_NO_TICKET) + ossl_ssl_def_const(OP_NO_TICKET); +#endif +#if defined(SSL_OP_NO_COMPRESSION) + ossl_ssl_def_const(OP_NO_COMPRESSION); +#endif + ossl_ssl_def_const(OP_PKCS1_CHECK_1); + ossl_ssl_def_const(OP_PKCS1_CHECK_2); + ossl_ssl_def_const(OP_NETSCAPE_CA_DN_BUG); + ossl_ssl_def_const(OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG); + + sym_exception = ID2SYM(rb_intern("exception")); +} diff --git a/ext/openssl/ossl_ssl.h b/ext/openssl/ossl_ssl.h new file mode 100644 index 00000000..034762fc --- /dev/null +++ b/ext/openssl/ossl_ssl.h @@ -0,0 +1,36 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2001-2002 Michal Rokos <m.rokos@sh.cvut.cz> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#if !defined(_OSSL_SSL_H_) +#define _OSSL_SSL_H_ + +#define GetSSLSession(obj, sess) do { \ + Data_Get_Struct((obj), SSL_SESSION, (sess)); \ + if (!(sess)) { \ + ossl_raise(rb_eRuntimeError, "SSL Session wasn't initialized."); \ + } \ +} while (0) + +#define SafeGetSSLSession(obj, sess) do { \ + OSSL_Check_Kind((obj), cSSLSession); \ + GetSSLSession((obj), (sess)); \ +} while (0) + +extern VALUE mSSL; +extern VALUE eSSLError; +extern VALUE cSSLSocket; +extern VALUE cSSLContext; +extern VALUE cSSLSession; + +void Init_ossl_ssl(void); +void Init_ossl_ssl_session(void); + +#endif /* _OSSL_SSL_H_ */ + diff --git a/ext/openssl/ossl_ssl_session.c b/ext/openssl/ossl_ssl_session.c new file mode 100644 index 00000000..a7437caf --- /dev/null +++ b/ext/openssl/ossl_ssl_session.c @@ -0,0 +1,323 @@ +/* + * Copyright (C) 2004-2007 Technorama Ltd. <oss-ruby@technorama.net> + */ + +#include "ossl.h" + +#define GetSSLSession(obj, sess) do { \ + Data_Get_Struct((obj), SSL_SESSION, (sess)); \ + if (!(sess)) { \ + ossl_raise(rb_eRuntimeError, "SSL Session wasn't initialized."); \ + } \ +} while (0) + +#define SafeGetSSLSession(obj, sess) do { \ + OSSL_Check_Kind((obj), cSSLSession); \ + GetSSLSession((obj), (sess)); \ +} while (0) + + +VALUE cSSLSession; +static VALUE eSSLSession; + +static VALUE ossl_ssl_session_alloc(VALUE klass) +{ + return Data_Wrap_Struct(klass, 0, SSL_SESSION_free, NULL); +} + +/* + * call-seq: + * Session.new(SSLSocket | string) => session + * + * === Parameters + * +SSLSocket+ is an OpenSSL::SSL::SSLSocket + * +string+ must be a DER or PEM encoded Session. +*/ +static VALUE ossl_ssl_session_initialize(VALUE self, VALUE arg1) +{ + SSL_SESSION *ctx = NULL; + + if (RDATA(self)->data) + ossl_raise(eSSLSession, "SSL Session already initialized"); + + if (rb_obj_is_instance_of(arg1, cSSLSocket)) { + SSL *ssl; + + Data_Get_Struct(arg1, SSL, ssl); + + if (!ssl || (ctx = SSL_get1_session(ssl)) == NULL) + ossl_raise(eSSLSession, "no session available"); + } else { + BIO *in = ossl_obj2bio(arg1); + + ctx = PEM_read_bio_SSL_SESSION(in, NULL, NULL, NULL); + + if (!ctx) { + OSSL_BIO_reset(in); + ctx = d2i_SSL_SESSION_bio(in, NULL); + } + + BIO_free(in); + + if (!ctx) + ossl_raise(rb_eArgError, "unknown type"); + } + + /* should not happen */ + if (ctx == NULL) + ossl_raise(eSSLSession, "ctx not set - internal error"); + + RDATA(self)->data = ctx; + + return self; +} + +#if HAVE_SSL_SESSION_CMP == 0 +int SSL_SESSION_cmp(const SSL_SESSION *a,const SSL_SESSION *b) +{ + if (a->ssl_version != b->ssl_version || + a->session_id_length != b->session_id_length) + return 1; + return memcmp(a->session_id,b-> session_id, a->session_id_length); +} +#endif + +/* + * call-seq: + * session1 == session2 -> boolean + * +*/ +static VALUE ossl_ssl_session_eq(VALUE val1, VALUE val2) +{ + SSL_SESSION *ctx1, *ctx2; + + GetSSLSession(val1, ctx1); + SafeGetSSLSession(val2, ctx2); + + switch (SSL_SESSION_cmp(ctx1, ctx2)) { + case 0: return Qtrue; + default: return Qfalse; + } +} + +/* + * call-seq: + * session.time -> Time + * + * Gets start time of the session. + * +*/ +static VALUE ossl_ssl_session_get_time(VALUE self) +{ + SSL_SESSION *ctx; + time_t t; + + GetSSLSession(self, ctx); + + t = SSL_SESSION_get_time(ctx); + + if (t == 0) + return Qnil; + + return rb_funcall(rb_cTime, rb_intern("at"), 1, TIMET2NUM(t)); +} + +/* + * call-seq: + * session.timeout -> integer + * + * Gets how long until the session expires in seconds. + * +*/ +static VALUE ossl_ssl_session_get_timeout(VALUE self) +{ + SSL_SESSION *ctx; + time_t t; + + GetSSLSession(self, ctx); + + t = SSL_SESSION_get_timeout(ctx); + + return TIMET2NUM(t); +} + +/* + * call-seq: + * session.time=(Time) -> Time + * session.time=(integer) -> Time + * + * Sets start time of the session. Time resolution is in seconds. + * +*/ +static VALUE ossl_ssl_session_set_time(VALUE self, VALUE time_v) +{ + SSL_SESSION *ctx; + long t; + + GetSSLSession(self, ctx); + if (rb_obj_is_instance_of(time_v, rb_cTime)) { + time_v = rb_funcall(time_v, rb_intern("to_i"), 0); + } + t = NUM2LONG(time_v); + SSL_SESSION_set_time(ctx, t); + return ossl_ssl_session_get_time(self); +} + +/* + * call-seq: + * session.timeout=(integer) -> integer + * + * Sets how long until the session expires in seconds. + * +*/ +static VALUE ossl_ssl_session_set_timeout(VALUE self, VALUE time_v) +{ + SSL_SESSION *ctx; + long t; + + GetSSLSession(self, ctx); + t = NUM2LONG(time_v); + SSL_SESSION_set_timeout(ctx, t); + return ossl_ssl_session_get_timeout(self); +} + +#ifdef HAVE_SSL_SESSION_GET_ID +/* + * call-seq: + * session.id -> aString + * + * Returns the Session ID. +*/ +static VALUE ossl_ssl_session_get_id(VALUE self) +{ + SSL_SESSION *ctx; + const unsigned char *p = NULL; + unsigned int i = 0; + + GetSSLSession(self, ctx); + + p = SSL_SESSION_get_id(ctx, &i); + + return rb_str_new((const char *) p, i); +} +#endif + +/* + * call-seq: + * session.to_der -> aString + * + * Returns an ASN1 encoded String that contains the Session object. +*/ +static VALUE ossl_ssl_session_to_der(VALUE self) +{ + SSL_SESSION *ctx; + unsigned char *p; + int len; + VALUE str; + + GetSSLSession(self, ctx); + len = i2d_SSL_SESSION(ctx, NULL); + if (len <= 0) { + ossl_raise(eSSLSession, "i2d_SSL_SESSION"); + } + + str = rb_str_new(0, len); + p = (unsigned char *)RSTRING_PTR(str); + i2d_SSL_SESSION(ctx, &p); + ossl_str_adjust(str, p); + return str; +} + +/* + * call-seq: + * session.to_pem -> String + * + * Returns a PEM encoded String that contains the Session object. +*/ +static VALUE ossl_ssl_session_to_pem(VALUE self) +{ + SSL_SESSION *ctx; + BIO *out; + BUF_MEM *buf; + VALUE str; + int i; + + GetSSLSession(self, ctx); + + if (!(out = BIO_new(BIO_s_mem()))) { + ossl_raise(eSSLSession, "BIO_s_mem()"); + } + + if (!(i=PEM_write_bio_SSL_SESSION(out, ctx))) { + BIO_free(out); + ossl_raise(eSSLSession, "SSL_SESSION_print()"); + } + + BIO_get_mem_ptr(out, &buf); + str = rb_str_new(buf->data, buf->length); + BIO_free(out); + + return str; +} + + +/* + * call-seq: + * session.to_text -> String + * + * Shows everything in the Session object. +*/ +static VALUE ossl_ssl_session_to_text(VALUE self) +{ + SSL_SESSION *ctx; + BIO *out; + BUF_MEM *buf; + VALUE str; + + GetSSLSession(self, ctx); + + if (!(out = BIO_new(BIO_s_mem()))) { + ossl_raise(eSSLSession, "BIO_s_mem()"); + } + + if (!SSL_SESSION_print(out, ctx)) { + BIO_free(out); + ossl_raise(eSSLSession, "SSL_SESSION_print()"); + } + + BIO_get_mem_ptr(out, &buf); + str = rb_str_new(buf->data, buf->length); + BIO_free(out); + + return str; +} + + +void Init_ossl_ssl_session(void) +{ +#if 0 + mOSSL = rb_define_module("OpenSSL"); /* let rdoc know about mOSSL */ + mSSL = rb_define_module_under(mOSSL, "SSL"); +#endif + cSSLSession = rb_define_class_under(mSSL, "Session", rb_cObject); + eSSLSession = rb_define_class_under(cSSLSession, "SessionError", eOSSLError); + + rb_define_alloc_func(cSSLSession, ossl_ssl_session_alloc); + rb_define_method(cSSLSession, "initialize", ossl_ssl_session_initialize, 1); + + rb_define_method(cSSLSession, "==", ossl_ssl_session_eq, 1); + + rb_define_method(cSSLSession, "time", ossl_ssl_session_get_time, 0); + rb_define_method(cSSLSession, "time=", ossl_ssl_session_set_time, 1); + rb_define_method(cSSLSession, "timeout", ossl_ssl_session_get_timeout, 0); + rb_define_method(cSSLSession, "timeout=", ossl_ssl_session_set_timeout, 1); + +#ifdef HAVE_SSL_SESSION_GET_ID + rb_define_method(cSSLSession, "id", ossl_ssl_session_get_id, 0); +#else + rb_undef_method(cSSLSession, "id"); +#endif + rb_define_method(cSSLSession, "to_der", ossl_ssl_session_to_der, 0); + rb_define_method(cSSLSession, "to_pem", ossl_ssl_session_to_pem, 0); + rb_define_method(cSSLSession, "to_text", ossl_ssl_session_to_text, 0); +} diff --git a/ext/openssl/ossl_version.h b/ext/openssl/ossl_version.h new file mode 100644 index 00000000..193ceab0 --- /dev/null +++ b/ext/openssl/ossl_version.h @@ -0,0 +1,16 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2001-2002 Michal Rokos <m.rokos@sh.cvut.cz> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#if !defined(_OSSL_VERSION_H_) +#define _OSSL_VERSION_H_ + +#define OSSL_VERSION "1.1.0" + +#endif /* _OSSL_VERSION_H_ */ diff --git a/ext/openssl/ossl_x509.c b/ext/openssl/ossl_x509.c new file mode 100644 index 00000000..4de45455 --- /dev/null +++ b/ext/openssl/ossl_x509.c @@ -0,0 +1,104 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2001-2002 Michal Rokos <m.rokos@sh.cvut.cz> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#include "ossl.h" + +VALUE mX509; + +#define DefX509Const(x) rb_define_const(mX509, #x,INT2FIX(X509_##x)) +#define DefX509Default(x,i) \ + rb_define_const(mX509, "DEFAULT_" #x, rb_str_new2(X509_get_default_##i())) + +void +Init_ossl_x509(void) +{ + mX509 = rb_define_module_under(mOSSL, "X509"); + + Init_ossl_x509attr(); + Init_ossl_x509cert(); + Init_ossl_x509crl(); + Init_ossl_x509ext(); + Init_ossl_x509name(); + Init_ossl_x509req(); + Init_ossl_x509revoked(); + Init_ossl_x509store(); + + DefX509Const(V_OK); + DefX509Const(V_ERR_UNABLE_TO_GET_ISSUER_CERT); + DefX509Const(V_ERR_UNABLE_TO_GET_CRL); + DefX509Const(V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE); + DefX509Const(V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE); + DefX509Const(V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY); + DefX509Const(V_ERR_CERT_SIGNATURE_FAILURE); + DefX509Const(V_ERR_CRL_SIGNATURE_FAILURE); + DefX509Const(V_ERR_CERT_NOT_YET_VALID); + DefX509Const(V_ERR_CERT_HAS_EXPIRED); + DefX509Const(V_ERR_CRL_NOT_YET_VALID); + DefX509Const(V_ERR_CRL_HAS_EXPIRED); + DefX509Const(V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD); + DefX509Const(V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD); + DefX509Const(V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD); + DefX509Const(V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD); + DefX509Const(V_ERR_OUT_OF_MEM); + DefX509Const(V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT); + DefX509Const(V_ERR_SELF_SIGNED_CERT_IN_CHAIN); + DefX509Const(V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY); + DefX509Const(V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE); + DefX509Const(V_ERR_CERT_CHAIN_TOO_LONG); + DefX509Const(V_ERR_CERT_REVOKED); + DefX509Const(V_ERR_INVALID_CA); + DefX509Const(V_ERR_PATH_LENGTH_EXCEEDED); + DefX509Const(V_ERR_INVALID_PURPOSE); + DefX509Const(V_ERR_CERT_UNTRUSTED); + DefX509Const(V_ERR_CERT_REJECTED); + DefX509Const(V_ERR_SUBJECT_ISSUER_MISMATCH); + DefX509Const(V_ERR_AKID_SKID_MISMATCH); + DefX509Const(V_ERR_AKID_ISSUER_SERIAL_MISMATCH); + DefX509Const(V_ERR_KEYUSAGE_NO_CERTSIGN); + DefX509Const(V_ERR_APPLICATION_VERIFICATION); + +#if defined(X509_V_FLAG_CRL_CHECK) + DefX509Const(V_FLAG_CRL_CHECK); +#endif +#if defined(X509_V_FLAG_CRL_CHECK_ALL) + DefX509Const(V_FLAG_CRL_CHECK_ALL); +#endif + + DefX509Const(PURPOSE_SSL_CLIENT); + DefX509Const(PURPOSE_SSL_SERVER); + DefX509Const(PURPOSE_NS_SSL_SERVER); + DefX509Const(PURPOSE_SMIME_SIGN); + DefX509Const(PURPOSE_SMIME_ENCRYPT); + DefX509Const(PURPOSE_CRL_SIGN); + DefX509Const(PURPOSE_ANY); +#if defined(X509_PURPOSE_OCSP_HELPER) + DefX509Const(PURPOSE_OCSP_HELPER); +#endif + + DefX509Const(TRUST_COMPAT); + DefX509Const(TRUST_SSL_CLIENT); + DefX509Const(TRUST_SSL_SERVER); + DefX509Const(TRUST_EMAIL); + DefX509Const(TRUST_OBJECT_SIGN); +#if defined(X509_TRUST_OCSP_SIGN) + DefX509Const(TRUST_OCSP_SIGN); +#endif +#if defined(X509_TRUST_OCSP_REQUEST) + DefX509Const(TRUST_OCSP_REQUEST); +#endif + + DefX509Default(CERT_AREA, cert_area); + DefX509Default(CERT_DIR, cert_dir); + DefX509Default(CERT_FILE, cert_file); + DefX509Default(CERT_DIR_ENV, cert_dir_env); + DefX509Default(CERT_FILE_ENV, cert_file_env); + DefX509Default(PRIVATE_DIR, private_dir); +} + diff --git a/ext/openssl/ossl_x509.h b/ext/openssl/ossl_x509.h new file mode 100644 index 00000000..1a435690 --- /dev/null +++ b/ext/openssl/ossl_x509.h @@ -0,0 +1,114 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2001-2002 Michal Rokos <m.rokos@sh.cvut.cz> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#if !defined(_OSSL_X509_H_) +#define _OSSL_X509_H_ + +/* + * X509 main module + */ +extern VALUE mX509; + +void Init_ossl_x509(void); + +/* + * X509Attr + */ +extern VALUE cX509Attr; +extern VALUE eX509AttrError; + +VALUE ossl_x509attr_new(X509_ATTRIBUTE *); +X509_ATTRIBUTE *DupX509AttrPtr(VALUE); +void Init_ossl_x509attr(void); + +/* + * X509Cert + */ +extern VALUE cX509Cert; +extern VALUE eX509CertError; + +VALUE ossl_x509_new(X509 *); +VALUE ossl_x509_new_from_file(VALUE); +X509 *GetX509CertPtr(VALUE); +X509 *DupX509CertPtr(VALUE); +void Init_ossl_x509cert(void); + +/* + * X509CRL + */ +extern VALUE cX509CRL; +extern VALUE eX509CRLError; + +VALUE ossl_x509crl_new(X509_CRL *); +X509_CRL *GetX509CRLPtr(VALUE); +X509_CRL *DupX509CRLPtr(VALUE); +void Init_ossl_x509crl(void); + +/* + * X509Extension + */ +extern VALUE cX509Ext; +extern VALUE cX509ExtFactory; +extern VALUE eX509ExtError; + +VALUE ossl_x509ext_new(X509_EXTENSION *); +X509_EXTENSION *GetX509ExtPtr(VALUE); +X509_EXTENSION *DupX509ExtPtr(VALUE); +void Init_ossl_x509ext(void); + +/* + * X509Name + */ +extern VALUE cX509Name; +extern VALUE eX509NameError; + +VALUE ossl_x509name_new(X509_NAME *); +X509_NAME *GetX509NamePtr(VALUE); +void Init_ossl_x509name(void); + +/* + * X509Request + */ +extern VALUE cX509Req; +extern VALUE eX509ReqError; + +VALUE ossl_x509req_new(X509_REQ *); +X509_REQ *GetX509ReqPtr(VALUE); +X509_REQ *DupX509ReqPtr(VALUE); +void Init_ossl_x509req(void); + +/* + * X509Revoked + */ +extern VALUE cX509Rev; +extern VALUE eX509RevError; + +VALUE ossl_x509revoked_new(X509_REVOKED *); +X509_REVOKED *DupX509RevokedPtr(VALUE); +void Init_ossl_x509revoked(void); + +/* + * X509Store and X509StoreContext + */ +extern VALUE cX509Store; +extern VALUE cX509StoreContext; +extern VALUE eX509StoreError; + +VALUE ossl_x509store_new(X509_STORE *); +X509_STORE *GetX509StorePtr(VALUE); +X509_STORE *DupX509StorePtr(VALUE); + +VALUE ossl_x509stctx_new(X509_STORE_CTX *); +VALUE ossl_x509stctx_clear_ptr(VALUE); +X509_STORE_CTX *GetX509StCtxtPtr(VALUE); + +void Init_ossl_x509store(void); + +#endif /* _OSSL_X509_H_ */ diff --git a/ext/openssl/ossl_x509attr.c b/ext/openssl/ossl_x509attr.c new file mode 100644 index 00000000..fdf0481c --- /dev/null +++ b/ext/openssl/ossl_x509attr.c @@ -0,0 +1,275 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2001 Michal Rokos <m.rokos@sh.cvut.cz> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#include "ossl.h" + +#define WrapX509Attr(klass, obj, attr) do { \ + if (!(attr)) { \ + ossl_raise(rb_eRuntimeError, "ATTR wasn't initialized!"); \ + } \ + (obj) = Data_Wrap_Struct((klass), 0, X509_ATTRIBUTE_free, (attr)); \ +} while (0) +#define GetX509Attr(obj, attr) do { \ + Data_Get_Struct((obj), X509_ATTRIBUTE, (attr)); \ + if (!(attr)) { \ + ossl_raise(rb_eRuntimeError, "ATTR wasn't initialized!"); \ + } \ +} while (0) +#define SafeGetX509Attr(obj, attr) do { \ + OSSL_Check_Kind((obj), cX509Attr); \ + GetX509Attr((obj), (attr)); \ +} while (0) + +/* + * Classes + */ +VALUE cX509Attr; +VALUE eX509AttrError; + +/* + * Public + */ +VALUE +ossl_x509attr_new(X509_ATTRIBUTE *attr) +{ + X509_ATTRIBUTE *new; + VALUE obj; + + if (!attr) { + new = X509_ATTRIBUTE_new(); + } else { + new = X509_ATTRIBUTE_dup(attr); + } + if (!new) { + ossl_raise(eX509AttrError, NULL); + } + WrapX509Attr(cX509Attr, obj, new); + + return obj; +} + +X509_ATTRIBUTE * +DupX509AttrPtr(VALUE obj) +{ + X509_ATTRIBUTE *attr, *new; + + SafeGetX509Attr(obj, attr); + if (!(new = X509_ATTRIBUTE_dup(attr))) { + ossl_raise(eX509AttrError, NULL); + } + + return new; +} + +/* + * Private + */ +static VALUE +ossl_x509attr_alloc(VALUE klass) +{ + X509_ATTRIBUTE *attr; + VALUE obj; + + if (!(attr = X509_ATTRIBUTE_new())) + ossl_raise(eX509AttrError, NULL); + WrapX509Attr(klass, obj, attr); + + return obj; +} + +/* + * call-seq: + * Attribute.new(oid [, value]) => attr + */ +static VALUE +ossl_x509attr_initialize(int argc, VALUE *argv, VALUE self) +{ + VALUE oid, value; + X509_ATTRIBUTE *attr, *x; + const unsigned char *p; + + GetX509Attr(self, attr); + if(rb_scan_args(argc, argv, "11", &oid, &value) == 1){ + oid = ossl_to_der_if_possible(oid); + StringValue(oid); + p = (unsigned char *)RSTRING_PTR(oid); + x = d2i_X509_ATTRIBUTE(&attr, &p, RSTRING_LEN(oid)); + DATA_PTR(self) = attr; + if(!x){ + ossl_raise(eX509AttrError, NULL); + } + return self; + } + rb_funcall(self, rb_intern("oid="), 1, oid); + rb_funcall(self, rb_intern("value="), 1, value); + + return self; +} + +/* + * call-seq: + * attr.oid = string => string + */ +static VALUE +ossl_x509attr_set_oid(VALUE self, VALUE oid) +{ + X509_ATTRIBUTE *attr; + ASN1_OBJECT *obj; + char *s; + + s = StringValuePtr(oid); + obj = OBJ_txt2obj(s, 0); + if(!obj) obj = OBJ_txt2obj(s, 1); + if(!obj) ossl_raise(eX509AttrError, NULL); + GetX509Attr(self, attr); + X509_ATTRIBUTE_set1_object(attr, obj); + + return oid; +} + +/* + * call-seq: + * attr.oid => string + */ +static VALUE +ossl_x509attr_get_oid(VALUE self) +{ + X509_ATTRIBUTE *attr; + ASN1_OBJECT *oid; + BIO *out; + VALUE ret; + int nid; + + GetX509Attr(self, attr); + oid = X509_ATTRIBUTE_get0_object(attr); + if ((nid = OBJ_obj2nid(oid)) != NID_undef) + ret = rb_str_new2(OBJ_nid2sn(nid)); + else{ + if (!(out = BIO_new(BIO_s_mem()))) + ossl_raise(eX509AttrError, NULL); + i2a_ASN1_OBJECT(out, oid); + ret = ossl_membio2str(out); + } + + return ret; +} + +#if defined(HAVE_ST_X509_ATTRIBUTE_SINGLE) || defined(HAVE_ST_SINGLE) +# define OSSL_X509ATTR_IS_SINGLE(attr) ((attr)->single) +# define OSSL_X509ATTR_SET_SINGLE(attr) ((attr)->single = 1) +#else +# define OSSL_X509ATTR_IS_SINGLE(attr) (!(attr)->value.set) +# define OSSL_X509ATTR_SET_SINGLE(attr) ((attr)->value.set = 0) +#endif + +/* + * call-seq: + * attr.value = asn1 => asn1 + */ +static VALUE +ossl_x509attr_set_value(VALUE self, VALUE value) +{ + X509_ATTRIBUTE *attr; + ASN1_TYPE *a1type; + + if(!(a1type = ossl_asn1_get_asn1type(value))) + ossl_raise(eASN1Error, "could not get ASN1_TYPE"); + if(ASN1_TYPE_get(a1type) == V_ASN1_SEQUENCE){ + ASN1_TYPE_free(a1type); + ossl_raise(eASN1Error, "couldn't set SEQUENCE for attribute value."); + } + GetX509Attr(self, attr); + if(attr->value.set){ + if(OSSL_X509ATTR_IS_SINGLE(attr)) ASN1_TYPE_free(attr->value.single); + else sk_ASN1_TYPE_free(attr->value.set); + } + OSSL_X509ATTR_SET_SINGLE(attr); + attr->value.single = a1type; + + return value; +} + +/* + * call-seq: + * attr.value => asn1 + */ +static VALUE +ossl_x509attr_get_value(VALUE self) +{ + X509_ATTRIBUTE *attr; + VALUE str, asn1; + long length; + unsigned char *p; + + GetX509Attr(self, attr); + if(attr->value.ptr == NULL) return Qnil; + if(OSSL_X509ATTR_IS_SINGLE(attr)){ + length = i2d_ASN1_TYPE(attr->value.single, NULL); + str = rb_str_new(0, length); + p = (unsigned char *)RSTRING_PTR(str); + i2d_ASN1_TYPE(attr->value.single, &p); + ossl_str_adjust(str, p); + } + else{ + length = i2d_ASN1_SET_OF_ASN1_TYPE(attr->value.set, + (unsigned char **) NULL, i2d_ASN1_TYPE, + V_ASN1_SET, V_ASN1_UNIVERSAL, 0); + str = rb_str_new(0, length); + p = (unsigned char *)RSTRING_PTR(str); + i2d_ASN1_SET_OF_ASN1_TYPE(attr->value.set, &p, + i2d_ASN1_TYPE, V_ASN1_SET, V_ASN1_UNIVERSAL, 0); + ossl_str_adjust(str, p); + } + asn1 = rb_funcall(mASN1, rb_intern("decode"), 1, str); + + return asn1; +} + +/* + * call-seq: + * attr.to_der => string + */ +static VALUE +ossl_x509attr_to_der(VALUE self) +{ + X509_ATTRIBUTE *attr; + VALUE str; + int len; + unsigned char *p; + + GetX509Attr(self, attr); + if((len = i2d_X509_ATTRIBUTE(attr, NULL)) <= 0) + ossl_raise(eX509AttrError, NULL); + str = rb_str_new(0, len); + p = (unsigned char *)RSTRING_PTR(str); + if(i2d_X509_ATTRIBUTE(attr, &p) <= 0) + ossl_raise(eX509AttrError, NULL); + rb_str_set_len(str, p - (unsigned char*)RSTRING_PTR(str)); + + return str; +} + +/* + * X509_ATTRIBUTE init + */ +void +Init_ossl_x509attr(void) +{ + eX509AttrError = rb_define_class_under(mX509, "AttributeError", eOSSLError); + + cX509Attr = rb_define_class_under(mX509, "Attribute", rb_cObject); + rb_define_alloc_func(cX509Attr, ossl_x509attr_alloc); + rb_define_method(cX509Attr, "initialize", ossl_x509attr_initialize, -1); + rb_define_method(cX509Attr, "oid=", ossl_x509attr_set_oid, 1); + rb_define_method(cX509Attr, "oid", ossl_x509attr_get_oid, 0); + rb_define_method(cX509Attr, "value=", ossl_x509attr_set_value, 1); + rb_define_method(cX509Attr, "value", ossl_x509attr_get_value, 0); + rb_define_method(cX509Attr, "to_der", ossl_x509attr_to_der, 0); +} diff --git a/ext/openssl/ossl_x509cert.c b/ext/openssl/ossl_x509cert.c new file mode 100644 index 00000000..0c8f0751 --- /dev/null +++ b/ext/openssl/ossl_x509cert.c @@ -0,0 +1,846 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2001-2002 Michal Rokos <m.rokos@sh.cvut.cz> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#include "ossl.h" + +#define WrapX509(klass, obj, x509) do { \ + if (!(x509)) { \ + ossl_raise(rb_eRuntimeError, "CERT wasn't initialized!"); \ + } \ + (obj) = Data_Wrap_Struct((klass), 0, X509_free, (x509)); \ +} while (0) +#define GetX509(obj, x509) do { \ + Data_Get_Struct((obj), X509, (x509)); \ + if (!(x509)) { \ + ossl_raise(rb_eRuntimeError, "CERT wasn't initialized!"); \ + } \ +} while (0) +#define SafeGetX509(obj, x509) do { \ + OSSL_Check_Kind((obj), cX509Cert); \ + GetX509((obj), (x509)); \ +} while (0) + +/* + * Classes + */ +VALUE cX509Cert; +VALUE eX509CertError; + +/* + * Public + */ +VALUE +ossl_x509_new(X509 *x509) +{ + X509 *new; + VALUE obj; + + if (!x509) { + new = X509_new(); + } else { + new = X509_dup(x509); + } + if (!new) { + ossl_raise(eX509CertError, NULL); + } + WrapX509(cX509Cert, obj, new); + + return obj; +} + +VALUE +ossl_x509_new_from_file(VALUE filename) +{ + X509 *x509; + FILE *fp; + VALUE obj; + + SafeStringValue(filename); + if (!(fp = fopen(RSTRING_PTR(filename), "r"))) { + ossl_raise(eX509CertError, "%s", strerror(errno)); + } + rb_fd_fix_cloexec(fileno(fp)); + x509 = PEM_read_X509(fp, NULL, NULL, NULL); + /* + * prepare for DER... +#if !defined(OPENSSL_NO_FP_API) + if (!x509) { + (void)ERR_get_error(); + rewind(fp); + + x509 = d2i_X509_fp(fp, NULL); + } +#endif + */ + fclose(fp); + if (!x509) { + ossl_raise(eX509CertError, NULL); + } + WrapX509(cX509Cert, obj, x509); + + return obj; +} + +X509 * +GetX509CertPtr(VALUE obj) +{ + X509 *x509; + + SafeGetX509(obj, x509); + + return x509; +} + +X509 * +DupX509CertPtr(VALUE obj) +{ + X509 *x509; + + SafeGetX509(obj, x509); + + CRYPTO_add(&x509->references, 1, CRYPTO_LOCK_X509); + + return x509; +} + +/* + * Private + */ +static VALUE +ossl_x509_alloc(VALUE klass) +{ + X509 *x509; + VALUE obj; + + x509 = X509_new(); + if (!x509) ossl_raise(eX509CertError, NULL); + + WrapX509(klass, obj, x509); + + return obj; +} + +/* + * call-seq: + * Certificate.new => cert + * Certificate.new(string) => cert + */ +static VALUE +ossl_x509_initialize(int argc, VALUE *argv, VALUE self) +{ + BIO *in; + X509 *x509, *x = DATA_PTR(self); + VALUE arg; + + if (rb_scan_args(argc, argv, "01", &arg) == 0) { + /* create just empty X509Cert */ + return self; + } + arg = ossl_to_der_if_possible(arg); + in = ossl_obj2bio(arg); + x509 = PEM_read_bio_X509(in, &x, NULL, NULL); + DATA_PTR(self) = x; + if (!x509) { + OSSL_BIO_reset(in); + x509 = d2i_X509_bio(in, &x); + DATA_PTR(self) = x; + } + BIO_free(in); + if (!x509) ossl_raise(eX509CertError, NULL); + + return self; +} + +static VALUE +ossl_x509_copy(VALUE self, VALUE other) +{ + X509 *a, *b, *x509; + + rb_check_frozen(self); + if (self == other) return self; + + GetX509(self, a); + SafeGetX509(other, b); + + x509 = X509_dup(b); + if (!x509) ossl_raise(eX509CertError, NULL); + + DATA_PTR(self) = x509; + X509_free(a); + + return self; +} + +/* + * call-seq: + * cert.to_der => string + */ +static VALUE +ossl_x509_to_der(VALUE self) +{ + X509 *x509; + VALUE str; + long len; + unsigned char *p; + + GetX509(self, x509); + if ((len = i2d_X509(x509, NULL)) <= 0) + ossl_raise(eX509CertError, NULL); + str = rb_str_new(0, len); + p = (unsigned char *)RSTRING_PTR(str); + if (i2d_X509(x509, &p) <= 0) + ossl_raise(eX509CertError, NULL); + ossl_str_adjust(str, p); + + return str; +} + +/* + * call-seq: + * cert.to_pem => string + */ +static VALUE +ossl_x509_to_pem(VALUE self) +{ + X509 *x509; + BIO *out; + VALUE str; + + GetX509(self, x509); + out = BIO_new(BIO_s_mem()); + if (!out) ossl_raise(eX509CertError, NULL); + + if (!PEM_write_bio_X509(out, x509)) { + BIO_free(out); + ossl_raise(eX509CertError, NULL); + } + str = ossl_membio2str(out); + + return str; +} + +/* + * call-seq: + * cert.to_text => string + */ +static VALUE +ossl_x509_to_text(VALUE self) +{ + X509 *x509; + BIO *out; + VALUE str; + + GetX509(self, x509); + + out = BIO_new(BIO_s_mem()); + if (!out) ossl_raise(eX509CertError, NULL); + + if (!X509_print(out, x509)) { + BIO_free(out); + ossl_raise(eX509CertError, NULL); + } + str = ossl_membio2str(out); + + return str; +} + +#if 0 +/* + * Makes from X509 X509_REQuest + */ +static VALUE +ossl_x509_to_req(VALUE self) +{ + X509 *x509; + X509_REQ *req; + VALUE obj; + + GetX509(self, x509); + if (!(req = X509_to_X509_REQ(x509, NULL, EVP_md5()))) { + ossl_raise(eX509CertError, NULL); + } + obj = ossl_x509req_new(req); + X509_REQ_free(req); + + return obj; +} +#endif + +/* + * call-seq: + * cert.version => integer + */ +static VALUE +ossl_x509_get_version(VALUE self) +{ + X509 *x509; + + GetX509(self, x509); + + return LONG2NUM(X509_get_version(x509)); +} + +/* + * call-seq: + * cert.version = integer => integer + */ +static VALUE +ossl_x509_set_version(VALUE self, VALUE version) +{ + X509 *x509; + long ver; + + if ((ver = NUM2LONG(version)) < 0) { + ossl_raise(eX509CertError, "version must be >= 0!"); + } + GetX509(self, x509); + if (!X509_set_version(x509, ver)) { + ossl_raise(eX509CertError, NULL); + } + + return version; +} + +/* + * call-seq: + * cert.serial => integer + */ +static VALUE +ossl_x509_get_serial(VALUE self) +{ + X509 *x509; + + GetX509(self, x509); + + return asn1integer_to_num(X509_get_serialNumber(x509)); +} + +/* + * call-seq: + * cert.serial = integer => integer + */ +static VALUE +ossl_x509_set_serial(VALUE self, VALUE num) +{ + X509 *x509; + + GetX509(self, x509); + + x509->cert_info->serialNumber = + num_to_asn1integer(num, X509_get_serialNumber(x509)); + + return num; +} + +/* + * call-seq: + * cert.signature_algorithm => string + */ +static VALUE +ossl_x509_get_signature_algorithm(VALUE self) +{ + X509 *x509; + BIO *out; + VALUE str; + + GetX509(self, x509); + out = BIO_new(BIO_s_mem()); + if (!out) ossl_raise(eX509CertError, NULL); + + if (!i2a_ASN1_OBJECT(out, x509->cert_info->signature->algorithm)) { + BIO_free(out); + ossl_raise(eX509CertError, NULL); + } + str = ossl_membio2str(out); + + return str; +} + +/* + * call-seq: + * cert.subject => name + */ +static VALUE +ossl_x509_get_subject(VALUE self) +{ + X509 *x509; + X509_NAME *name; + + GetX509(self, x509); + if (!(name = X509_get_subject_name(x509))) { /* NO DUP - don't free! */ + ossl_raise(eX509CertError, NULL); + } + + return ossl_x509name_new(name); +} + +/* + * call-seq: + * cert.subject = name => name + */ +static VALUE +ossl_x509_set_subject(VALUE self, VALUE subject) +{ + X509 *x509; + + GetX509(self, x509); + if (!X509_set_subject_name(x509, GetX509NamePtr(subject))) { /* DUPs name */ + ossl_raise(eX509CertError, NULL); + } + + return subject; +} + +/* + * call-seq: + * cert.issuer => name + */ +static VALUE +ossl_x509_get_issuer(VALUE self) +{ + X509 *x509; + X509_NAME *name; + + GetX509(self, x509); + if(!(name = X509_get_issuer_name(x509))) { /* NO DUP - don't free! */ + ossl_raise(eX509CertError, NULL); + } + + return ossl_x509name_new(name); +} + +/* + * call-seq: + * cert.issuer = name => name + */ +static VALUE +ossl_x509_set_issuer(VALUE self, VALUE issuer) +{ + X509 *x509; + + GetX509(self, x509); + if (!X509_set_issuer_name(x509, GetX509NamePtr(issuer))) { /* DUPs name */ + ossl_raise(eX509CertError, NULL); + } + + return issuer; +} + +/* + * call-seq: + * cert.not_before => time + */ +static VALUE +ossl_x509_get_not_before(VALUE self) +{ + X509 *x509; + ASN1_UTCTIME *asn1time; + + GetX509(self, x509); + if (!(asn1time = X509_get_notBefore(x509))) { /* NO DUP - don't free! */ + ossl_raise(eX509CertError, NULL); + } + + return asn1time_to_time(asn1time); +} + +/* + * call-seq: + * cert.not_before = time => time + */ +static VALUE +ossl_x509_set_not_before(VALUE self, VALUE time) +{ + X509 *x509; + time_t sec; + + sec = time_to_time_t(time); + GetX509(self, x509); + if (!X509_time_adj(X509_get_notBefore(x509), 0, &sec)) { + ossl_raise(eX509CertError, NULL); + } + + return time; +} + +/* + * call-seq: + * cert.not_after => time + */ +static VALUE +ossl_x509_get_not_after(VALUE self) +{ + X509 *x509; + ASN1_TIME *asn1time; + + GetX509(self, x509); + if (!(asn1time = X509_get_notAfter(x509))) { /* NO DUP - don't free! */ + ossl_raise(eX509CertError, NULL); + } + + return asn1time_to_time(asn1time); +} + +/* + * call-seq: + * cert.not_after = time => time + */ +static VALUE +ossl_x509_set_not_after(VALUE self, VALUE time) +{ + X509 *x509; + time_t sec; + + sec = time_to_time_t(time); + GetX509(self, x509); + if (!X509_time_adj(X509_get_notAfter(x509), 0, &sec)) { + ossl_raise(eX509CertError, NULL); + } + + return time; +} + +/* + * call-seq: + * cert.public_key => key + */ +static VALUE +ossl_x509_get_public_key(VALUE self) +{ + X509 *x509; + EVP_PKEY *pkey; + + GetX509(self, x509); + if (!(pkey = X509_get_pubkey(x509))) { /* adds an reference */ + ossl_raise(eX509CertError, NULL); + } + + return ossl_pkey_new(pkey); /* NO DUP - OK */ +} + +/* + * call-seq: + * cert.public_key = key => key + */ +static VALUE +ossl_x509_set_public_key(VALUE self, VALUE key) +{ + X509 *x509; + + GetX509(self, x509); + if (!X509_set_pubkey(x509, GetPKeyPtr(key))) { /* DUPs pkey */ + ossl_raise(eX509CertError, NULL); + } + + return key; +} + +/* + * call-seq: + * cert.sign(key, digest) => self + */ +static VALUE +ossl_x509_sign(VALUE self, VALUE key, VALUE digest) +{ + X509 *x509; + EVP_PKEY *pkey; + const EVP_MD *md; + + pkey = GetPrivPKeyPtr(key); /* NO NEED TO DUP */ + md = GetDigestPtr(digest); + GetX509(self, x509); + if (!X509_sign(x509, pkey, md)) { + ossl_raise(eX509CertError, NULL); + } + + return self; +} + +/* + * call-seq: + * cert.verify(key) => true | false + * + * Checks that cert signature is made with PRIVversion of this PUBLIC 'key' + */ +static VALUE +ossl_x509_verify(VALUE self, VALUE key) +{ + X509 *x509; + EVP_PKEY *pkey; + int i; + + pkey = GetPKeyPtr(key); /* NO NEED TO DUP */ + GetX509(self, x509); + if ((i = X509_verify(x509, pkey)) < 0) { + ossl_raise(eX509CertError, NULL); + } + if (i > 0) { + return Qtrue; + } + + return Qfalse; +} + +/* + * call-seq: + * cert.check_private_key(key) + * + * Checks if 'key' is PRIV key for this cert + */ +static VALUE +ossl_x509_check_private_key(VALUE self, VALUE key) +{ + X509 *x509; + EVP_PKEY *pkey; + + /* not needed private key, but should be */ + pkey = GetPrivPKeyPtr(key); /* NO NEED TO DUP */ + GetX509(self, x509); + if (!X509_check_private_key(x509, pkey)) { + OSSL_Warning("Check private key:%s", OSSL_ErrMsg()); + return Qfalse; + } + + return Qtrue; +} + +/* + * call-seq: + * cert.extensions => [extension...] + */ +static VALUE +ossl_x509_get_extensions(VALUE self) +{ + X509 *x509; + int count, i; + X509_EXTENSION *ext; + VALUE ary; + + GetX509(self, x509); + count = X509_get_ext_count(x509); + if (count < 0) { + return rb_ary_new(); + } + ary = rb_ary_new2(count); + for (i=0; i<count; i++) { + ext = X509_get_ext(x509, i); /* NO DUP - don't free! */ + rb_ary_push(ary, ossl_x509ext_new(ext)); + } + + return ary; +} + +/* + * call-seq: + * cert.extensions = [ext...] => [ext...] + */ +static VALUE +ossl_x509_set_extensions(VALUE self, VALUE ary) +{ + X509 *x509; + X509_EXTENSION *ext; + int i; + + Check_Type(ary, T_ARRAY); + /* All ary's members should be X509Extension */ + for (i=0; i<RARRAY_LEN(ary); i++) { + OSSL_Check_Kind(RARRAY_PTR(ary)[i], cX509Ext); + } + GetX509(self, x509); + sk_X509_EXTENSION_pop_free(x509->cert_info->extensions, X509_EXTENSION_free); + x509->cert_info->extensions = NULL; + for (i=0; i<RARRAY_LEN(ary); i++) { + ext = DupX509ExtPtr(RARRAY_PTR(ary)[i]); + + if (!X509_add_ext(x509, ext, -1)) { /* DUPs ext - FREE it */ + X509_EXTENSION_free(ext); + ossl_raise(eX509CertError, NULL); + } + X509_EXTENSION_free(ext); + } + + return ary; +} + +/* + * call-seq: + * cert.add_extension(extension) => extension + */ +static VALUE +ossl_x509_add_extension(VALUE self, VALUE extension) +{ + X509 *x509; + X509_EXTENSION *ext; + + GetX509(self, x509); + ext = DupX509ExtPtr(extension); + if (!X509_add_ext(x509, ext, -1)) { /* DUPs ext - FREE it */ + X509_EXTENSION_free(ext); + ossl_raise(eX509CertError, NULL); + } + X509_EXTENSION_free(ext); + + return extension; +} + +static VALUE +ossl_x509_inspect(VALUE self) +{ + return rb_sprintf("#<%"PRIsVALUE": subject=%+"PRIsVALUE", " + "issuer=%+"PRIsVALUE", serial=%+"PRIsVALUE", " + "not_before=%+"PRIsVALUE", not_after=%+"PRIsVALUE">", + rb_obj_class(self), + ossl_x509_get_subject(self), + ossl_x509_get_issuer(self), + ossl_x509_get_serial(self), + ossl_x509_get_not_before(self), + ossl_x509_get_not_after(self)); +} + +/* + * INIT + */ +void +Init_ossl_x509cert(void) +{ + +#if 0 + mOSSL = rb_define_module("OpenSSL"); /* let rdoc know about mOSSL */ + mX509 = rb_define_module_under(mOSSL, "X509"); +#endif + + eX509CertError = rb_define_class_under(mX509, "CertificateError", eOSSLError); + + /* Document-class: OpenSSL::X509::Certificate + * + * Implementation of an X.509 certificate as specified in RFC 5280. + * Provides access to a certificate's attributes and allows certificates + * to be read from a string, but also supports the creation of new + * certificates from scratch. + * + * === Reading a certificate from a file + * + * Certificate is capable of handling DER-encoded certificates and + * certificates encoded in OpenSSL's PEM format. + * + * raw = File.read "cert.cer" # DER- or PEM-encoded + * certificate = OpenSSL::X509::Certificate.new raw + * + * === Saving a certificate to a file + * + * A certificate may be encoded in DER format + * + * cert = ... + * File.open("cert.cer", "wb") { |f| f.print cert.to_der } + * + * or in PEM format + * + * cert = ... + * File.open("cert.pem", "wb") { |f| f.print cert.to_pem } + * + * X.509 certificates are associated with a private/public key pair, + * typically a RSA, DSA or ECC key (see also OpenSSL::PKey::RSA, + * OpenSSL::PKey::DSA and OpenSSL::PKey::EC), the public key itself is + * stored within the certificate and can be accessed in form of an + * OpenSSL::PKey. Certificates are typically used to be able to associate + * some form of identity with a key pair, for example web servers serving + * pages over HTTPs use certificates to authenticate themselves to the user. + * + * The public key infrastructure (PKI) model relies on trusted certificate + * authorities ("root CAs") that issue these certificates, so that end + * users need to base their trust just on a selected few authorities + * that themselves again vouch for subordinate CAs issuing their + * certificates to end users. + * + * The OpenSSL::X509 module provides the tools to set up an independent + * PKI, similar to scenarios where the 'openssl' command line tool is + * used for issuing certificates in a private PKI. + * + * === Creating a root CA certificate and an end-entity certificate + * + * First, we need to create a "self-signed" root certificate. To do so, + * we need to generate a key first. Please note that the choice of "1" + * as a serial number is considered a security flaw for real certificates. + * Secure choices are integers in the two-digit byte range and ideally + * not sequential but secure random numbers, steps omitted here to keep + * the example concise. + * + * root_key = OpenSSL::PKey::RSA.new 2048 # the CA's public/private key + * root_ca = OpenSSL::X509::Certificate.new + * root_ca.version = 2 # cf. RFC 5280 - to make it a "v3" certificate + * root_ca.serial = 1 + * root_ca.subject = OpenSSL::X509::Name.parse "/DC=org/DC=ruby-lang/CN=Ruby CA" + * root_ca.issuer = root_ca.subject # root CA's are "self-signed" + * root_ca.public_key = root_key.public_key + * root_ca.not_before = Time.now + * root_ca.not_after = root_ca.not_before + 2 * 365 * 24 * 60 * 60 # 2 years validity + * ef = OpenSSL::X509::ExtensionFactory.new + * ef.subject_certificate = root_ca + * ef.issuer_certificate = root_ca + * root_ca.add_extension(ef.create_extension("basicConstraints","CA:TRUE",true)) + * root_ca.add_extension(ef.create_extension("keyUsage","keyCertSign, cRLSign", true)) + * root_ca.add_extension(ef.create_extension("subjectKeyIdentifier","hash",false)) + * root_ca.add_extension(ef.create_extension("authorityKeyIdentifier","keyid:always",false)) + * root_ca.sign(root_key, OpenSSL::Digest::SHA256.new) + * + * The next step is to create the end-entity certificate using the root CA + * certificate. + * + * key = OpenSSL::PKey::RSA.new 2048 + * cert = OpenSSL::X509::Certificate.new + * cert.version = 2 + * cert.serial = 2 + * cert.subject = OpenSSL::X509::Name.parse "/DC=org/DC=ruby-lang/CN=Ruby certificate" + * cert.issuer = root_ca.subject # root CA is the issuer + * cert.public_key = key.public_key + * cert.not_before = Time.now + * cert.not_after = cert.not_before + 1 * 365 * 24 * 60 * 60 # 1 years validity + * ef = OpenSSL::X509::ExtensionFactory.new + * ef.subject_certificate = cert + * ef.issuer_certificate = root_ca + * cert.add_extension(ef.create_extension("keyUsage","digitalSignature", true)) + * cert.add_extension(ef.create_extension("subjectKeyIdentifier","hash",false)) + * cert.sign(root_key, OpenSSL::Digest::SHA256.new) + * + */ + cX509Cert = rb_define_class_under(mX509, "Certificate", rb_cObject); + + rb_define_alloc_func(cX509Cert, ossl_x509_alloc); + rb_define_method(cX509Cert, "initialize", ossl_x509_initialize, -1); + rb_define_copy_func(cX509Cert, ossl_x509_copy); + + rb_define_method(cX509Cert, "to_der", ossl_x509_to_der, 0); + rb_define_method(cX509Cert, "to_pem", ossl_x509_to_pem, 0); + rb_define_alias(cX509Cert, "to_s", "to_pem"); + rb_define_method(cX509Cert, "to_text", ossl_x509_to_text, 0); + rb_define_method(cX509Cert, "version", ossl_x509_get_version, 0); + rb_define_method(cX509Cert, "version=", ossl_x509_set_version, 1); + rb_define_method(cX509Cert, "signature_algorithm", ossl_x509_get_signature_algorithm, 0); + rb_define_method(cX509Cert, "serial", ossl_x509_get_serial, 0); + rb_define_method(cX509Cert, "serial=", ossl_x509_set_serial, 1); + rb_define_method(cX509Cert, "subject", ossl_x509_get_subject, 0); + rb_define_method(cX509Cert, "subject=", ossl_x509_set_subject, 1); + rb_define_method(cX509Cert, "issuer", ossl_x509_get_issuer, 0); + rb_define_method(cX509Cert, "issuer=", ossl_x509_set_issuer, 1); + rb_define_method(cX509Cert, "not_before", ossl_x509_get_not_before, 0); + rb_define_method(cX509Cert, "not_before=", ossl_x509_set_not_before, 1); + rb_define_method(cX509Cert, "not_after", ossl_x509_get_not_after, 0); + rb_define_method(cX509Cert, "not_after=", ossl_x509_set_not_after, 1); + rb_define_method(cX509Cert, "public_key", ossl_x509_get_public_key, 0); + rb_define_method(cX509Cert, "public_key=", ossl_x509_set_public_key, 1); + rb_define_method(cX509Cert, "sign", ossl_x509_sign, 2); + rb_define_method(cX509Cert, "verify", ossl_x509_verify, 1); + rb_define_method(cX509Cert, "check_private_key", ossl_x509_check_private_key, 1); + rb_define_method(cX509Cert, "extensions", ossl_x509_get_extensions, 0); + rb_define_method(cX509Cert, "extensions=", ossl_x509_set_extensions, 1); + rb_define_method(cX509Cert, "add_extension", ossl_x509_add_extension, 1); + rb_define_method(cX509Cert, "inspect", ossl_x509_inspect, 0); +} + diff --git a/ext/openssl/ossl_x509crl.c b/ext/openssl/ossl_x509crl.c new file mode 100644 index 00000000..beacc260 --- /dev/null +++ b/ext/openssl/ossl_x509crl.c @@ -0,0 +1,537 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2001-2002 Michal Rokos <m.rokos@sh.cvut.cz> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#include "ossl.h" + +#define WrapX509CRL(klass, obj, crl) do { \ + if (!(crl)) { \ + ossl_raise(rb_eRuntimeError, "CRL wasn't initialized!"); \ + } \ + (obj) = Data_Wrap_Struct((klass), 0, X509_CRL_free, (crl)); \ +} while (0) +#define GetX509CRL(obj, crl) do { \ + Data_Get_Struct((obj), X509_CRL, (crl)); \ + if (!(crl)) { \ + ossl_raise(rb_eRuntimeError, "CRL wasn't initialized!"); \ + } \ +} while (0) +#define SafeGetX509CRL(obj, crl) do { \ + OSSL_Check_Kind((obj), cX509CRL); \ + GetX509CRL((obj), (crl)); \ +} while (0) + +/* + * Classes + */ +VALUE cX509CRL; +VALUE eX509CRLError; + +/* + * PUBLIC + */ +X509_CRL * +GetX509CRLPtr(VALUE obj) +{ + X509_CRL *crl; + + SafeGetX509CRL(obj, crl); + + return crl; +} + +X509_CRL * +DupX509CRLPtr(VALUE obj) +{ + X509_CRL *crl; + + SafeGetX509CRL(obj, crl); + CRYPTO_add(&crl->references, 1, CRYPTO_LOCK_X509_CRL); + + return crl; +} + +VALUE +ossl_x509crl_new(X509_CRL *crl) +{ + X509_CRL *tmp; + VALUE obj; + + tmp = crl ? X509_CRL_dup(crl) : X509_CRL_new(); + if(!tmp) ossl_raise(eX509CRLError, NULL); + WrapX509CRL(cX509CRL, obj, tmp); + + return obj; +} + +/* + * PRIVATE + */ +static VALUE +ossl_x509crl_alloc(VALUE klass) +{ + X509_CRL *crl; + VALUE obj; + + if (!(crl = X509_CRL_new())) { + ossl_raise(eX509CRLError, NULL); + } + WrapX509CRL(klass, obj, crl); + + return obj; +} + +static VALUE +ossl_x509crl_initialize(int argc, VALUE *argv, VALUE self) +{ + BIO *in; + X509_CRL *crl, *x = DATA_PTR(self); + VALUE arg; + + if (rb_scan_args(argc, argv, "01", &arg) == 0) { + return self; + } + arg = ossl_to_der_if_possible(arg); + in = ossl_obj2bio(arg); + crl = PEM_read_bio_X509_CRL(in, &x, NULL, NULL); + DATA_PTR(self) = x; + if (!crl) { + OSSL_BIO_reset(in); + crl = d2i_X509_CRL_bio(in, &x); + DATA_PTR(self) = x; + } + BIO_free(in); + if (!crl) ossl_raise(eX509CRLError, NULL); + + return self; +} + +static VALUE +ossl_x509crl_copy(VALUE self, VALUE other) +{ + X509_CRL *a, *b, *crl; + + rb_check_frozen(self); + if (self == other) return self; + GetX509CRL(self, a); + SafeGetX509CRL(other, b); + if (!(crl = X509_CRL_dup(b))) { + ossl_raise(eX509CRLError, NULL); + } + X509_CRL_free(a); + DATA_PTR(self) = crl; + + return self; +} + +static VALUE +ossl_x509crl_get_version(VALUE self) +{ + X509_CRL *crl; + long ver; + + GetX509CRL(self, crl); + ver = X509_CRL_get_version(crl); + + return LONG2NUM(ver); +} + +static VALUE +ossl_x509crl_set_version(VALUE self, VALUE version) +{ + X509_CRL *crl; + long ver; + + if ((ver = NUM2LONG(version)) < 0) { + ossl_raise(eX509CRLError, "version must be >= 0!"); + } + GetX509CRL(self, crl); + if (!X509_CRL_set_version(crl, ver)) { + ossl_raise(eX509CRLError, NULL); + } + + return version; +} + +static VALUE +ossl_x509crl_get_signature_algorithm(VALUE self) +{ + X509_CRL *crl; + BIO *out; + BUF_MEM *buf; + VALUE str; + + GetX509CRL(self, crl); + if (!(out = BIO_new(BIO_s_mem()))) { + ossl_raise(eX509CRLError, NULL); + } + if (!i2a_ASN1_OBJECT(out, crl->sig_alg->algorithm)) { + BIO_free(out); + ossl_raise(eX509CRLError, NULL); + } + BIO_get_mem_ptr(out, &buf); + str = rb_str_new(buf->data, buf->length); + BIO_free(out); + return str; +} + +static VALUE +ossl_x509crl_get_issuer(VALUE self) +{ + X509_CRL *crl; + + GetX509CRL(self, crl); + + return ossl_x509name_new(X509_CRL_get_issuer(crl)); /* NO DUP - don't free */ +} + +static VALUE +ossl_x509crl_set_issuer(VALUE self, VALUE issuer) +{ + X509_CRL *crl; + + GetX509CRL(self, crl); + + if (!X509_CRL_set_issuer_name(crl, GetX509NamePtr(issuer))) { /* DUPs name */ + ossl_raise(eX509CRLError, NULL); + } + return issuer; +} + +static VALUE +ossl_x509crl_get_last_update(VALUE self) +{ + X509_CRL *crl; + + GetX509CRL(self, crl); + + return asn1time_to_time(X509_CRL_get_lastUpdate(crl)); +} + +static VALUE +ossl_x509crl_set_last_update(VALUE self, VALUE time) +{ + X509_CRL *crl; + time_t sec; + + sec = time_to_time_t(time); + GetX509CRL(self, crl); + if (!X509_time_adj(crl->crl->lastUpdate, 0, &sec)) { + ossl_raise(eX509CRLError, NULL); + } + + return time; +} + +static VALUE +ossl_x509crl_get_next_update(VALUE self) +{ + X509_CRL *crl; + + GetX509CRL(self, crl); + + return asn1time_to_time(X509_CRL_get_nextUpdate(crl)); +} + +static VALUE +ossl_x509crl_set_next_update(VALUE self, VALUE time) +{ + X509_CRL *crl; + time_t sec; + + sec = time_to_time_t(time); + GetX509CRL(self, crl); + /* This must be some thinko in OpenSSL */ + if (!(crl->crl->nextUpdate = X509_time_adj(crl->crl->nextUpdate, 0, &sec))){ + ossl_raise(eX509CRLError, NULL); + } + + return time; +} + +static VALUE +ossl_x509crl_get_revoked(VALUE self) +{ + X509_CRL *crl; + int i, num; + X509_REVOKED *rev; + VALUE ary, revoked; + + GetX509CRL(self, crl); + num = sk_X509_REVOKED_num(X509_CRL_get_REVOKED(crl)); + if (num < 0) { + OSSL_Debug("num < 0???"); + return rb_ary_new(); + } + ary = rb_ary_new2(num); + for(i=0; i<num; i++) { + /* NO DUP - don't free! */ + rev = sk_X509_REVOKED_value(X509_CRL_get_REVOKED(crl), i); + revoked = ossl_x509revoked_new(rev); + rb_ary_push(ary, revoked); + } + + return ary; +} + +static VALUE +ossl_x509crl_set_revoked(VALUE self, VALUE ary) +{ + X509_CRL *crl; + X509_REVOKED *rev; + int i; + + Check_Type(ary, T_ARRAY); + /* All ary members should be X509 Revoked */ + for (i=0; i<RARRAY_LEN(ary); i++) { + OSSL_Check_Kind(RARRAY_PTR(ary)[i], cX509Rev); + } + GetX509CRL(self, crl); + sk_X509_REVOKED_pop_free(crl->crl->revoked, X509_REVOKED_free); + crl->crl->revoked = NULL; + for (i=0; i<RARRAY_LEN(ary); i++) { + rev = DupX509RevokedPtr(RARRAY_PTR(ary)[i]); + if (!X509_CRL_add0_revoked(crl, rev)) { /* NO DUP - don't free! */ + ossl_raise(eX509CRLError, NULL); + } + } + X509_CRL_sort(crl); + + return ary; +} + +static VALUE +ossl_x509crl_add_revoked(VALUE self, VALUE revoked) +{ + X509_CRL *crl; + X509_REVOKED *rev; + + GetX509CRL(self, crl); + rev = DupX509RevokedPtr(revoked); + if (!X509_CRL_add0_revoked(crl, rev)) { /* NO DUP - don't free! */ + ossl_raise(eX509CRLError, NULL); + } + X509_CRL_sort(crl); + + return revoked; +} + +static VALUE +ossl_x509crl_sign(VALUE self, VALUE key, VALUE digest) +{ + X509_CRL *crl; + EVP_PKEY *pkey; + const EVP_MD *md; + + GetX509CRL(self, crl); + pkey = GetPrivPKeyPtr(key); /* NO NEED TO DUP */ + md = GetDigestPtr(digest); + if (!X509_CRL_sign(crl, pkey, md)) { + ossl_raise(eX509CRLError, NULL); + } + + return self; +} + +static VALUE +ossl_x509crl_verify(VALUE self, VALUE key) +{ + X509_CRL *crl; + int ret; + + GetX509CRL(self, crl); + if ((ret = X509_CRL_verify(crl, GetPKeyPtr(key))) < 0) { + ossl_raise(eX509CRLError, NULL); + } + if (ret == 1) { + return Qtrue; + } + + return Qfalse; +} + +static VALUE +ossl_x509crl_to_der(VALUE self) +{ + X509_CRL *crl; + BIO *out; + BUF_MEM *buf; + VALUE str; + + GetX509CRL(self, crl); + if (!(out = BIO_new(BIO_s_mem()))) { + ossl_raise(eX509CRLError, NULL); + } + if (!i2d_X509_CRL_bio(out, crl)) { + BIO_free(out); + ossl_raise(eX509CRLError, NULL); + } + BIO_get_mem_ptr(out, &buf); + str = rb_str_new(buf->data, buf->length); + BIO_free(out); + + return str; +} + +static VALUE +ossl_x509crl_to_pem(VALUE self) +{ + X509_CRL *crl; + BIO *out; + BUF_MEM *buf; + VALUE str; + + GetX509CRL(self, crl); + if (!(out = BIO_new(BIO_s_mem()))) { + ossl_raise(eX509CRLError, NULL); + } + if (!PEM_write_bio_X509_CRL(out, crl)) { + BIO_free(out); + ossl_raise(eX509CRLError, NULL); + } + BIO_get_mem_ptr(out, &buf); + str = rb_str_new(buf->data, buf->length); + BIO_free(out); + + return str; +} + +static VALUE +ossl_x509crl_to_text(VALUE self) +{ + X509_CRL *crl; + BIO *out; + BUF_MEM *buf; + VALUE str; + + GetX509CRL(self, crl); + if (!(out = BIO_new(BIO_s_mem()))) { + ossl_raise(eX509CRLError, NULL); + } + if (!X509_CRL_print(out, crl)) { + BIO_free(out); + ossl_raise(eX509CRLError, NULL); + } + BIO_get_mem_ptr(out, &buf); + str = rb_str_new(buf->data, buf->length); + BIO_free(out); + + return str; +} + +/* + * Gets X509v3 extensions as array of X509Ext objects + */ +static VALUE +ossl_x509crl_get_extensions(VALUE self) +{ + X509_CRL *crl; + int count, i; + X509_EXTENSION *ext; + VALUE ary; + + GetX509CRL(self, crl); + count = X509_CRL_get_ext_count(crl); + if (count < 0) { + OSSL_Debug("count < 0???"); + return rb_ary_new(); + } + ary = rb_ary_new2(count); + for (i=0; i<count; i++) { + ext = X509_CRL_get_ext(crl, i); /* NO DUP - don't free! */ + rb_ary_push(ary, ossl_x509ext_new(ext)); + } + + return ary; +} + +/* + * Sets X509_EXTENSIONs + */ +static VALUE +ossl_x509crl_set_extensions(VALUE self, VALUE ary) +{ + X509_CRL *crl; + X509_EXTENSION *ext; + int i; + + Check_Type(ary, T_ARRAY); + /* All ary members should be X509 Extensions */ + for (i=0; i<RARRAY_LEN(ary); i++) { + OSSL_Check_Kind(RARRAY_PTR(ary)[i], cX509Ext); + } + GetX509CRL(self, crl); + sk_X509_EXTENSION_pop_free(crl->crl->extensions, X509_EXTENSION_free); + crl->crl->extensions = NULL; + for (i=0; i<RARRAY_LEN(ary); i++) { + ext = DupX509ExtPtr(RARRAY_PTR(ary)[i]); + if(!X509_CRL_add_ext(crl, ext, -1)) { /* DUPs ext - FREE it */ + X509_EXTENSION_free(ext); + ossl_raise(eX509CRLError, NULL); + } + X509_EXTENSION_free(ext); + } + + return ary; +} + +static VALUE +ossl_x509crl_add_extension(VALUE self, VALUE extension) +{ + X509_CRL *crl; + X509_EXTENSION *ext; + + GetX509CRL(self, crl); + ext = DupX509ExtPtr(extension); + if (!X509_CRL_add_ext(crl, ext, -1)) { /* DUPs ext - FREE it */ + X509_EXTENSION_free(ext); + ossl_raise(eX509CRLError, NULL); + } + X509_EXTENSION_free(ext); + + return extension; +} + +/* + * INIT + */ +void +Init_ossl_x509crl(void) +{ + eX509CRLError = rb_define_class_under(mX509, "CRLError", eOSSLError); + + cX509CRL = rb_define_class_under(mX509, "CRL", rb_cObject); + + rb_define_alloc_func(cX509CRL, ossl_x509crl_alloc); + rb_define_method(cX509CRL, "initialize", ossl_x509crl_initialize, -1); + rb_define_copy_func(cX509CRL, ossl_x509crl_copy); + + rb_define_method(cX509CRL, "version", ossl_x509crl_get_version, 0); + rb_define_method(cX509CRL, "version=", ossl_x509crl_set_version, 1); + rb_define_method(cX509CRL, "signature_algorithm", ossl_x509crl_get_signature_algorithm, 0); + rb_define_method(cX509CRL, "issuer", ossl_x509crl_get_issuer, 0); + rb_define_method(cX509CRL, "issuer=", ossl_x509crl_set_issuer, 1); + rb_define_method(cX509CRL, "last_update", ossl_x509crl_get_last_update, 0); + rb_define_method(cX509CRL, "last_update=", ossl_x509crl_set_last_update, 1); + rb_define_method(cX509CRL, "next_update", ossl_x509crl_get_next_update, 0); + rb_define_method(cX509CRL, "next_update=", ossl_x509crl_set_next_update, 1); + rb_define_method(cX509CRL, "revoked", ossl_x509crl_get_revoked, 0); + rb_define_method(cX509CRL, "revoked=", ossl_x509crl_set_revoked, 1); + rb_define_method(cX509CRL, "add_revoked", ossl_x509crl_add_revoked, 1); + rb_define_method(cX509CRL, "sign", ossl_x509crl_sign, 2); + rb_define_method(cX509CRL, "verify", ossl_x509crl_verify, 1); + rb_define_method(cX509CRL, "to_der", ossl_x509crl_to_der, 0); + rb_define_method(cX509CRL, "to_pem", ossl_x509crl_to_pem, 0); + rb_define_alias(cX509CRL, "to_s", "to_pem"); + rb_define_method(cX509CRL, "to_text", ossl_x509crl_to_text, 0); + rb_define_method(cX509CRL, "extensions", ossl_x509crl_get_extensions, 0); + rb_define_method(cX509CRL, "extensions=", ossl_x509crl_set_extensions, 1); + rb_define_method(cX509CRL, "add_extension", ossl_x509crl_add_extension, 1); +} + diff --git a/ext/openssl/ossl_x509ext.c b/ext/openssl/ossl_x509ext.c new file mode 100644 index 00000000..48625f85 --- /dev/null +++ b/ext/openssl/ossl_x509ext.c @@ -0,0 +1,471 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2001-2002 Michal Rokos <m.rokos@sh.cvut.cz> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#include "ossl.h" + +#define WrapX509Ext(klass, obj, ext) do { \ + if (!(ext)) { \ + ossl_raise(rb_eRuntimeError, "EXT wasn't initialized!"); \ + } \ + (obj) = Data_Wrap_Struct((klass), 0, X509_EXTENSION_free, (ext)); \ +} while (0) +#define GetX509Ext(obj, ext) do { \ + Data_Get_Struct((obj), X509_EXTENSION, (ext)); \ + if (!(ext)) { \ + ossl_raise(rb_eRuntimeError, "EXT wasn't initialized!"); \ + } \ +} while (0) +#define SafeGetX509Ext(obj, ext) do { \ + OSSL_Check_Kind((obj), cX509Ext); \ + GetX509Ext((obj), (ext)); \ +} while (0) +#define MakeX509ExtFactory(klass, obj, ctx) do { \ + if (!((ctx) = OPENSSL_malloc(sizeof(X509V3_CTX)))) \ + ossl_raise(rb_eRuntimeError, "CTX wasn't allocated!"); \ + X509V3_set_ctx((ctx), NULL, NULL, NULL, NULL, 0); \ + (obj) = Data_Wrap_Struct((klass), 0, ossl_x509extfactory_free, (ctx)); \ +} while (0) +#define GetX509ExtFactory(obj, ctx) do { \ + Data_Get_Struct((obj), X509V3_CTX, (ctx)); \ + if (!(ctx)) { \ + ossl_raise(rb_eRuntimeError, "CTX wasn't initialized!"); \ + } \ +} while (0) + +/* + * Classes + */ +VALUE cX509Ext; +VALUE cX509ExtFactory; +VALUE eX509ExtError; + +/* + * Public + */ +VALUE +ossl_x509ext_new(X509_EXTENSION *ext) +{ + X509_EXTENSION *new; + VALUE obj; + + if (!ext) { + new = X509_EXTENSION_new(); + } else { + new = X509_EXTENSION_dup(ext); + } + if (!new) { + ossl_raise(eX509ExtError, NULL); + } + WrapX509Ext(cX509Ext, obj, new); + + return obj; +} + +X509_EXTENSION * +GetX509ExtPtr(VALUE obj) +{ + X509_EXTENSION *ext; + + SafeGetX509Ext(obj, ext); + + return ext; +} + +X509_EXTENSION * +DupX509ExtPtr(VALUE obj) +{ + X509_EXTENSION *ext, *new; + + SafeGetX509Ext(obj, ext); + if (!(new = X509_EXTENSION_dup(ext))) { + ossl_raise(eX509ExtError, NULL); + } + + return new; +} + +/* + * Private + */ +/* + * Ext factory + */ +static void +ossl_x509extfactory_free(X509V3_CTX *ctx) +{ + OPENSSL_free(ctx); +} + +static VALUE +ossl_x509extfactory_alloc(VALUE klass) +{ + X509V3_CTX *ctx; + VALUE obj; + + MakeX509ExtFactory(klass, obj, ctx); + rb_iv_set(obj, "@config", Qnil); + + return obj; +} + +static VALUE +ossl_x509extfactory_set_issuer_cert(VALUE self, VALUE cert) +{ + X509V3_CTX *ctx; + + GetX509ExtFactory(self, ctx); + rb_iv_set(self, "@issuer_certificate", cert); + ctx->issuer_cert = GetX509CertPtr(cert); /* NO DUP NEEDED */ + + return cert; +} + +static VALUE +ossl_x509extfactory_set_subject_cert(VALUE self, VALUE cert) +{ + X509V3_CTX *ctx; + + GetX509ExtFactory(self, ctx); + rb_iv_set(self, "@subject_certificate", cert); + ctx->subject_cert = GetX509CertPtr(cert); /* NO DUP NEEDED */ + + return cert; +} + +static VALUE +ossl_x509extfactory_set_subject_req(VALUE self, VALUE req) +{ + X509V3_CTX *ctx; + + GetX509ExtFactory(self, ctx); + rb_iv_set(self, "@subject_request", req); + ctx->subject_req = GetX509ReqPtr(req); /* NO DUP NEEDED */ + + return req; +} + +static VALUE +ossl_x509extfactory_set_crl(VALUE self, VALUE crl) +{ + X509V3_CTX *ctx; + + GetX509ExtFactory(self, ctx); + rb_iv_set(self, "@crl", crl); + ctx->crl = GetX509CRLPtr(crl); /* NO DUP NEEDED */ + + return crl; +} + +#ifdef HAVE_X509V3_SET_NCONF +static VALUE +ossl_x509extfactory_set_config(VALUE self, VALUE config) +{ + X509V3_CTX *ctx; + CONF *conf; + + GetX509ExtFactory(self, ctx); + rb_iv_set(self, "@config", config); + conf = GetConfigPtr(config); /* NO DUP NEEDED */ + X509V3_set_nconf(ctx, conf); + + return config; +} +#else +#define ossl_x509extfactory_set_config rb_f_notimplement +#endif + +static VALUE +ossl_x509extfactory_initialize(int argc, VALUE *argv, VALUE self) +{ + /*X509V3_CTX *ctx;*/ + VALUE issuer_cert, subject_cert, subject_req, crl; + + /*GetX509ExtFactory(self, ctx);*/ + + rb_scan_args(argc, argv, "04", + &issuer_cert, &subject_cert, &subject_req, &crl); + if (!NIL_P(issuer_cert)) + ossl_x509extfactory_set_issuer_cert(self, issuer_cert); + if (!NIL_P(subject_cert)) + ossl_x509extfactory_set_subject_cert(self, subject_cert); + if (!NIL_P(subject_req)) + ossl_x509extfactory_set_subject_req(self, subject_req); + if (!NIL_P(crl)) + ossl_x509extfactory_set_crl(self, crl); + + return self; +} + +/* + * Array to X509_EXTENSION + * Structure: + * ["ln", "value", bool_critical] or + * ["sn", "value", bool_critical] or + * ["ln", "critical,value"] or the same for sn + * ["ln", "value"] => not critical + */ +static VALUE +ossl_x509extfactory_create_ext(int argc, VALUE *argv, VALUE self) +{ + X509V3_CTX *ctx; + X509_EXTENSION *ext; + VALUE oid, value, critical, valstr, obj; + int nid; +#ifdef HAVE_X509V3_EXT_NCONF_NID + VALUE rconf; + CONF *conf; +#else + static LHASH *empty_lhash; +#endif + + rb_scan_args(argc, argv, "21", &oid, &value, &critical); + StringValue(oid); + StringValue(value); + if(NIL_P(critical)) critical = Qfalse; + + nid = OBJ_ln2nid(RSTRING_PTR(oid)); + if(!nid) nid = OBJ_sn2nid(RSTRING_PTR(oid)); + if(!nid) ossl_raise(eX509ExtError, "unknown OID `%s'", RSTRING_PTR(oid)); + valstr = rb_str_new2(RTEST(critical) ? "critical," : ""); + rb_str_append(valstr, value); + GetX509ExtFactory(self, ctx); +#ifdef HAVE_X509V3_EXT_NCONF_NID + rconf = rb_iv_get(self, "@config"); + conf = NIL_P(rconf) ? NULL : GetConfigPtr(rconf); + ext = X509V3_EXT_nconf_nid(conf, ctx, nid, RSTRING_PTR(valstr)); +#else + if (!empty_lhash) empty_lhash = lh_new(NULL, NULL); + ext = X509V3_EXT_conf_nid(empty_lhash, ctx, nid, RSTRING_PTR(valstr)); +#endif + if (!ext){ + ossl_raise(eX509ExtError, "%s = %s", + RSTRING_PTR(oid), RSTRING_PTR(value)); + } + WrapX509Ext(cX509Ext, obj, ext); + + return obj; +} + +/* + * Ext + */ +static VALUE +ossl_x509ext_alloc(VALUE klass) +{ + X509_EXTENSION *ext; + VALUE obj; + + if(!(ext = X509_EXTENSION_new())){ + ossl_raise(eX509ExtError, NULL); + } + WrapX509Ext(klass, obj, ext); + + return obj; +} + +/* + * call-seq: + * OpenSSL::X509::Extension.new asn1 + * OpenSSL::X509::Extension.new name, value + * OpenSSL::X509::Extension.new name, value, critical + * + * Creates an X509 extension. + * + * The extension may be created from +asn1+ data or from an extension +name+ + * and +value+. The +name+ may be either an OID or an extension name. If + * +critical+ is true the extension is marked critical. + */ +static VALUE +ossl_x509ext_initialize(int argc, VALUE *argv, VALUE self) +{ + VALUE oid, value, critical; + const unsigned char *p; + X509_EXTENSION *ext, *x; + + GetX509Ext(self, ext); + if(rb_scan_args(argc, argv, "12", &oid, &value, &critical) == 1){ + oid = ossl_to_der_if_possible(oid); + StringValue(oid); + p = (unsigned char *)RSTRING_PTR(oid); + x = d2i_X509_EXTENSION(&ext, &p, RSTRING_LEN(oid)); + DATA_PTR(self) = ext; + if(!x) + ossl_raise(eX509ExtError, NULL); + return self; + } + rb_funcall(self, rb_intern("oid="), 1, oid); + rb_funcall(self, rb_intern("value="), 1, value); + if(argc > 2) rb_funcall(self, rb_intern("critical="), 1, critical); + + return self; +} + +static VALUE +ossl_x509ext_set_oid(VALUE self, VALUE oid) +{ + X509_EXTENSION *ext; + ASN1_OBJECT *obj; + char *s; + + s = StringValuePtr(oid); + obj = OBJ_txt2obj(s, 0); + if(!obj) obj = OBJ_txt2obj(s, 1); + if(!obj) ossl_raise(eX509ExtError, NULL); + GetX509Ext(self, ext); + X509_EXTENSION_set_object(ext, obj); + + return oid; +} + +static VALUE +ossl_x509ext_set_value(VALUE self, VALUE data) +{ + X509_EXTENSION *ext; + ASN1_OCTET_STRING *asn1s; + char *s; + + data = ossl_to_der_if_possible(data); + StringValue(data); + if(!(s = OPENSSL_malloc(RSTRING_LEN(data)))) + ossl_raise(eX509ExtError, "malloc error"); + memcpy(s, RSTRING_PTR(data), RSTRING_LEN(data)); + if(!(asn1s = ASN1_OCTET_STRING_new())){ + OPENSSL_free(s); + ossl_raise(eX509ExtError, NULL); + } + if(!M_ASN1_OCTET_STRING_set(asn1s, s, RSTRING_LENINT(data))){ + OPENSSL_free(s); + ASN1_OCTET_STRING_free(asn1s); + ossl_raise(eX509ExtError, NULL); + } + OPENSSL_free(s); + GetX509Ext(self, ext); + X509_EXTENSION_set_data(ext, asn1s); + + return data; +} + +static VALUE +ossl_x509ext_set_critical(VALUE self, VALUE flag) +{ + X509_EXTENSION *ext; + + GetX509Ext(self, ext); + X509_EXTENSION_set_critical(ext, RTEST(flag) ? 1 : 0); + + return flag; +} + +static VALUE +ossl_x509ext_get_oid(VALUE obj) +{ + X509_EXTENSION *ext; + ASN1_OBJECT *extobj; + BIO *out; + VALUE ret; + int nid; + + GetX509Ext(obj, ext); + extobj = X509_EXTENSION_get_object(ext); + if ((nid = OBJ_obj2nid(extobj)) != NID_undef) + ret = rb_str_new2(OBJ_nid2sn(nid)); + else{ + if (!(out = BIO_new(BIO_s_mem()))) + ossl_raise(eX509ExtError, NULL); + i2a_ASN1_OBJECT(out, extobj); + ret = ossl_membio2str(out); + } + + return ret; +} + +static VALUE +ossl_x509ext_get_value(VALUE obj) +{ + X509_EXTENSION *ext; + BIO *out; + VALUE ret; + + GetX509Ext(obj, ext); + if (!(out = BIO_new(BIO_s_mem()))) + ossl_raise(eX509ExtError, NULL); + if (!X509V3_EXT_print(out, ext, 0, 0)) + M_ASN1_OCTET_STRING_print(out, ext->value); + ret = ossl_membio2str(out); + + return ret; +} + +static VALUE +ossl_x509ext_get_critical(VALUE obj) +{ + X509_EXTENSION *ext; + + GetX509Ext(obj, ext); + return X509_EXTENSION_get_critical(ext) ? Qtrue : Qfalse; +} + +static VALUE +ossl_x509ext_to_der(VALUE obj) +{ + X509_EXTENSION *ext; + unsigned char *p; + long len; + VALUE str; + + GetX509Ext(obj, ext); + if((len = i2d_X509_EXTENSION(ext, NULL)) <= 0) + ossl_raise(eX509ExtError, NULL); + str = rb_str_new(0, len); + p = (unsigned char *)RSTRING_PTR(str); + if(i2d_X509_EXTENSION(ext, &p) < 0) + ossl_raise(eX509ExtError, NULL); + ossl_str_adjust(str, p); + + return str; +} + +/* + * INIT + */ +void +Init_ossl_x509ext(void) +{ + eX509ExtError = rb_define_class_under(mX509, "ExtensionError", eOSSLError); + + cX509ExtFactory = rb_define_class_under(mX509, "ExtensionFactory", rb_cObject); + + rb_define_alloc_func(cX509ExtFactory, ossl_x509extfactory_alloc); + rb_define_method(cX509ExtFactory, "initialize", ossl_x509extfactory_initialize, -1); + + rb_attr(cX509ExtFactory, rb_intern("issuer_certificate"), 1, 0, Qfalse); + rb_attr(cX509ExtFactory, rb_intern("subject_certificate"), 1, 0, Qfalse); + rb_attr(cX509ExtFactory, rb_intern("subject_request"), 1, 0, Qfalse); + rb_attr(cX509ExtFactory, rb_intern("crl"), 1, 0, Qfalse); + rb_attr(cX509ExtFactory, rb_intern("config"), 1, 0, Qfalse); + + rb_define_method(cX509ExtFactory, "issuer_certificate=", ossl_x509extfactory_set_issuer_cert, 1); + rb_define_method(cX509ExtFactory, "subject_certificate=", ossl_x509extfactory_set_subject_cert, 1); + rb_define_method(cX509ExtFactory, "subject_request=", ossl_x509extfactory_set_subject_req, 1); + rb_define_method(cX509ExtFactory, "crl=", ossl_x509extfactory_set_crl, 1); + rb_define_method(cX509ExtFactory, "config=", ossl_x509extfactory_set_config, 1); + rb_define_method(cX509ExtFactory, "create_ext", ossl_x509extfactory_create_ext, -1); + + cX509Ext = rb_define_class_under(mX509, "Extension", rb_cObject); + rb_define_alloc_func(cX509Ext, ossl_x509ext_alloc); + rb_define_method(cX509Ext, "initialize", ossl_x509ext_initialize, -1); + rb_define_method(cX509Ext, "oid=", ossl_x509ext_set_oid, 1); + rb_define_method(cX509Ext, "value=", ossl_x509ext_set_value, 1); + rb_define_method(cX509Ext, "critical=", ossl_x509ext_set_critical, 1); + rb_define_method(cX509Ext, "oid", ossl_x509ext_get_oid, 0); + rb_define_method(cX509Ext, "value", ossl_x509ext_get_value, 0); + rb_define_method(cX509Ext, "critical?", ossl_x509ext_get_critical, 0); + rb_define_method(cX509Ext, "to_der", ossl_x509ext_to_der, 0); +} diff --git a/ext/openssl/ossl_x509name.c b/ext/openssl/ossl_x509name.c new file mode 100644 index 00000000..cf541e56 --- /dev/null +++ b/ext/openssl/ossl_x509name.c @@ -0,0 +1,510 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2001 Michal Rokos <m.rokos@sh.cvut.cz> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#include "ossl.h" + +#define WrapX509Name(klass, obj, name) do { \ + if (!(name)) { \ + ossl_raise(rb_eRuntimeError, "Name wasn't initialized."); \ + } \ + (obj) = Data_Wrap_Struct((klass), 0, X509_NAME_free, (name)); \ +} while (0) +#define GetX509Name(obj, name) do { \ + Data_Get_Struct((obj), X509_NAME, (name)); \ + if (!(name)) { \ + ossl_raise(rb_eRuntimeError, "Name wasn't initialized."); \ + } \ +} while (0) +#define SafeGetX509Name(obj, name) do { \ + OSSL_Check_Kind((obj), cX509Name); \ + GetX509Name((obj), (name)); \ +} while (0) + +#define OBJECT_TYPE_TEMPLATE \ + rb_const_get(cX509Name, rb_intern("OBJECT_TYPE_TEMPLATE")) +#define DEFAULT_OBJECT_TYPE \ + rb_const_get(cX509Name, rb_intern("DEFAULT_OBJECT_TYPE")) + +/* + * Classes + */ +VALUE cX509Name; +VALUE eX509NameError; + +/* + * Public + */ +VALUE +ossl_x509name_new(X509_NAME *name) +{ + X509_NAME *new; + VALUE obj; + + if (!name) { + new = X509_NAME_new(); + } else { + new = X509_NAME_dup(name); + } + if (!new) { + ossl_raise(eX509NameError, NULL); + } + WrapX509Name(cX509Name, obj, new); + + return obj; +} + +X509_NAME * +GetX509NamePtr(VALUE obj) +{ + X509_NAME *name; + + SafeGetX509Name(obj, name); + + return name; +} + +/* + * Private + */ +static VALUE +ossl_x509name_alloc(VALUE klass) +{ + X509_NAME *name; + VALUE obj; + + if (!(name = X509_NAME_new())) { + ossl_raise(eX509NameError, NULL); + } + WrapX509Name(klass, obj, name); + + return obj; +} + +static ID id_aref; +static VALUE ossl_x509name_add_entry(int, VALUE*, VALUE); +#define rb_aref(obj, key) rb_funcall((obj), id_aref, 1, (key)) + +static VALUE +ossl_x509name_init_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, args)) +{ + VALUE self = rb_ary_entry(args, 0); + VALUE template = rb_ary_entry(args, 1); + VALUE entry[3]; + + Check_Type(i, T_ARRAY); + entry[0] = rb_ary_entry(i, 0); + entry[1] = rb_ary_entry(i, 1); + entry[2] = rb_ary_entry(i, 2); + if(NIL_P(entry[2])) entry[2] = rb_aref(template, entry[0]); + if(NIL_P(entry[2])) entry[2] = DEFAULT_OBJECT_TYPE; + ossl_x509name_add_entry(3, entry, self); + + return Qnil; +} + +/* + * call-seq: + * X509::Name.new => name + * X509::Name.new(der) => name + * X509::Name.new(distinguished_name) => name + * X509::Name.new(distinguished_name, template) => name + * + * Creates a new Name. + * + * A name may be created from a DER encoded string +der+, an Array + * representing a +distinguished_name+ or a +distinguished_name+ along with a + * +template+. + * + * name = OpenSSL::X509::Name.new [['CN', 'nobody'], ['DC', 'example']] + * + * name = OpenSSL::X509::Name.new name.to_der + * + * See add_entry for a description of the +distinguished_name+ Array's + * contents + */ +static VALUE +ossl_x509name_initialize(int argc, VALUE *argv, VALUE self) +{ + X509_NAME *name; + VALUE arg, template; + + GetX509Name(self, name); + if (rb_scan_args(argc, argv, "02", &arg, &template) == 0) { + return self; + } + else { + VALUE tmp = rb_check_array_type(arg); + if (!NIL_P(tmp)) { + VALUE args; + if(NIL_P(template)) template = OBJECT_TYPE_TEMPLATE; + args = rb_ary_new3(2, self, template); + rb_block_call(tmp, rb_intern("each"), 0, 0, ossl_x509name_init_i, args); + } + else{ + const unsigned char *p; + VALUE str = ossl_to_der_if_possible(arg); + X509_NAME *x; + StringValue(str); + p = (unsigned char *)RSTRING_PTR(str); + x = d2i_X509_NAME(&name, &p, RSTRING_LEN(str)); + DATA_PTR(self) = name; + if(!x){ + ossl_raise(eX509NameError, NULL); + } + } + } + + return self; +} + +/* + * call-seq: + * name.add_entry(oid, value [, type]) => self + * + * Adds a new entry with the given +oid+ and +value+ to this name. The +oid+ + * is an object identifier defined in ASN.1. Some common OIDs are: + * + * C:: Country Name + * CN:: Common Name + * DC:: Domain Component + * O:: Organization Name + * OU:: Organizational Unit Name + * ST:: State or Province Name + */ +static +VALUE ossl_x509name_add_entry(int argc, VALUE *argv, VALUE self) +{ + X509_NAME *name; + VALUE oid, value, type; + const char *oid_name; + + rb_scan_args(argc, argv, "21", &oid, &value, &type); + oid_name = StringValueCStr(oid); + StringValue(value); + if(NIL_P(type)) type = rb_aref(OBJECT_TYPE_TEMPLATE, oid); + GetX509Name(self, name); + if (!X509_NAME_add_entry_by_txt(name, oid_name, NUM2INT(type), + (const unsigned char *)RSTRING_PTR(value), RSTRING_LENINT(value), -1, 0)) { + ossl_raise(eX509NameError, NULL); + } + + return self; +} + +static VALUE +ossl_x509name_to_s_old(VALUE self) +{ + X509_NAME *name; + char *buf; + VALUE str; + + GetX509Name(self, name); + buf = X509_NAME_oneline(name, NULL, 0); + str = rb_str_new2(buf); + OPENSSL_free(buf); + + return str; +} + +/* + * call-seq: + * name.to_s => string + * name.to_s(flags) => string + * + * Returns this name as a Distinguished Name string. +flags+ may be one of: + * + * * OpenSSL::X509::Name::COMPAT + * * OpenSSL::X509::Name::RFC2253 + * * OpenSSL::X509::Name::ONELINE + * * OpenSSL::X509::Name::MULTILINE + */ +static VALUE +ossl_x509name_to_s(int argc, VALUE *argv, VALUE self) +{ + X509_NAME *name; + VALUE flag, str; + BIO *out; + unsigned long iflag; + + rb_scan_args(argc, argv, "01", &flag); + if (NIL_P(flag)) + return ossl_x509name_to_s_old(self); + else iflag = NUM2ULONG(flag); + if (!(out = BIO_new(BIO_s_mem()))) + ossl_raise(eX509NameError, NULL); + GetX509Name(self, name); + if (!X509_NAME_print_ex(out, name, 0, iflag)){ + BIO_free(out); + ossl_raise(eX509NameError, NULL); + } + str = ossl_membio2str(out); + + return str; +} + +/* + * call-seq: + * name.to_a => [[name, data, type], ...] + * + * Returns an Array representation of the distinguished name suitable for + * passing to ::new + */ +static VALUE +ossl_x509name_to_a(VALUE self) +{ + X509_NAME *name; + X509_NAME_ENTRY *entry; + int i,entries,nid; + char long_name[512]; + const char *short_name; + VALUE ary, vname, ret; + + GetX509Name(self, name); + entries = X509_NAME_entry_count(name); + if (entries < 0) { + OSSL_Debug("name entries < 0!"); + return rb_ary_new(); + } + ret = rb_ary_new2(entries); + for (i=0; i<entries; i++) { + if (!(entry = X509_NAME_get_entry(name, i))) { + ossl_raise(eX509NameError, NULL); + } + if (!i2t_ASN1_OBJECT(long_name, sizeof(long_name), entry->object)) { + ossl_raise(eX509NameError, NULL); + } + nid = OBJ_ln2nid(long_name); + if (nid == NID_undef) { + vname = rb_str_new2((const char *) &long_name); + } else { + short_name = OBJ_nid2sn(nid); + vname = rb_str_new2(short_name); /*do not free*/ + } + ary = rb_ary_new3(3, + vname, + rb_str_new((const char *)entry->value->data, entry->value->length), + INT2FIX(entry->value->type)); + rb_ary_push(ret, ary); + } + return ret; +} + +static int +ossl_x509name_cmp0(VALUE self, VALUE other) +{ + X509_NAME *name1, *name2; + + GetX509Name(self, name1); + SafeGetX509Name(other, name2); + + return X509_NAME_cmp(name1, name2); +} + +/* + * call-seq: + * name.cmp other => integer + * name.<=> other => integer + * + * Compares this Name with +other+ and returns 0 if they are the same and -1 or + * +1 if they are greater or less than each other respectively. + */ +static VALUE +ossl_x509name_cmp(VALUE self, VALUE other) +{ + int result; + + result = ossl_x509name_cmp0(self, other); + if (result < 0) return INT2FIX(-1); + if (result > 1) return INT2FIX(1); + + return INT2FIX(0); +} + +/* + * call-seq: + * name.eql? other => boolean + * + * Returns true if +name+ and +other+ refer to the same hash key. + */ +static VALUE +ossl_x509name_eql(VALUE self, VALUE other) +{ + int result; + + if(CLASS_OF(other) != cX509Name) return Qfalse; + result = ossl_x509name_cmp0(self, other); + + return (result == 0) ? Qtrue : Qfalse; +} + +/* + * call-seq: + * name.hash => integer + * + * The hash value returned is suitable for use as a certificate's filename in + * a CA path. + */ +static VALUE +ossl_x509name_hash(VALUE self) +{ + X509_NAME *name; + unsigned long hash; + + GetX509Name(self, name); + + hash = X509_NAME_hash(name); + + return ULONG2NUM(hash); +} + +#ifdef HAVE_X509_NAME_HASH_OLD +/* + * call-seq: + * name.hash_old => integer + * + * Returns an MD5 based hash used in OpenSSL 0.9.X. + */ +static VALUE +ossl_x509name_hash_old(VALUE self) +{ + X509_NAME *name; + unsigned long hash; + + GetX509Name(self, name); + + hash = X509_NAME_hash_old(name); + + return ULONG2NUM(hash); +} +#endif + +/* + * call-seq: + * name.to_der => string + * + * Converts the name to DER encoding + */ +static VALUE +ossl_x509name_to_der(VALUE self) +{ + X509_NAME *name; + VALUE str; + long len; + unsigned char *p; + + GetX509Name(self, name); + if((len = i2d_X509_NAME(name, NULL)) <= 0) + ossl_raise(eX509NameError, NULL); + str = rb_str_new(0, len); + p = (unsigned char *)RSTRING_PTR(str); + if(i2d_X509_NAME(name, &p) <= 0) + ossl_raise(eX509NameError, NULL); + ossl_str_adjust(str, p); + + return str; +} + +/* + * Document-class: OpenSSL::X509::Name + * + * An X.509 name represents a hostname, email address or other entity + * associated with a public key. + * + * You can create a Name by parsing a distinguished name String or by + * supplying the distinguished name as an Array. + * + * name = OpenSSL::X509::Name.parse 'CN=nobody/DC=example' + * + * name = OpenSSL::X509::Name.new [['CN', 'nobody'], ['DC', 'example']] + */ + +void +Init_ossl_x509name(void) +{ + VALUE utf8str, ptrstr, ia5str, hash; + + id_aref = rb_intern("[]"); + eX509NameError = rb_define_class_under(mX509, "NameError", eOSSLError); + cX509Name = rb_define_class_under(mX509, "Name", rb_cObject); + + rb_include_module(cX509Name, rb_mComparable); + + rb_define_alloc_func(cX509Name, ossl_x509name_alloc); + rb_define_method(cX509Name, "initialize", ossl_x509name_initialize, -1); + rb_define_method(cX509Name, "add_entry", ossl_x509name_add_entry, -1); + rb_define_method(cX509Name, "to_s", ossl_x509name_to_s, -1); + rb_define_method(cX509Name, "to_a", ossl_x509name_to_a, 0); + rb_define_method(cX509Name, "cmp", ossl_x509name_cmp, 1); + rb_define_alias(cX509Name, "<=>", "cmp"); + rb_define_method(cX509Name, "eql?", ossl_x509name_eql, 1); + rb_define_method(cX509Name, "hash", ossl_x509name_hash, 0); +#ifdef HAVE_X509_NAME_HASH_OLD + rb_define_method(cX509Name, "hash_old", ossl_x509name_hash_old, 0); +#endif + rb_define_method(cX509Name, "to_der", ossl_x509name_to_der, 0); + + utf8str = INT2NUM(V_ASN1_UTF8STRING); + ptrstr = INT2NUM(V_ASN1_PRINTABLESTRING); + ia5str = INT2NUM(V_ASN1_IA5STRING); + + /* Document-const: DEFAULT_OBJECT_TYPE + * + * The default object type for name entries. + */ + rb_define_const(cX509Name, "DEFAULT_OBJECT_TYPE", utf8str); + hash = rb_hash_new(); + RHASH_SET_IFNONE(hash, utf8str); + rb_hash_aset(hash, rb_str_new2("C"), ptrstr); + rb_hash_aset(hash, rb_str_new2("countryName"), ptrstr); + rb_hash_aset(hash, rb_str_new2("serialNumber"), ptrstr); + rb_hash_aset(hash, rb_str_new2("dnQualifier"), ptrstr); + rb_hash_aset(hash, rb_str_new2("DC"), ia5str); + rb_hash_aset(hash, rb_str_new2("domainComponent"), ia5str); + rb_hash_aset(hash, rb_str_new2("emailAddress"), ia5str); + + /* Document-const: OBJECT_TYPE_TEMPLATE + * + * The default object type template for name entries. + */ + rb_define_const(cX509Name, "OBJECT_TYPE_TEMPLATE", hash); + + /* Document-const: COMPAT + * + * A flag for #to_s. + * + * Breaks the name returned into multiple lines if longer than 80 + * characters. + */ + rb_define_const(cX509Name, "COMPAT", ULONG2NUM(XN_FLAG_COMPAT)); + + /* Document-const: RFC2253 + * + * A flag for #to_s. + * + * Returns an RFC2253 format name. + */ + rb_define_const(cX509Name, "RFC2253", ULONG2NUM(XN_FLAG_RFC2253)); + + /* Document-const: ONELINE + * + * A flag for #to_s. + * + * Returns a more readable format than RFC2253. + */ + rb_define_const(cX509Name, "ONELINE", ULONG2NUM(XN_FLAG_ONELINE)); + + /* Document-const: MULTILINE + * + * A flag for #to_s. + * + * Returns a multiline format. + */ + rb_define_const(cX509Name, "MULTILINE", ULONG2NUM(XN_FLAG_MULTILINE)); +} diff --git a/ext/openssl/ossl_x509req.c b/ext/openssl/ossl_x509req.c new file mode 100644 index 00000000..a7f5dd20 --- /dev/null +++ b/ext/openssl/ossl_x509req.c @@ -0,0 +1,468 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2001-2002 Michal Rokos <m.rokos@sh.cvut.cz> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#include "ossl.h" + +#define WrapX509Req(klass, obj, req) do { \ + if (!(req)) { \ + ossl_raise(rb_eRuntimeError, "Req wasn't initialized!"); \ + } \ + (obj) = Data_Wrap_Struct((klass), 0, X509_REQ_free, (req)); \ +} while (0) +#define GetX509Req(obj, req) do { \ + Data_Get_Struct((obj), X509_REQ, (req)); \ + if (!(req)) { \ + ossl_raise(rb_eRuntimeError, "Req wasn't initialized!"); \ + } \ +} while (0) +#define SafeGetX509Req(obj, req) do { \ + OSSL_Check_Kind((obj), cX509Req); \ + GetX509Req((obj), (req)); \ +} while (0) + +/* + * Classes + */ +VALUE cX509Req; +VALUE eX509ReqError; + +/* + * Public functions + */ +VALUE +ossl_x509req_new(X509_REQ *req) +{ + X509_REQ *new; + VALUE obj; + + if (!req) { + new = X509_REQ_new(); + } else { + new = X509_REQ_dup(req); + } + if (!new) { + ossl_raise(eX509ReqError, NULL); + } + WrapX509Req(cX509Req, obj, new); + + return obj; +} + +X509_REQ * +GetX509ReqPtr(VALUE obj) +{ + X509_REQ *req; + + SafeGetX509Req(obj, req); + + return req; +} + +X509_REQ * +DupX509ReqPtr(VALUE obj) +{ + X509_REQ *req, *new; + + SafeGetX509Req(obj, req); + if (!(new = X509_REQ_dup(req))) { + ossl_raise(eX509ReqError, NULL); + } + + return new; +} + +/* + * Private functions + */ +static VALUE +ossl_x509req_alloc(VALUE klass) +{ + X509_REQ *req; + VALUE obj; + + if (!(req = X509_REQ_new())) { + ossl_raise(eX509ReqError, NULL); + } + WrapX509Req(klass, obj, req); + + return obj; +} + +static VALUE +ossl_x509req_initialize(int argc, VALUE *argv, VALUE self) +{ + BIO *in; + X509_REQ *req, *x = DATA_PTR(self); + VALUE arg; + + if (rb_scan_args(argc, argv, "01", &arg) == 0) { + return self; + } + arg = ossl_to_der_if_possible(arg); + in = ossl_obj2bio(arg); + req = PEM_read_bio_X509_REQ(in, &x, NULL, NULL); + DATA_PTR(self) = x; + if (!req) { + OSSL_BIO_reset(in); + req = d2i_X509_REQ_bio(in, &x); + DATA_PTR(self) = x; + } + BIO_free(in); + if (!req) ossl_raise(eX509ReqError, NULL); + + return self; +} + +static VALUE +ossl_x509req_copy(VALUE self, VALUE other) +{ + X509_REQ *a, *b, *req; + + rb_check_frozen(self); + if (self == other) return self; + GetX509Req(self, a); + SafeGetX509Req(other, b); + if (!(req = X509_REQ_dup(b))) { + ossl_raise(eX509ReqError, NULL); + } + X509_REQ_free(a); + DATA_PTR(self) = req; + + return self; +} + +static VALUE +ossl_x509req_to_pem(VALUE self) +{ + X509_REQ *req; + BIO *out; + BUF_MEM *buf; + VALUE str; + + GetX509Req(self, req); + if (!(out = BIO_new(BIO_s_mem()))) { + ossl_raise(eX509ReqError, NULL); + } + if (!PEM_write_bio_X509_REQ(out, req)) { + BIO_free(out); + ossl_raise(eX509ReqError, NULL); + } + BIO_get_mem_ptr(out, &buf); + str = rb_str_new(buf->data, buf->length); + BIO_free(out); + + return str; +} + +static VALUE +ossl_x509req_to_der(VALUE self) +{ + X509_REQ *req; + VALUE str; + long len; + unsigned char *p; + + GetX509Req(self, req); + if ((len = i2d_X509_REQ(req, NULL)) <= 0) + ossl_raise(eX509ReqError, NULL); + str = rb_str_new(0, len); + p = (unsigned char *)RSTRING_PTR(str); + if (i2d_X509_REQ(req, &p) <= 0) + ossl_raise(eX509ReqError, NULL); + ossl_str_adjust(str, p); + + return str; +} + +static VALUE +ossl_x509req_to_text(VALUE self) +{ + X509_REQ *req; + BIO *out; + BUF_MEM *buf; + VALUE str; + + GetX509Req(self, req); + if (!(out = BIO_new(BIO_s_mem()))) { + ossl_raise(eX509ReqError, NULL); + } + if (!X509_REQ_print(out, req)) { + BIO_free(out); + ossl_raise(eX509ReqError, NULL); + } + BIO_get_mem_ptr(out, &buf); + str = rb_str_new(buf->data, buf->length); + BIO_free(out); + + return str; +} + +#if 0 +/* + * Makes X509 from X509_REQuest + */ +static VALUE +ossl_x509req_to_x509(VALUE self, VALUE days, VALUE key) +{ + X509_REQ *req; + X509 *x509; + + GetX509Req(self, req); + ... + if (!(x509 = X509_REQ_to_X509(req, d, pkey))) { + ossl_raise(eX509ReqError, NULL); + } + + return ossl_x509_new(x509); +} +#endif + +static VALUE +ossl_x509req_get_version(VALUE self) +{ + X509_REQ *req; + long version; + + GetX509Req(self, req); + version = X509_REQ_get_version(req); + + return LONG2FIX(version); +} + +static VALUE +ossl_x509req_set_version(VALUE self, VALUE version) +{ + X509_REQ *req; + long ver; + + if ((ver = FIX2LONG(version)) < 0) { + ossl_raise(eX509ReqError, "version must be >= 0!"); + } + GetX509Req(self, req); + if (!X509_REQ_set_version(req, ver)) { + ossl_raise(eX509ReqError, NULL); + } + + return version; +} + +static VALUE +ossl_x509req_get_subject(VALUE self) +{ + X509_REQ *req; + X509_NAME *name; + + GetX509Req(self, req); + if (!(name = X509_REQ_get_subject_name(req))) { /* NO DUP - don't free */ + ossl_raise(eX509ReqError, NULL); + } + + return ossl_x509name_new(name); +} + +static VALUE +ossl_x509req_set_subject(VALUE self, VALUE subject) +{ + X509_REQ *req; + + GetX509Req(self, req); + /* DUPs name */ + if (!X509_REQ_set_subject_name(req, GetX509NamePtr(subject))) { + ossl_raise(eX509ReqError, NULL); + } + + return subject; +} + +static VALUE +ossl_x509req_get_signature_algorithm(VALUE self) +{ + X509_REQ *req; + BIO *out; + BUF_MEM *buf; + VALUE str; + + GetX509Req(self, req); + + if (!(out = BIO_new(BIO_s_mem()))) { + ossl_raise(eX509ReqError, NULL); + } + if (!i2a_ASN1_OBJECT(out, req->sig_alg->algorithm)) { + BIO_free(out); + ossl_raise(eX509ReqError, NULL); + } + BIO_get_mem_ptr(out, &buf); + str = rb_str_new(buf->data, buf->length); + BIO_free(out); + return str; +} + +static VALUE +ossl_x509req_get_public_key(VALUE self) +{ + X509_REQ *req; + EVP_PKEY *pkey; + + GetX509Req(self, req); + if (!(pkey = X509_REQ_get_pubkey(req))) { /* adds reference */ + ossl_raise(eX509ReqError, NULL); + } + + return ossl_pkey_new(pkey); /* NO DUP - OK */ +} + +static VALUE +ossl_x509req_set_public_key(VALUE self, VALUE key) +{ + X509_REQ *req; + EVP_PKEY *pkey; + + GetX509Req(self, req); + pkey = GetPKeyPtr(key); /* NO NEED TO DUP */ + if (!X509_REQ_set_pubkey(req, pkey)) { + ossl_raise(eX509ReqError, NULL); + } + + return key; +} + +static VALUE +ossl_x509req_sign(VALUE self, VALUE key, VALUE digest) +{ + X509_REQ *req; + EVP_PKEY *pkey; + const EVP_MD *md; + + GetX509Req(self, req); + pkey = GetPrivPKeyPtr(key); /* NO NEED TO DUP */ + md = GetDigestPtr(digest); + if (!X509_REQ_sign(req, pkey, md)) { + ossl_raise(eX509ReqError, NULL); + } + + return self; +} + +/* + * Checks that cert signature is made with PRIVversion of this PUBLIC 'key' + */ +static VALUE +ossl_x509req_verify(VALUE self, VALUE key) +{ + X509_REQ *req; + EVP_PKEY *pkey; + int i; + + GetX509Req(self, req); + pkey = GetPKeyPtr(key); /* NO NEED TO DUP */ + if ((i = X509_REQ_verify(req, pkey)) < 0) { + ossl_raise(eX509ReqError, NULL); + } + if (i > 0) { + return Qtrue; + } + + return Qfalse; +} + +static VALUE +ossl_x509req_get_attributes(VALUE self) +{ + X509_REQ *req; + int count, i; + X509_ATTRIBUTE *attr; + VALUE ary; + + GetX509Req(self, req); + + count = X509_REQ_get_attr_count(req); + if (count < 0) { + OSSL_Debug("count < 0???"); + return rb_ary_new(); + } + ary = rb_ary_new2(count); + for (i=0; i<count; i++) { + attr = X509_REQ_get_attr(req, i); + rb_ary_push(ary, ossl_x509attr_new(attr)); + } + + return ary; +} + +static VALUE +ossl_x509req_set_attributes(VALUE self, VALUE ary) +{ + X509_REQ *req; + X509_ATTRIBUTE *attr; + int i; + VALUE item; + + Check_Type(ary, T_ARRAY); + for (i=0;i<RARRAY_LEN(ary); i++) { + OSSL_Check_Kind(RARRAY_PTR(ary)[i], cX509Attr); + } + GetX509Req(self, req); + sk_X509_ATTRIBUTE_pop_free(req->req_info->attributes, X509_ATTRIBUTE_free); + req->req_info->attributes = NULL; + for (i=0;i<RARRAY_LEN(ary); i++) { + item = RARRAY_PTR(ary)[i]; + attr = DupX509AttrPtr(item); + if (!X509_REQ_add1_attr(req, attr)) { + ossl_raise(eX509ReqError, NULL); + } + } + return ary; +} + +static VALUE +ossl_x509req_add_attribute(VALUE self, VALUE attr) +{ + X509_REQ *req; + + GetX509Req(self, req); + if (!X509_REQ_add1_attr(req, DupX509AttrPtr(attr))) { + ossl_raise(eX509ReqError, NULL); + } + + return attr; +} + +/* + * X509_REQUEST init + */ +void +Init_ossl_x509req(void) +{ + eX509ReqError = rb_define_class_under(mX509, "RequestError", eOSSLError); + + cX509Req = rb_define_class_under(mX509, "Request", rb_cObject); + + rb_define_alloc_func(cX509Req, ossl_x509req_alloc); + rb_define_method(cX509Req, "initialize", ossl_x509req_initialize, -1); + rb_define_copy_func(cX509Req, ossl_x509req_copy); + + rb_define_method(cX509Req, "to_pem", ossl_x509req_to_pem, 0); + rb_define_method(cX509Req, "to_der", ossl_x509req_to_der, 0); + rb_define_alias(cX509Req, "to_s", "to_pem"); + rb_define_method(cX509Req, "to_text", ossl_x509req_to_text, 0); + rb_define_method(cX509Req, "version", ossl_x509req_get_version, 0); + rb_define_method(cX509Req, "version=", ossl_x509req_set_version, 1); + rb_define_method(cX509Req, "subject", ossl_x509req_get_subject, 0); + rb_define_method(cX509Req, "subject=", ossl_x509req_set_subject, 1); + rb_define_method(cX509Req, "signature_algorithm", ossl_x509req_get_signature_algorithm, 0); + rb_define_method(cX509Req, "public_key", ossl_x509req_get_public_key, 0); + rb_define_method(cX509Req, "public_key=", ossl_x509req_set_public_key, 1); + rb_define_method(cX509Req, "sign", ossl_x509req_sign, 2); + rb_define_method(cX509Req, "verify", ossl_x509req_verify, 1); + rb_define_method(cX509Req, "attributes", ossl_x509req_get_attributes, 0); + rb_define_method(cX509Req, "attributes=", ossl_x509req_set_attributes, 1); + rb_define_method(cX509Req, "add_attribute", ossl_x509req_add_attribute, 1); +} + diff --git a/ext/openssl/ossl_x509revoked.c b/ext/openssl/ossl_x509revoked.c new file mode 100644 index 00000000..c98a95ea --- /dev/null +++ b/ext/openssl/ossl_x509revoked.c @@ -0,0 +1,229 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2001-2002 Michal Rokos <m.rokos@sh.cvut.cz> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#include "ossl.h" + +#define WrapX509Rev(klass, obj, rev) do { \ + if (!(rev)) { \ + ossl_raise(rb_eRuntimeError, "REV wasn't initialized!"); \ + } \ + (obj) = Data_Wrap_Struct((klass), 0, X509_REVOKED_free, (rev)); \ +} while (0) +#define GetX509Rev(obj, rev) do { \ + Data_Get_Struct((obj), X509_REVOKED, (rev)); \ + if (!(rev)) { \ + ossl_raise(rb_eRuntimeError, "REV wasn't initialized!"); \ + } \ +} while (0) +#define SafeGetX509Rev(obj, rev) do { \ + OSSL_Check_Kind((obj), cX509Rev); \ + GetX509Rev((obj), (rev)); \ +} while (0) + +/* + * Classes + */ +VALUE cX509Rev; +VALUE eX509RevError; + +/* + * PUBLIC + */ +VALUE +ossl_x509revoked_new(X509_REVOKED *rev) +{ + X509_REVOKED *new; + VALUE obj; + + if (!rev) { + new = X509_REVOKED_new(); + } else { + new = X509_REVOKED_dup(rev); + } + if (!new) { + ossl_raise(eX509RevError, NULL); + } + WrapX509Rev(cX509Rev, obj, new); + + return obj; +} + +X509_REVOKED * +DupX509RevokedPtr(VALUE obj) +{ + X509_REVOKED *rev, *new; + + SafeGetX509Rev(obj, rev); + if (!(new = X509_REVOKED_dup(rev))) { + ossl_raise(eX509RevError, NULL); + } + + return new; +} + +/* + * PRIVATE + */ +static VALUE +ossl_x509revoked_alloc(VALUE klass) +{ + X509_REVOKED *rev; + VALUE obj; + + if (!(rev = X509_REVOKED_new())) { + ossl_raise(eX509RevError, NULL); + } + WrapX509Rev(klass, obj, rev); + + return obj; +} + +static VALUE +ossl_x509revoked_initialize(int argc, VALUE *argv, VALUE self) +{ + /* EMPTY */ + return self; +} + +static VALUE +ossl_x509revoked_get_serial(VALUE self) +{ + X509_REVOKED *rev; + + GetX509Rev(self, rev); + + return asn1integer_to_num(rev->serialNumber); +} + +static VALUE +ossl_x509revoked_set_serial(VALUE self, VALUE num) +{ + X509_REVOKED *rev; + + GetX509Rev(self, rev); + rev->serialNumber = num_to_asn1integer(num, rev->serialNumber); + + return num; +} + +static VALUE +ossl_x509revoked_get_time(VALUE self) +{ + X509_REVOKED *rev; + + GetX509Rev(self, rev); + + return asn1time_to_time(rev->revocationDate); +} + +static VALUE +ossl_x509revoked_set_time(VALUE self, VALUE time) +{ + X509_REVOKED *rev; + time_t sec; + + sec = time_to_time_t(time); + GetX509Rev(self, rev); + if (!X509_time_adj(rev->revocationDate, 0, &sec)) { + ossl_raise(eX509RevError, NULL); + } + + return time; +} +/* + * Gets X509v3 extensions as array of X509Ext objects + */ +static VALUE +ossl_x509revoked_get_extensions(VALUE self) +{ + X509_REVOKED *rev; + int count, i; + X509_EXTENSION *ext; + VALUE ary; + + GetX509Rev(self, rev); + count = X509_REVOKED_get_ext_count(rev); + if (count < 0) { + OSSL_Debug("count < 0???"); + return rb_ary_new(); + } + ary = rb_ary_new2(count); + for (i=0; i<count; i++) { + ext = X509_REVOKED_get_ext(rev, i); + rb_ary_push(ary, ossl_x509ext_new(ext)); + } + + return ary; +} + +/* + * Sets X509_EXTENSIONs + */ +static VALUE +ossl_x509revoked_set_extensions(VALUE self, VALUE ary) +{ + X509_REVOKED *rev; + X509_EXTENSION *ext; + int i; + VALUE item; + + Check_Type(ary, T_ARRAY); + for (i=0; i<RARRAY_LEN(ary); i++) { + OSSL_Check_Kind(RARRAY_PTR(ary)[i], cX509Ext); + } + GetX509Rev(self, rev); + sk_X509_EXTENSION_pop_free(rev->extensions, X509_EXTENSION_free); + rev->extensions = NULL; + for (i=0; i<RARRAY_LEN(ary); i++) { + item = RARRAY_PTR(ary)[i]; + ext = DupX509ExtPtr(item); + if(!X509_REVOKED_add_ext(rev, ext, -1)) { + ossl_raise(eX509RevError, NULL); + } + } + + return ary; +} + +static VALUE +ossl_x509revoked_add_extension(VALUE self, VALUE ext) +{ + X509_REVOKED *rev; + + GetX509Rev(self, rev); + if(!X509_REVOKED_add_ext(rev, DupX509ExtPtr(ext), -1)) { + ossl_raise(eX509RevError, NULL); + } + + return ext; +} + +/* + * INIT + */ +void +Init_ossl_x509revoked(void) +{ + eX509RevError = rb_define_class_under(mX509, "RevokedError", eOSSLError); + + cX509Rev = rb_define_class_under(mX509, "Revoked", rb_cObject); + + rb_define_alloc_func(cX509Rev, ossl_x509revoked_alloc); + rb_define_method(cX509Rev, "initialize", ossl_x509revoked_initialize, -1); + + rb_define_method(cX509Rev, "serial", ossl_x509revoked_get_serial, 0); + rb_define_method(cX509Rev, "serial=", ossl_x509revoked_set_serial, 1); + rb_define_method(cX509Rev, "time", ossl_x509revoked_get_time, 0); + rb_define_method(cX509Rev, "time=", ossl_x509revoked_set_time, 1); + rb_define_method(cX509Rev, "extensions", ossl_x509revoked_get_extensions, 0); + rb_define_method(cX509Rev, "extensions=", ossl_x509revoked_set_extensions, 1); + rb_define_method(cX509Rev, "add_extension", ossl_x509revoked_add_extension, 1); +} + diff --git a/ext/openssl/ossl_x509store.c b/ext/openssl/ossl_x509store.c new file mode 100644 index 00000000..dd924bf9 --- /dev/null +++ b/ext/openssl/ossl_x509store.c @@ -0,0 +1,677 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2001-2002 Michal Rokos <m.rokos@sh.cvut.cz> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#include "ossl.h" + +#define WrapX509Store(klass, obj, st) do { \ + if (!(st)) { \ + ossl_raise(rb_eRuntimeError, "STORE wasn't initialized!"); \ + } \ + (obj) = Data_Wrap_Struct((klass), 0, X509_STORE_free, (st)); \ +} while (0) +#define GetX509Store(obj, st) do { \ + Data_Get_Struct((obj), X509_STORE, (st)); \ + if (!(st)) { \ + ossl_raise(rb_eRuntimeError, "STORE wasn't initialized!"); \ + } \ +} while (0) +#define SafeGetX509Store(obj, st) do { \ + OSSL_Check_Kind((obj), cX509Store); \ + GetX509Store((obj), (st)); \ +} while (0) + +#define WrapX509StCtx(klass, obj, ctx) do { \ + if (!(ctx)) { \ + ossl_raise(rb_eRuntimeError, "STORE_CTX wasn't initialized!"); \ + } \ + (obj) = Data_Wrap_Struct((klass), 0, ossl_x509stctx_free, (ctx)); \ +} while (0) +#define GetX509StCtx(obj, ctx) do { \ + Data_Get_Struct((obj), X509_STORE_CTX, (ctx)); \ + if (!(ctx)) { \ + ossl_raise(rb_eRuntimeError, "STORE_CTX is out of scope!"); \ + } \ +} while (0) +#define SafeGetX509StCtx(obj, storep) do { \ + OSSL_Check_Kind((obj), cX509StoreContext); \ + GetX509Store((obj), (ctx)); \ +} while (0) + +/* + * Classes + */ +VALUE cX509Store; +VALUE cX509StoreContext; +VALUE eX509StoreError; + +/* + * Public functions + */ +VALUE +ossl_x509store_new(X509_STORE *store) +{ + VALUE obj; + + WrapX509Store(cX509Store, obj, store); + + return obj; +} + +X509_STORE * +GetX509StorePtr(VALUE obj) +{ + X509_STORE *store; + + SafeGetX509Store(obj, store); + + return store; +} + +X509_STORE * +DupX509StorePtr(VALUE obj) +{ + X509_STORE *store; + + SafeGetX509Store(obj, store); + CRYPTO_add(&store->references, 1, CRYPTO_LOCK_X509_STORE); + + return store; +} + +/* + * Private functions + */ +static VALUE +ossl_x509store_alloc(VALUE klass) +{ + X509_STORE *store; + VALUE obj; + + if((store = X509_STORE_new()) == NULL){ + ossl_raise(eX509StoreError, NULL); + } + WrapX509Store(klass, obj, store); + + return obj; +} + +/* + * General callback for OpenSSL verify + */ +static VALUE +ossl_x509store_set_vfy_cb(VALUE self, VALUE cb) +{ + X509_STORE *store; + + GetX509Store(self, store); + X509_STORE_set_ex_data(store, ossl_verify_cb_idx, (void*)cb); + rb_iv_set(self, "@verify_callback", cb); + + return cb; +} + + +/* + * call-seq: + * X509::Store.new => store + * + */ +static VALUE +ossl_x509store_initialize(int argc, VALUE *argv, VALUE self) +{ + X509_STORE *store; + +/* BUG: This method takes any number of arguments but appears to ignore them. */ + GetX509Store(self, store); + store->ex_data.sk = NULL; + X509_STORE_set_verify_cb_func(store, ossl_verify_cb); + ossl_x509store_set_vfy_cb(self, Qnil); + +#if (OPENSSL_VERSION_NUMBER < 0x00907000L) + rb_iv_set(self, "@flags", INT2FIX(0)); + rb_iv_set(self, "@purpose", INT2FIX(0)); + rb_iv_set(self, "@trust", INT2FIX(0)); +#endif + + /* last verification status */ + rb_iv_set(self, "@error", Qnil); + rb_iv_set(self, "@error_string", Qnil); + rb_iv_set(self, "@chain", Qnil); + rb_iv_set(self, "@time", Qnil); + + return self; +} + +static VALUE +ossl_x509store_set_flags(VALUE self, VALUE flags) +{ +#if (OPENSSL_VERSION_NUMBER >= 0x00907000L) + X509_STORE *store; + long f = NUM2LONG(flags); + + GetX509Store(self, store); + X509_STORE_set_flags(store, f); +#else + rb_iv_set(self, "@flags", flags); +#endif + + return flags; +} + +static VALUE +ossl_x509store_set_purpose(VALUE self, VALUE purpose) +{ +#if (OPENSSL_VERSION_NUMBER >= 0x00907000L) + X509_STORE *store; + int p = NUM2INT(purpose); + + GetX509Store(self, store); + X509_STORE_set_purpose(store, p); +#else + rb_iv_set(self, "@purpose", purpose); +#endif + + return purpose; +} + +static VALUE +ossl_x509store_set_trust(VALUE self, VALUE trust) +{ +#if (OPENSSL_VERSION_NUMBER >= 0x00907000L) + X509_STORE *store; + int t = NUM2INT(trust); + + GetX509Store(self, store); + X509_STORE_set_trust(store, t); +#else + rb_iv_set(self, "@trust", trust); +#endif + + return trust; +} + +static VALUE +ossl_x509store_set_time(VALUE self, VALUE time) +{ + rb_iv_set(self, "@time", time); + return time; +} + +/* + * call-seq: + * store.add_file(file) -> store + * + * + * Adds the certificates in +file+ to the certificate store. The +file+ can + * contain multiple PEM-encoded certificates. + */ + +static VALUE +ossl_x509store_add_file(VALUE self, VALUE file) +{ + X509_STORE *store; + X509_LOOKUP *lookup; + char *path = NULL; + + if(file != Qnil){ + SafeStringValue(file); + path = RSTRING_PTR(file); + } + GetX509Store(self, store); + lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file()); + if(lookup == NULL) ossl_raise(eX509StoreError, NULL); + if(X509_LOOKUP_load_file(lookup, path, X509_FILETYPE_PEM) != 1){ + ossl_raise(eX509StoreError, NULL); + } + + return self; +} + +static VALUE +ossl_x509store_add_path(VALUE self, VALUE dir) +{ + X509_STORE *store; + X509_LOOKUP *lookup; + char *path = NULL; + + if(dir != Qnil){ + SafeStringValue(dir); + path = RSTRING_PTR(dir); + } + GetX509Store(self, store); + lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir()); + if(lookup == NULL) ossl_raise(eX509StoreError, NULL); + if(X509_LOOKUP_add_dir(lookup, path, X509_FILETYPE_PEM) != 1){ + ossl_raise(eX509StoreError, NULL); + } + + return self; +} + +/* + * call-seq: + * store.set_default_paths + * + * Adds the default certificates to the certificate store. These certificates + * are loaded from the default configuration directory which can usually be + * determined by: + * + * File.dirname OpenSSL::Config::DEFAULT_CONFIG_FILE + */ +static VALUE +ossl_x509store_set_default_paths(VALUE self) +{ + X509_STORE *store; + + GetX509Store(self, store); + if (X509_STORE_set_default_paths(store) != 1){ + ossl_raise(eX509StoreError, NULL); + } + + return Qnil; +} + +/* + * call-seq: + * store.add_cert(cert) + * + * Adds the OpenSSL::X509::Certificate +cert+ to the certificate store. + */ + +static VALUE +ossl_x509store_add_cert(VALUE self, VALUE arg) +{ + X509_STORE *store; + X509 *cert; + + cert = GetX509CertPtr(arg); /* NO NEED TO DUP */ + GetX509Store(self, store); + if (X509_STORE_add_cert(store, cert) != 1){ + ossl_raise(eX509StoreError, NULL); + } + + return self; +} + +static VALUE +ossl_x509store_add_crl(VALUE self, VALUE arg) +{ + X509_STORE *store; + X509_CRL *crl; + + crl = GetX509CRLPtr(arg); /* NO NEED TO DUP */ + GetX509Store(self, store); + if (X509_STORE_add_crl(store, crl) != 1){ + ossl_raise(eX509StoreError, NULL); + } + + return self; +} + +static VALUE ossl_x509stctx_get_err(VALUE); +static VALUE ossl_x509stctx_get_err_string(VALUE); +static VALUE ossl_x509stctx_get_chain(VALUE); + +static VALUE +ossl_x509store_verify(int argc, VALUE *argv, VALUE self) +{ + VALUE cert, chain; + VALUE ctx, proc, result; + + rb_scan_args(argc, argv, "11", &cert, &chain); + ctx = rb_funcall(cX509StoreContext, rb_intern("new"), 3, self, cert, chain); + proc = rb_block_given_p() ? rb_block_proc() : + rb_iv_get(self, "@verify_callback"); + rb_iv_set(ctx, "@verify_callback", proc); + result = rb_funcall(ctx, rb_intern("verify"), 0); + + rb_iv_set(self, "@error", ossl_x509stctx_get_err(ctx)); + rb_iv_set(self, "@error_string", ossl_x509stctx_get_err_string(ctx)); + rb_iv_set(self, "@chain", ossl_x509stctx_get_chain(ctx)); + + return result; +} + +/* + * Public Functions + */ +static void ossl_x509stctx_free(X509_STORE_CTX*); + +VALUE +ossl_x509stctx_new(X509_STORE_CTX *ctx) +{ + VALUE obj; + + WrapX509StCtx(cX509StoreContext, obj, ctx); + + return obj; +} + +VALUE +ossl_x509stctx_clear_ptr(VALUE obj) +{ + OSSL_Check_Kind(obj, cX509StoreContext); + RDATA(obj)->data = NULL; + + return obj; +} + +/* + * Private functions + */ +static void +ossl_x509stctx_free(X509_STORE_CTX *ctx) +{ + if(ctx->untrusted) + sk_X509_pop_free(ctx->untrusted, X509_free); + if(ctx->cert) + X509_free(ctx->cert); + X509_STORE_CTX_free(ctx); +} + +static VALUE +ossl_x509stctx_alloc(VALUE klass) +{ + X509_STORE_CTX *ctx; + VALUE obj; + + if((ctx = X509_STORE_CTX_new()) == NULL){ + ossl_raise(eX509StoreError, NULL); + } + WrapX509StCtx(klass, obj, ctx); + + return obj; +} + +static VALUE ossl_x509stctx_set_flags(VALUE, VALUE); +static VALUE ossl_x509stctx_set_purpose(VALUE, VALUE); +static VALUE ossl_x509stctx_set_trust(VALUE, VALUE); +static VALUE ossl_x509stctx_set_time(VALUE, VALUE); + +static VALUE +ossl_x509stctx_initialize(int argc, VALUE *argv, VALUE self) +{ + VALUE store, cert, chain, t; + X509_STORE_CTX *ctx; + X509_STORE *x509st; + X509 *x509 = NULL; + STACK_OF(X509) *x509s = NULL; + + rb_scan_args(argc, argv, "12", &store, &cert, &chain); + GetX509StCtx(self, ctx); + SafeGetX509Store(store, x509st); + if(!NIL_P(cert)) x509 = DupX509CertPtr(cert); /* NEED TO DUP */ + if(!NIL_P(chain)) x509s = ossl_x509_ary2sk(chain); +#if (OPENSSL_VERSION_NUMBER >= 0x00907000L) + if(X509_STORE_CTX_init(ctx, x509st, x509, x509s) != 1){ + sk_X509_pop_free(x509s, X509_free); + ossl_raise(eX509StoreError, NULL); + } +#else + X509_STORE_CTX_init(ctx, x509st, x509, x509s); + ossl_x509stctx_set_flags(self, rb_iv_get(store, "@flags")); + ossl_x509stctx_set_purpose(self, rb_iv_get(store, "@purpose")); + ossl_x509stctx_set_trust(self, rb_iv_get(store, "@trust")); +#endif + if (!NIL_P(t = rb_iv_get(store, "@time"))) + ossl_x509stctx_set_time(self, t); + rb_iv_set(self, "@verify_callback", rb_iv_get(store, "@verify_callback")); + rb_iv_set(self, "@cert", cert); + + return self; +} + +static VALUE +ossl_x509stctx_verify(VALUE self) +{ + X509_STORE_CTX *ctx; + int result; + + GetX509StCtx(self, ctx); + X509_STORE_CTX_set_ex_data(ctx, ossl_verify_cb_idx, + (void*)rb_iv_get(self, "@verify_callback")); + result = X509_verify_cert(ctx); + + return result ? Qtrue : Qfalse; +} + +static VALUE +ossl_x509stctx_get_chain(VALUE self) +{ + X509_STORE_CTX *ctx; + STACK_OF(X509) *chain; + X509 *x509; + int i, num; + VALUE ary; + + GetX509StCtx(self, ctx); + if((chain = X509_STORE_CTX_get_chain(ctx)) == NULL){ + return Qnil; + } + if((num = sk_X509_num(chain)) < 0){ + OSSL_Debug("certs in chain < 0???"); + return rb_ary_new(); + } + ary = rb_ary_new2(num); + for(i = 0; i < num; i++) { + x509 = sk_X509_value(chain, i); + rb_ary_push(ary, ossl_x509_new(x509)); + } + + return ary; +} + +static VALUE +ossl_x509stctx_get_err(VALUE self) +{ + X509_STORE_CTX *ctx; + + GetX509StCtx(self, ctx); + + return INT2FIX(X509_STORE_CTX_get_error(ctx)); +} + +static VALUE +ossl_x509stctx_set_error(VALUE self, VALUE err) +{ + X509_STORE_CTX *ctx; + + GetX509StCtx(self, ctx); + X509_STORE_CTX_set_error(ctx, NUM2INT(err)); + + return err; +} + +static VALUE +ossl_x509stctx_get_err_string(VALUE self) +{ + X509_STORE_CTX *ctx; + long err; + + GetX509StCtx(self, ctx); + err = X509_STORE_CTX_get_error(ctx); + + return rb_str_new2(X509_verify_cert_error_string(err)); +} + +static VALUE +ossl_x509stctx_get_err_depth(VALUE self) +{ + X509_STORE_CTX *ctx; + + GetX509StCtx(self, ctx); + + return INT2FIX(X509_STORE_CTX_get_error_depth(ctx)); +} + +static VALUE +ossl_x509stctx_get_curr_cert(VALUE self) +{ + X509_STORE_CTX *ctx; + + GetX509StCtx(self, ctx); + + return ossl_x509_new(X509_STORE_CTX_get_current_cert(ctx)); +} + +static VALUE +ossl_x509stctx_get_curr_crl(VALUE self) +{ +#if (OPENSSL_VERSION_NUMBER >= 0x00907000L) + X509_STORE_CTX *ctx; + + GetX509StCtx(self, ctx); + if(!ctx->current_crl) return Qnil; + + return ossl_x509crl_new(ctx->current_crl); +#else + return Qnil; +#endif +} + +static VALUE +ossl_x509stctx_set_flags(VALUE self, VALUE flags) +{ + X509_STORE_CTX *store; + long f = NUM2LONG(flags); + + GetX509StCtx(self, store); + X509_STORE_CTX_set_flags(store, f); + + return flags; +} + +static VALUE +ossl_x509stctx_set_purpose(VALUE self, VALUE purpose) +{ + X509_STORE_CTX *store; + int p = NUM2INT(purpose); + + GetX509StCtx(self, store); + X509_STORE_CTX_set_purpose(store, p); + + return purpose; +} + +static VALUE +ossl_x509stctx_set_trust(VALUE self, VALUE trust) +{ + X509_STORE_CTX *store; + int t = NUM2INT(trust); + + GetX509StCtx(self, store); + X509_STORE_CTX_set_trust(store, t); + + return trust; +} + +/* + * call-seq: + * storectx.time = time => time + */ +static VALUE +ossl_x509stctx_set_time(VALUE self, VALUE time) +{ + X509_STORE_CTX *store; + long t; + + t = NUM2LONG(rb_Integer(time)); + GetX509StCtx(self, store); + X509_STORE_CTX_set_time(store, 0, t); + + return time; +} + +/* + * INIT + */ +void +Init_ossl_x509store(void) +{ + VALUE x509stctx; + +#if 0 + mOSSL = rb_define_module("OpenSSL"); /* let rdoc know about mOSSL */ + mX509 = rb_define_module_under(mOSSL, "X509"); +#endif + + eX509StoreError = rb_define_class_under(mX509, "StoreError", eOSSLError); + + /* Document-class: OpenSSL::X509::Store + * + * The X509 certificate store holds trusted CA certificates used to verify + * peer certificates. + * + * The easiest way to create a useful certificate store is: + * + * cert_store = OpenSSL::X509::Store.new + * cert_store.set_default_paths + * + * This will use your system's built-in certificates. + * + * If your system does not have a default set of certificates you can + * obtain a set from Mozilla here: http://curl.haxx.se/docs/caextract.html + * (Note that this set does not have an HTTPS download option so you may + * wish to use the firefox-db2pem.sh script to extract the certificates + * from a local install to avoid man-in-the-middle attacks.) + * + * After downloading or generating a cacert.pem from the above link you + * can create a certificate store from the pem file like this: + * + * cert_store = OpenSSL::X509::Store.new + * cert_store.add_file 'cacert.pem' + * + * The certificate store can be used with an SSLSocket like this: + * + * ssl_context = OpenSSL::SSL::SSLContext.new + * ssl_context.cert_store = cert_store + * + * tcp_socket = TCPSocket.open 'example.com', 443 + * + * ssl_socket = OpenSSL::SSL::SSLSocket.new tcp_socket, ssl_context + */ + + cX509Store = rb_define_class_under(mX509, "Store", rb_cObject); + rb_attr(cX509Store, rb_intern("verify_callback"), 1, 0, Qfalse); + rb_attr(cX509Store, rb_intern("error"), 1, 0, Qfalse); + rb_attr(cX509Store, rb_intern("error_string"), 1, 0, Qfalse); + rb_attr(cX509Store, rb_intern("chain"), 1, 0, Qfalse); + rb_define_alloc_func(cX509Store, ossl_x509store_alloc); + rb_define_method(cX509Store, "initialize", ossl_x509store_initialize, -1); + rb_define_method(cX509Store, "verify_callback=", ossl_x509store_set_vfy_cb, 1); + rb_define_method(cX509Store, "flags=", ossl_x509store_set_flags, 1); + rb_define_method(cX509Store, "purpose=", ossl_x509store_set_purpose, 1); + rb_define_method(cX509Store, "trust=", ossl_x509store_set_trust, 1); + rb_define_method(cX509Store, "time=", ossl_x509store_set_time, 1); + rb_define_method(cX509Store, "add_path", ossl_x509store_add_path, 1); + rb_define_method(cX509Store, "add_file", ossl_x509store_add_file, 1); + rb_define_method(cX509Store, "set_default_paths", ossl_x509store_set_default_paths, 0); + rb_define_method(cX509Store, "add_cert", ossl_x509store_add_cert, 1); + rb_define_method(cX509Store, "add_crl", ossl_x509store_add_crl, 1); + rb_define_method(cX509Store, "verify", ossl_x509store_verify, -1); + + cX509StoreContext = rb_define_class_under(mX509,"StoreContext",rb_cObject); + x509stctx = cX509StoreContext; + rb_define_alloc_func(cX509StoreContext, ossl_x509stctx_alloc); + rb_define_method(x509stctx,"initialize", ossl_x509stctx_initialize, -1); + rb_define_method(x509stctx,"verify", ossl_x509stctx_verify, 0); + rb_define_method(x509stctx,"chain", ossl_x509stctx_get_chain,0); + rb_define_method(x509stctx,"error", ossl_x509stctx_get_err, 0); + rb_define_method(x509stctx,"error=", ossl_x509stctx_set_error, 1); + rb_define_method(x509stctx,"error_string",ossl_x509stctx_get_err_string,0); + rb_define_method(x509stctx,"error_depth", ossl_x509stctx_get_err_depth, 0); + rb_define_method(x509stctx,"current_cert",ossl_x509stctx_get_curr_cert, 0); + rb_define_method(x509stctx,"current_crl", ossl_x509stctx_get_curr_crl, 0); + rb_define_method(x509stctx,"flags=", ossl_x509stctx_set_flags, 1); + rb_define_method(x509stctx,"purpose=", ossl_x509stctx_set_purpose, 1); + rb_define_method(x509stctx,"trust=", ossl_x509stctx_set_trust, 1); + rb_define_method(x509stctx,"time=", ossl_x509stctx_set_time, 1); + +} diff --git a/ext/openssl/ruby_missing.h b/ext/openssl/ruby_missing.h new file mode 100644 index 00000000..0f9de1c8 --- /dev/null +++ b/ext/openssl/ruby_missing.h @@ -0,0 +1,28 @@ +/* + * $Id$ + * 'OpenSSL for Ruby' project + * Copyright (C) 2001-2003 Michal Rokos <m.rokos@sh.cvut.cz> + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#if !defined(_OSSL_RUBY_MISSING_H_) +#define _OSSL_RUBY_MISSING_H_ + +#define rb_define_copy_func(klass, func) \ + rb_define_method((klass), "initialize_copy", (func), 1) + + +#ifndef GetReadFile +#define FPTR_TO_FD(fptr) ((fptr)->fd) +#else +#define FPTR_TO_FD(fptr) (fileno(GetReadFile(fptr))) +#endif + +#ifndef HAVE_RB_IO_T +#define rb_io_t OpenFile +#endif + +#endif /* _OSSL_RUBY_MISSING_H_ */ diff --git a/lib/openssl.rb b/lib/openssl.rb index b1d057a4..19a4382d 100644 --- a/lib/openssl.rb +++ b/lib/openssl.rb @@ -1,5 +1,24 @@ -require "openssl/version" +=begin += $RCSfile$ -- Loader for all OpenSSL C-space and Ruby-space definitions -module Openssl - # Your code goes here... -end += Info + 'OpenSSL for Ruby 2' project + Copyright (C) 2002 Michal Rokos <m.rokos@sh.cvut.cz> + All rights reserved. + += Licence + This program is licenced under the same licence as Ruby. + (See the file 'LICENCE'.) + += Version + $Id$ +=end + +require 'openssl.so' + +require 'openssl/bn' +require 'openssl/cipher' +require 'openssl/config' +require 'openssl/digest' +require 'openssl/x509' +require 'openssl/ssl' diff --git a/lib/openssl/bn.rb b/lib/openssl/bn.rb new file mode 100644 index 00000000..95babb4c --- /dev/null +++ b/lib/openssl/bn.rb @@ -0,0 +1,45 @@ +#-- +# +# $RCSfile$ +# +# = Ruby-space definitions that completes C-space funcs for BN +# +# = Info +# 'OpenSSL for Ruby 2' project +# Copyright (C) 2002 Michal Rokos <m.rokos@sh.cvut.cz> +# All rights reserved. +# +# = Licence +# This program is licenced under the same licence as Ruby. +# (See the file 'LICENCE'.) +# +# = Version +# $Id$ +# +#++ + +module OpenSSL + class BN + include Comparable + + def pretty_print(q) + q.object_group(self) { + q.text ' ' + q.text to_i.to_s + } + end + end # BN +end # OpenSSL + +## +# Add double dispatch to Integer +# +class Integer + # Casts an Integer as an OpenSSL::BN + # + # See `man bn` for more info. + def to_bn + OpenSSL::BN::new(self) + end +end # Integer + diff --git a/lib/openssl/buffering.rb b/lib/openssl/buffering.rb new file mode 100644 index 00000000..1223c5de --- /dev/null +++ b/lib/openssl/buffering.rb @@ -0,0 +1,457 @@ +# coding: binary +#-- +#= $RCSfile$ -- Buffering mix-in module. +# +#= Info +# 'OpenSSL for Ruby 2' project +# Copyright (C) 2001 GOTOU YUUZOU <gotoyuzo@notwork.org> +# All rights reserved. +# +#= Licence +# This program is licenced under the same licence as Ruby. +# (See the file 'LICENCE'.) +# +#= Version +# $Id$ +#++ + +## +# OpenSSL IO buffering mix-in module. +# +# This module allows an OpenSSL::SSL::SSLSocket to behave like an IO. +# +# You typically won't use this module directly, you can see it implemented in +# OpenSSL::SSL::SSLSocket. + +module OpenSSL::Buffering + include Enumerable + + ## + # The "sync mode" of the SSLSocket. + # + # See IO#sync for full details. + + attr_accessor :sync + + ## + # Default size to read from or write to the SSLSocket for buffer operations. + + BLOCK_SIZE = 1024*16 + + ## + # Creates an instance of OpenSSL's buffering IO module. + + def initialize(*) + super + @eof = false + @rbuffer = "" + @sync = @io.sync + end + + # + # for reading. + # + private + + ## + # Fills the buffer from the underlying SSLSocket + + def fill_rbuff + begin + @rbuffer << self.sysread(BLOCK_SIZE) + rescue Errno::EAGAIN + retry + rescue EOFError + @eof = true + end + end + + ## + # Consumes +size+ bytes from the buffer + + def consume_rbuff(size=nil) + if @rbuffer.empty? + nil + else + size = @rbuffer.size unless size + ret = @rbuffer[0, size] + @rbuffer[0, size] = "" + ret + end + end + + public + + ## + # Reads +size+ bytes from the stream. If +buf+ is provided it must + # reference a string which will receive the data. + # + # See IO#read for full details. + + def read(size=nil, buf=nil) + if size == 0 + if buf + buf.clear + return buf + else + return "" + end + end + until @eof + break if size && size <= @rbuffer.size + fill_rbuff + end + ret = consume_rbuff(size) || "" + if buf + buf.replace(ret) + ret = buf + end + (size && ret.empty?) ? nil : ret + end + + ## + # Reads at most +maxlen+ bytes from the stream. If +buf+ is provided it + # must reference a string which will receive the data. + # + # See IO#readpartial for full details. + + def readpartial(maxlen, buf=nil) + if maxlen == 0 + if buf + buf.clear + return buf + else + return "" + end + end + if @rbuffer.empty? + begin + return sysread(maxlen, buf) + rescue Errno::EAGAIN + retry + end + end + ret = consume_rbuff(maxlen) + if buf + buf.replace(ret) + ret = buf + end + raise EOFError if ret.empty? + ret + end + + ## + # Reads at most +maxlen+ bytes in the non-blocking manner. + # + # When no data can be read without blocking it raises + # OpenSSL::SSL::SSLError extended by IO::WaitReadable or IO::WaitWritable. + # + # IO::WaitReadable means SSL needs to read internally so read_nonblock + # should be called again when the underlying IO is readable. + # + # IO::WaitWritable means SSL needs to write internally so read_nonblock + # should be called again after the underlying IO is writable. + # + # OpenSSL::Buffering#read_nonblock needs two rescue clause as follows: + # + # # emulates blocking read (readpartial). + # begin + # result = ssl.read_nonblock(maxlen) + # rescue IO::WaitReadable + # IO.select([io]) + # retry + # rescue IO::WaitWritable + # IO.select(nil, [io]) + # retry + # end + # + # Note that one reason that read_nonblock writes to the underlying IO is + # when the peer requests a new TLS/SSL handshake. See openssl the FAQ for + # more details. http://www.openssl.org/support/faq.html + + def read_nonblock(maxlen, buf=nil, exception: true) + if maxlen == 0 + if buf + buf.clear + return buf + else + return "" + end + end + if @rbuffer.empty? + return sysread_nonblock(maxlen, buf, exception: exception) + end + ret = consume_rbuff(maxlen) + if buf + buf.replace(ret) + ret = buf + end + raise EOFError if ret.empty? + ret + end + + ## + # Reads the next "line+ from the stream. Lines are separated by +eol+. If + # +limit+ is provided the result will not be longer than the given number of + # bytes. + # + # +eol+ may be a String or Regexp. + # + # Unlike IO#gets the line read will not be assigned to +$_+. + # + # Unlike IO#gets the separator must be provided if a limit is provided. + + def gets(eol=$/, limit=nil) + idx = @rbuffer.index(eol) + until @eof + break if idx + fill_rbuff + idx = @rbuffer.index(eol) + end + if eol.is_a?(Regexp) + size = idx ? idx+$&.size : nil + else + size = idx ? idx+eol.size : nil + end + if limit and limit >= 0 + size = [size, limit].min + end + consume_rbuff(size) + end + + ## + # Executes the block for every line in the stream where lines are separated + # by +eol+. + # + # See also #gets + + def each(eol=$/) + while line = self.gets(eol) + yield line + end + end + alias each_line each + + ## + # Reads lines from the stream which are separated by +eol+. + # + # See also #gets + + def readlines(eol=$/) + ary = [] + while line = self.gets(eol) + ary << line + end + ary + end + + ## + # Reads a line from the stream which is separated by +eol+. + # + # Raises EOFError if at end of file. + + def readline(eol=$/) + raise EOFError if eof? + gets(eol) + end + + ## + # Reads one character from the stream. Returns nil if called at end of + # file. + + def getc + read(1) + end + + ## + # Calls the given block once for each byte in the stream. + + def each_byte # :yields: byte + while c = getc + yield(c.ord) + end + end + + ## + # Reads a one-character string from the stream. Raises an EOFError at end + # of file. + + def readchar + raise EOFError if eof? + getc + end + + ## + # Pushes character +c+ back onto the stream such that a subsequent buffered + # character read will return it. + # + # Unlike IO#getc multiple bytes may be pushed back onto the stream. + # + # Has no effect on unbuffered reads (such as #sysread). + + def ungetc(c) + @rbuffer[0,0] = c.chr + end + + ## + # Returns true if the stream is at file which means there is no more data to + # be read. + + def eof? + fill_rbuff if !@eof && @rbuffer.empty? + @eof && @rbuffer.empty? + end + alias eof eof? + + # + # for writing. + # + private + + ## + # Writes +s+ to the buffer. When the buffer is full or #sync is true the + # buffer is flushed to the underlying socket. + + def do_write(s) + @wbuffer = "" unless defined? @wbuffer + @wbuffer << s + @wbuffer.force_encoding(Encoding::BINARY) + @sync ||= false + if @sync or @wbuffer.size > BLOCK_SIZE or idx = @wbuffer.rindex($/) + remain = idx ? idx + $/.size : @wbuffer.length + nwritten = 0 + while remain > 0 + str = @wbuffer[nwritten,remain] + begin + nwrote = syswrite(str) + rescue Errno::EAGAIN + retry + end + remain -= nwrote + nwritten += nwrote + end + @wbuffer[0,nwritten] = "" + end + end + + public + + ## + # Writes +s+ to the stream. If the argument is not a string it will be + # converted using String#to_s. Returns the number of bytes written. + + def write(s) + do_write(s) + s.bytesize + end + + ## + # Writes +str+ in the non-blocking manner. + # + # If there is buffered data, it is flushed first. This may block. + # + # write_nonblock returns number of bytes written to the SSL connection. + # + # When no data can be written without blocking it raises + # OpenSSL::SSL::SSLError extended by IO::WaitReadable or IO::WaitWritable. + # + # IO::WaitReadable means SSL needs to read internally so write_nonblock + # should be called again after the underlying IO is readable. + # + # IO::WaitWritable means SSL needs to write internally so write_nonblock + # should be called again after underlying IO is writable. + # + # So OpenSSL::Buffering#write_nonblock needs two rescue clause as follows. + # + # # emulates blocking write. + # begin + # result = ssl.write_nonblock(str) + # rescue IO::WaitReadable + # IO.select([io]) + # retry + # rescue IO::WaitWritable + # IO.select(nil, [io]) + # retry + # end + # + # Note that one reason that write_nonblock reads from the underlying IO + # is when the peer requests a new TLS/SSL handshake. See the openssl FAQ + # for more details. http://www.openssl.org/support/faq.html + + def write_nonblock(s, exception: true) + flush + syswrite_nonblock(s, exception: exception) + end + + ## + # Writes +s+ to the stream. +s+ will be converted to a String using + # String#to_s. + + def << (s) + do_write(s) + self + end + + ## + # Writes +args+ to the stream along with a record separator. + # + # See IO#puts for full details. + + def puts(*args) + s = "" + if args.empty? + s << "\n" + end + args.each{|arg| + s << arg.to_s + if $/ && /\n\z/ !~ s + s << "\n" + end + } + do_write(s) + nil + end + + ## + # Writes +args+ to the stream. + # + # See IO#print for full details. + + def print(*args) + s = "" + args.each{ |arg| s << arg.to_s } + do_write(s) + nil + end + + ## + # Formats and writes to the stream converting parameters under control of + # the format string. + # + # See Kernel#sprintf for format string details. + + def printf(s, *args) + do_write(s % args) + nil + end + + ## + # Flushes buffered data to the SSLSocket. + + def flush + osync = @sync + @sync = true + do_write "" + return self + ensure + @sync = osync + end + + ## + # Closes the SSLSocket and flushes any unwritten data. + + def close + flush rescue nil + sysclose + end +end diff --git a/lib/openssl/cipher.rb b/lib/openssl/cipher.rb new file mode 100644 index 00000000..b3340ff5 --- /dev/null +++ b/lib/openssl/cipher.rb @@ -0,0 +1,65 @@ +#-- +# +# $RCSfile$ +# +# = Ruby-space predefined Cipher subclasses +# +# = Info +# 'OpenSSL for Ruby 2' project +# Copyright (C) 2002 Michal Rokos <m.rokos@sh.cvut.cz> +# All rights reserved. +# +# = Licence +# This program is licenced under the same licence as Ruby. +# (See the file 'LICENCE'.) +# +# = Version +# $Id$ +# +#++ + +module OpenSSL + class Cipher + %w(AES CAST5 BF DES IDEA RC2 RC4 RC5).each{|name| + klass = Class.new(Cipher){ + define_method(:initialize){|*args| + cipher_name = args.inject(name){|n, arg| "#{n}-#{arg}" } + super(cipher_name) + } + } + const_set(name, klass) + } + + %w(128 192 256).each{|keylen| + klass = Class.new(Cipher){ + define_method(:initialize){|mode| + mode ||= "CBC" + cipher_name = "AES-#{keylen}-#{mode}" + super(cipher_name) + } + } + const_set("AES#{keylen}", klass) + } + + # Generate, set, and return a random key. + # You must call cipher.encrypt or cipher.decrypt before calling this method. + def random_key + str = OpenSSL::Random.random_bytes(self.key_len) + self.key = str + return str + end + + # Generate, set, and return a random iv. + # You must call cipher.encrypt or cipher.decrypt before calling this method. + def random_iv + str = OpenSSL::Random.random_bytes(self.iv_len) + self.iv = str + return str + end + + # This class is only provided for backwards compatibility. Use OpenSSL::Cipher in the future. + class Cipher < Cipher + # add warning + end + end # Cipher +end # OpenSSL diff --git a/lib/openssl/config.rb b/lib/openssl/config.rb new file mode 100644 index 00000000..5716d59f --- /dev/null +++ b/lib/openssl/config.rb @@ -0,0 +1,472 @@ +=begin += Ruby-space definitions that completes C-space funcs for Config + += Info + Copyright (C) 2010 Hiroshi Nakamura <nahi@ruby-lang.org> + += Licence + This program is licenced under the same licence as Ruby. + (See the file 'LICENCE'.) + +=end + +require 'stringio' + +module OpenSSL + ## + # = OpenSSL::Config + # + # Configuration for the openssl library. + # + # Many system's installation of openssl library will depend on your system + # configuration. See the value of OpenSSL::Config::DEFAULT_CONFIG_FILE for + # the location of the file for your host. + # + # See also http://www.openssl.org/docs/apps/config.html + class Config + include Enumerable + + class << self + + ## + # Parses a given +string+ as a blob that contains configuration for openssl. + # + # If the source of the IO is a file, then consider using #parse_config. + def parse(string) + c = new() + parse_config(StringIO.new(string)).each do |section, hash| + c[section] = hash + end + c + end + + ## + # load is an alias to ::new + alias load new + + ## + # Parses the configuration data read from +io+, see also #parse. + # + # Raises a ConfigError on invalid configuration data. + def parse_config(io) + begin + parse_config_lines(io) + rescue ConfigError => e + e.message.replace("error in line #{io.lineno}: " + e.message) + raise + end + end + + def get_key_string(data, section, key) # :nodoc: + if v = data[section] && data[section][key] + return v + elsif section == 'ENV' + if v = ENV[key] + return v + end + end + if v = data['default'] && data['default'][key] + return v + end + end + + private + + def parse_config_lines(io) + section = 'default' + data = {section => {}} + while definition = get_definition(io) + definition = clear_comments(definition) + next if definition.empty? + if definition[0] == ?[ + if /\[([^\]]*)\]/ =~ definition + section = $1.strip + data[section] ||= {} + else + raise ConfigError, "missing close square bracket" + end + else + if /\A([^:\s]*)(?:::([^:\s]*))?\s*=(.*)\z/ =~ definition + if $2 + section = $1 + key = $2 + else + key = $1 + end + value = unescape_value(data, section, $3) + (data[section] ||= {})[key] = value.strip + else + raise ConfigError, "missing equal sign" + end + end + end + data + end + + # escape with backslash + QUOTE_REGEXP_SQ = /\A([^'\\]*(?:\\.[^'\\]*)*)'/ + # escape with backslash and doubled dq + QUOTE_REGEXP_DQ = /\A([^"\\]*(?:""[^"\\]*|\\.[^"\\]*)*)"/ + # escaped char map + ESCAPE_MAP = { + "r" => "\r", + "n" => "\n", + "b" => "\b", + "t" => "\t", + } + + def unescape_value(data, section, value) + scanned = [] + while m = value.match(/['"\\$]/) + scanned << m.pre_match + c = m[0] + value = m.post_match + case c + when "'" + if m = value.match(QUOTE_REGEXP_SQ) + scanned << m[1].gsub(/\\(.)/, '\\1') + value = m.post_match + else + break + end + when '"' + if m = value.match(QUOTE_REGEXP_DQ) + scanned << m[1].gsub(/""/, '').gsub(/\\(.)/, '\\1') + value = m.post_match + else + break + end + when "\\" + c = value.slice!(0, 1) + scanned << (ESCAPE_MAP[c] || c) + when "$" + ref, value = extract_reference(value) + refsec = section + if ref.index('::') + refsec, ref = ref.split('::', 2) + end + if v = get_key_string(data, refsec, ref) + scanned << v + else + raise ConfigError, "variable has no value" + end + else + raise 'must not reaced' + end + end + scanned << value + scanned.join + end + + def extract_reference(value) + rest = '' + if m = value.match(/\(([^)]*)\)|\{([^}]*)\}/) + value = m[1] || m[2] + rest = m.post_match + elsif [?(, ?{].include?(value[0]) + raise ConfigError, "no close brace" + end + if m = value.match(/[a-zA-Z0-9_]*(?:::[a-zA-Z0-9_]*)?/) + return m[0], m.post_match + rest + else + raise + end + end + + def clear_comments(line) + # FCOMMENT + if m = line.match(/\A([\t\n\f ]*);.*\z/) + return m[1] + end + # COMMENT + scanned = [] + while m = line.match(/[#'"\\]/) + scanned << m.pre_match + c = m[0] + line = m.post_match + case c + when '#' + line = nil + break + when "'", '"' + regexp = (c == "'") ? QUOTE_REGEXP_SQ : QUOTE_REGEXP_DQ + scanned << c + if m = line.match(regexp) + scanned << m[0] + line = m.post_match + else + scanned << line + line = nil + break + end + when "\\" + scanned << c + scanned << line.slice!(0, 1) + else + raise 'must not reaced' + end + end + scanned << line + scanned.join + end + + def get_definition(io) + if line = get_line(io) + while /[^\\]\\\z/ =~ line + if extra = get_line(io) + line += extra + else + break + end + end + return line.strip + end + end + + def get_line(io) + if line = io.gets + line.gsub(/[\r\n]*/, '') + end + end + end + + ## + # Creates an instance of OpenSSL's configuration class. + # + # This can be used in contexts like OpenSSL::X509::ExtensionFactory.config= + # + # If the optional +filename+ parameter is provided, then it is read in and + # parsed via #parse_config. + # + # This can raise IO exceptions based on the access, or availability of the + # file. A ConfigError exception may be raised depending on the validity of + # the data being configured. + # + def initialize(filename = nil) + @data = {} + if filename + File.open(filename.to_s) do |file| + Config.parse_config(file).each do |section, hash| + self[section] = hash + end + end + end + end + + ## + # Gets the value of +key+ from the given +section+ + # + # Given the following configurating file being loaded: + # + # config = OpenSSL::Config.load('foo.cnf') + # #=> #<OpenSSL::Config sections=["default"]> + # puts config.to_s + # #=> [ default ] + # # foo=bar + # + # You can get a specific value from the config if you know the +section+ + # and +key+ like so: + # + # config.get_value('default','foo') + # #=> "bar" + # + def get_value(section, key) + if section.nil? + raise TypeError.new('nil not allowed') + end + section = 'default' if section.empty? + get_key_string(section, key) + end + + ## + # + # *Deprecated* + # + # Use #get_value instead + def value(arg1, arg2 = nil) # :nodoc: + warn('Config#value is deprecated; use Config#get_value') + if arg2.nil? + section, key = 'default', arg1 + else + section, key = arg1, arg2 + end + section ||= 'default' + section = 'default' if section.empty? + get_key_string(section, key) + end + + ## + # Set the target +key+ with a given +value+ under a specific +section+. + # + # Given the following configurating file being loaded: + # + # config = OpenSSL::Config.load('foo.cnf') + # #=> #<OpenSSL::Config sections=["default"]> + # puts config.to_s + # #=> [ default ] + # # foo=bar + # + # You can set the value of +foo+ under the +default+ section to a new + # value: + # + # config.add_value('default', 'foo', 'buzz') + # #=> "buzz" + # puts config.to_s + # #=> [ default ] + # # foo=buzz + # + def add_value(section, key, value) + check_modify + (@data[section] ||= {})[key] = value + end + + ## + # Get a specific +section+ from the current configuration + # + # Given the following configurating file being loaded: + # + # config = OpenSSL::Config.load('foo.cnf') + # #=> #<OpenSSL::Config sections=["default"]> + # puts config.to_s + # #=> [ default ] + # # foo=bar + # + # You can get a hash of the specific section like so: + # + # config['default'] + # #=> {"foo"=>"bar"} + # + def [](section) + @data[section] || {} + end + + ## + # Deprecated + # + # Use #[] instead + def section(name) # :nodoc: + warn('Config#section is deprecated; use Config#[]') + @data[name] || {} + end + + ## + # Sets a specific +section+ name with a Hash +pairs+ + # + # Given the following configuration being created: + # + # config = OpenSSL::Config.new + # #=> #<OpenSSL::Config sections=[]> + # config['default'] = {"foo"=>"bar","baz"=>"buz"} + # #=> {"foo"=>"bar", "baz"=>"buz"} + # puts config.to_s + # #=> [ default ] + # # foo=bar + # # baz=buz + # + # It's important to note that this will essentially merge any of the keys + # in +pairs+ with the existing +section+. For example: + # + # config['default'] + # #=> {"foo"=>"bar", "baz"=>"buz"} + # config['default'] = {"foo" => "changed"} + # #=> {"foo"=>"changed"} + # config['default'] + # #=> {"foo"=>"changed", "baz"=>"buz"} + # + def []=(section, pairs) + check_modify + @data[section] ||= {} + pairs.each do |key, value| + self.add_value(section, key, value) + end + end + + ## + # Get the names of all sections in the current configuration + def sections + @data.keys + end + + ## + # Get the parsable form of the current configuration + # + # Given the following configuration being created: + # + # config = OpenSSL::Config.new + # #=> #<OpenSSL::Config sections=[]> + # config['default'] = {"foo"=>"bar","baz"=>"buz"} + # #=> {"foo"=>"bar", "baz"=>"buz"} + # puts config.to_s + # #=> [ default ] + # # foo=bar + # # baz=buz + # + # You can parse get the serialized configuration using #to_s and then parse + # it later: + # + # serialized_config = config.to_s + # # much later... + # new_config = OpenSSL::Config.parse(serialized_config) + # #=> #<OpenSSL::Config sections=["default"]> + # puts new_config + # #=> [ default ] + # foo=bar + # baz=buz + # + def to_s + ary = [] + @data.keys.sort.each do |section| + ary << "[ #{section} ]\n" + @data[section].keys.each do |key| + ary << "#{key}=#{@data[section][key]}\n" + end + ary << "\n" + end + ary.join + end + + ## + # For a block. + # + # Receive the section and its pairs for the current configuration. + # + # config.each do |section, key, value| + # # ... + # end + # + def each + @data.each do |section, hash| + hash.each do |key, value| + yield [section, key, value] + end + end + end + + ## + # String representation of this configuration object, including the class + # name and its sections. + def inspect + "#<#{self.class.name} sections=#{sections.inspect}>" + end + + protected + + def data # :nodoc: + @data + end + + private + + def initialize_copy(other) + @data = other.data.dup + end + + def check_modify + raise TypeError.new("Insecure: can't modify OpenSSL config") if frozen? + end + + def get_key_string(section, key) + Config.get_key_string(@data, section, key) + end + end +end diff --git a/lib/openssl/digest.rb b/lib/openssl/digest.rb new file mode 100644 index 00000000..a7b641fd --- /dev/null +++ b/lib/openssl/digest.rb @@ -0,0 +1,88 @@ +#-- +# +# $RCSfile$ +# +# = Ruby-space predefined Digest subclasses +# +# = Info +# 'OpenSSL for Ruby 2' project +# Copyright (C) 2002 Michal Rokos <m.rokos@sh.cvut.cz> +# All rights reserved. +# +# = Licence +# This program is licenced under the same licence as Ruby. +# (See the file 'LICENCE'.) +# +# = Version +# $Id$ +# +#++ + +module OpenSSL + class Digest + + alg = %w(DSS DSS1 MD2 MD4 MD5 MDC2 RIPEMD160 SHA SHA1) + if OPENSSL_VERSION_NUMBER > 0x00908000 + alg += %w(SHA224 SHA256 SHA384 SHA512) + end + + # Return the +data+ hash computed with +name+ Digest. +name+ is either the + # long name or short name of a supported digest algorithm. + # + # === Examples + # + # OpenSSL::Digest.digest("SHA256", "abc") + # + # which is equivalent to: + # + # OpenSSL::Digest::SHA256.digest("abc") + + def self.digest(name, data) + super(data, name) + end + + alg.each{|name| + klass = Class.new(self) { + define_method(:initialize, ->(data = nil) {super(name, data)}) + } + singleton = (class << klass; self; end) + singleton.class_eval{ + define_method(:digest){|data| new.digest(data) } + define_method(:hexdigest){|data| new.hexdigest(data) } + } + const_set(name, klass) + } + + # Deprecated. + # + # This class is only provided for backwards compatibility. + class Digest < Digest # :nodoc: + # Deprecated. + # + # See OpenSSL::Digest.new + def initialize(*args) + warn('Digest::Digest is deprecated; use Digest') + super(*args) + end + end + + end # Digest + + # Returns a Digest subclass by +name+. + # + # require 'openssl' + # + # OpenSSL::Digest("MD5") + # # => OpenSSL::Digest::MD5 + # + # Digest("Foo") + # # => NameError: wrong constant name Foo + + def Digest(name) + OpenSSL::Digest.const_get(name) + end + + module_function :Digest + +end # OpenSSL + diff --git a/lib/openssl/ssl.rb b/lib/openssl/ssl.rb new file mode 100644 index 00000000..ff1d4efb --- /dev/null +++ b/lib/openssl/ssl.rb @@ -0,0 +1,254 @@ +=begin += $RCSfile$ -- Ruby-space definitions that completes C-space funcs for SSL + += Info + 'OpenSSL for Ruby 2' project + Copyright (C) 2001 GOTOU YUUZOU <gotoyuzo@notwork.org> + All rights reserved. + += Licence + This program is licenced under the same licence as Ruby. + (See the file 'LICENCE'.) + += Version + $Id$ +=end + +require "openssl/buffering" +require "fcntl" + +module OpenSSL + module SSL + class SSLContext + DEFAULT_PARAMS = { + :ssl_version => "SSLv23", + :verify_mode => OpenSSL::SSL::VERIFY_PEER, + :ciphers => %w{ + ECDHE-ECDSA-AES128-GCM-SHA256 + ECDHE-RSA-AES128-GCM-SHA256 + ECDHE-ECDSA-AES256-GCM-SHA384 + ECDHE-RSA-AES256-GCM-SHA384 + DHE-RSA-AES128-GCM-SHA256 + DHE-DSS-AES128-GCM-SHA256 + DHE-RSA-AES256-GCM-SHA384 + DHE-DSS-AES256-GCM-SHA384 + ECDHE-ECDSA-AES128-SHA256 + ECDHE-RSA-AES128-SHA256 + ECDHE-ECDSA-AES128-SHA + ECDHE-RSA-AES128-SHA + ECDHE-ECDSA-AES256-SHA384 + ECDHE-RSA-AES256-SHA384 + ECDHE-ECDSA-AES256-SHA + ECDHE-RSA-AES256-SHA + DHE-RSA-AES128-SHA256 + DHE-RSA-AES256-SHA256 + DHE-RSA-AES128-SHA + DHE-RSA-AES256-SHA + DHE-DSS-AES128-SHA256 + DHE-DSS-AES256-SHA256 + DHE-DSS-AES128-SHA + DHE-DSS-AES256-SHA + AES128-GCM-SHA256 + AES256-GCM-SHA384 + AES128-SHA256 + AES256-SHA256 + AES128-SHA + AES256-SHA + ECDHE-ECDSA-RC4-SHA + ECDHE-RSA-RC4-SHA + RC4-SHA + }.join(":"), + :options => -> { + opts = OpenSSL::SSL::OP_ALL + opts &= ~OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS if defined?(OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS) + opts |= OpenSSL::SSL::OP_NO_COMPRESSION if defined?(OpenSSL::SSL::OP_NO_COMPRESSION) + opts |= OpenSSL::SSL::OP_NO_SSLv2 if defined?(OpenSSL::SSL::OP_NO_SSLv2) + opts |= OpenSSL::SSL::OP_NO_SSLv3 if defined?(OpenSSL::SSL::OP_NO_SSLv3) + opts + }.call + } + + DEFAULT_CERT_STORE = OpenSSL::X509::Store.new + DEFAULT_CERT_STORE.set_default_paths + if defined?(OpenSSL::X509::V_FLAG_CRL_CHECK_ALL) + DEFAULT_CERT_STORE.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL + end + + ## + # Sets the parameters for this SSL context to the values in +params+. + # The keys in +params+ must be assignment methods on SSLContext. + # + # If the verify_mode is not VERIFY_NONE and ca_file, ca_path and + # cert_store are not set then the system default certificate store is + # used. + + def set_params(params={}) + params = DEFAULT_PARAMS.merge(params) + params.each{|name, value| self.__send__("#{name}=", value) } + if self.verify_mode != OpenSSL::SSL::VERIFY_NONE + unless self.ca_file or self.ca_path or self.cert_store + self.cert_store = DEFAULT_CERT_STORE + end + end + return params + end + end + + module SocketForwarder + def addr + to_io.addr + end + + def peeraddr + to_io.peeraddr + end + + def setsockopt(level, optname, optval) + to_io.setsockopt(level, optname, optval) + end + + def getsockopt(level, optname) + to_io.getsockopt(level, optname) + end + + def fcntl(*args) + to_io.fcntl(*args) + end + + def closed? + to_io.closed? + end + + def do_not_reverse_lookup=(flag) + to_io.do_not_reverse_lookup = flag + end + end + + module Nonblock + def initialize(*args) + flag = File::NONBLOCK + flag |= @io.fcntl(Fcntl::F_GETFL) if defined?(Fcntl::F_GETFL) + @io.fcntl(Fcntl::F_SETFL, flag) + super + end + end + + def verify_certificate_identity(cert, hostname) + should_verify_common_name = true + cert.extensions.each{|ext| + next if ext.oid != "subjectAltName" + ostr = OpenSSL::ASN1.decode(ext.to_der).value.last + sequence = OpenSSL::ASN1.decode(ostr.value) + sequence.value.each{|san| + case san.tag + when 2 # dNSName in GeneralName (RFC5280) + should_verify_common_name = false + reg = Regexp.escape(san.value).gsub(/\\\*/, "[^.]+") + return true if /\A#{reg}\z/i =~ hostname + when 7 # iPAddress in GeneralName (RFC5280) + should_verify_common_name = false + # follows GENERAL_NAME_print() in x509v3/v3_alt.c + if san.value.size == 4 + return true if san.value.unpack('C*').join('.') == hostname + elsif san.value.size == 16 + return true if san.value.unpack('n*').map { |e| sprintf("%X", e) }.join(':') == hostname + end + end + } + } + if should_verify_common_name + cert.subject.to_a.each{|oid, value| + if oid == "CN" + reg = Regexp.escape(value).gsub(/\\\*/, "[^.]+") + return true if /\A#{reg}\z/i =~ hostname + end + } + end + return false + end + module_function :verify_certificate_identity + + class SSLSocket + include Buffering + include SocketForwarder + include Nonblock + + def post_connection_check(hostname) + unless OpenSSL::SSL.verify_certificate_identity(peer_cert, hostname) + raise SSLError, "hostname \"#{hostname}\" does not match the server certificate" + end + return true + end + + def session + SSL::Session.new(self) + rescue SSL::Session::SessionError + nil + end + end + + ## + # SSLServer represents a TCP/IP server socket with Secure Sockets Layer. + class SSLServer + include SocketForwarder + # When true then #accept works exactly the same as TCPServer#accept + attr_accessor :start_immediately + + # Creates a new instance of SSLServer. + # * +srv+ is an instance of TCPServer. + # * +ctx+ is an instance of OpenSSL::SSL::SSLContext. + def initialize(svr, ctx) + @svr = svr + @ctx = ctx + unless ctx.session_id_context + # see #6137 - session id may not exceed 32 bytes + prng = ::Random.new($0.hash) + session_id = prng.bytes(16).unpack('H*')[0] + @ctx.session_id_context = session_id + end + @start_immediately = true + end + + # Returns the TCPServer passed to the SSLServer when initialized. + def to_io + @svr + end + + # See TCPServer#listen for details. + def listen(backlog=5) + @svr.listen(backlog) + end + + # See BasicSocket#shutdown for details. + def shutdown(how=Socket::SHUT_RDWR) + @svr.shutdown(how) + end + + # Works similar to TCPServer#accept. + def accept + # Socket#accept returns [socket, addrinfo]. + # TCPServer#accept returns a socket. + # The following comma strips addrinfo. + sock, = @svr.accept + begin + ssl = OpenSSL::SSL::SSLSocket.new(sock, @ctx) + ssl.sync_close = true + ssl.accept if @start_immediately + ssl + rescue Exception => ex + if ssl + ssl.close + else + sock.close + end + raise ex + end + end + + # See IO#close for details. + def close + @svr.close + end + end + end +end diff --git a/lib/openssl/version.rb b/lib/openssl/version.rb deleted file mode 100644 index b7eaf0aa..00000000 --- a/lib/openssl/version.rb +++ /dev/null @@ -1,3 +0,0 @@ -module Openssl - VERSION = "0.0.1" -end diff --git a/lib/openssl/x509.rb b/lib/openssl/x509.rb new file mode 100644 index 00000000..10a08894 --- /dev/null +++ b/lib/openssl/x509.rb @@ -0,0 +1,182 @@ +#-- +# +# $RCSfile$ +# +# = Ruby-space definitions that completes C-space funcs for X509 and subclasses +# +# = Info +# 'OpenSSL for Ruby 2' project +# Copyright (C) 2002 Michal Rokos <m.rokos@sh.cvut.cz> +# All rights reserved. +# +# = Licence +# This program is licenced under the same licence as Ruby. +# (See the file 'LICENCE'.) +# +# = Version +# $Id$ +# +#++ + +module OpenSSL + module X509 + class ExtensionFactory + def create_extension(*arg) + if arg.size > 1 + create_ext(*arg) + else + send("create_ext_from_"+arg[0].class.name.downcase, arg[0]) + end + end + + def create_ext_from_array(ary) + raise ExtensionError, "unexpected array form" if ary.size > 3 + create_ext(ary[0], ary[1], ary[2]) + end + + def create_ext_from_string(str) # "oid = critical, value" + oid, value = str.split(/=/, 2) + oid.strip! + value.strip! + create_ext(oid, value) + end + + def create_ext_from_hash(hash) + create_ext(hash["oid"], hash["value"], hash["critical"]) + end + end + + class Extension + def to_s # "oid = critical, value" + str = self.oid + str << " = " + str << "critical, " if self.critical? + str << self.value.gsub(/\n/, ", ") + end + + def to_h # {"oid"=>sn|ln, "value"=>value, "critical"=>true|false} + {"oid"=>self.oid,"value"=>self.value,"critical"=>self.critical?} + end + + def to_a + [ self.oid, self.value, self.critical? ] + end + end + + class Name + module RFC2253DN + Special = ',=+<>#;' + HexChar = /[0-9a-fA-F]/ + HexPair = /#{HexChar}#{HexChar}/ + HexString = /#{HexPair}+/ + Pair = /\\(?:[#{Special}]|\\|"|#{HexPair})/ + StringChar = /[^#{Special}\\"]/ + QuoteChar = /[^\\"]/ + AttributeType = /[a-zA-Z][0-9a-zA-Z]*|[0-9]+(?:\.[0-9]+)*/ + AttributeValue = / + (?!["#])((?:#{StringChar}|#{Pair})*)| + \#(#{HexString})| + "((?:#{QuoteChar}|#{Pair})*)" + /x + TypeAndValue = /\A(#{AttributeType})=#{AttributeValue}/ + + module_function + + def expand_pair(str) + return nil unless str + return str.gsub(Pair){ + pair = $& + case pair.size + when 2 then pair[1,1] + when 3 then Integer("0x#{pair[1,2]}").chr + else raise OpenSSL::X509::NameError, "invalid pair: #{str}" + end + } + end + + def expand_hexstring(str) + return nil unless str + der = str.gsub(HexPair){$&.to_i(16).chr } + a1 = OpenSSL::ASN1.decode(der) + return a1.value, a1.tag + end + + def expand_value(str1, str2, str3) + value = expand_pair(str1) + value, tag = expand_hexstring(str2) unless value + value = expand_pair(str3) unless value + return value, tag + end + + def scan(dn) + str = dn + ary = [] + while true + if md = TypeAndValue.match(str) + remain = md.post_match + type = md[1] + value, tag = expand_value(md[2], md[3], md[4]) rescue nil + if value + type_and_value = [type, value] + type_and_value.push(tag) if tag + ary.unshift(type_and_value) + if remain.length > 2 && remain[0] == ?, + str = remain[1..-1] + next + elsif remain.length > 2 && remain[0] == ?+ + raise OpenSSL::X509::NameError, + "multi-valued RDN is not supported: #{dn}" + elsif remain.empty? + break + end + end + end + msg_dn = dn[0, dn.length - str.length] + " =>" + str + raise OpenSSL::X509::NameError, "malformed RDN: #{msg_dn}" + end + return ary + end + end + + class << self + def parse_rfc2253(str, template=OBJECT_TYPE_TEMPLATE) + ary = OpenSSL::X509::Name::RFC2253DN.scan(str) + self.new(ary, template) + end + + def parse_openssl(str, template=OBJECT_TYPE_TEMPLATE) + ary = str.scan(/\s*([^\/,]+)\s*/).collect{|i| i[0].split("=", 2) } + self.new(ary, template) + end + + alias parse parse_openssl + end + + def pretty_print(q) + q.object_group(self) { + q.text ' ' + q.text to_s(OpenSSL::X509::Name::RFC2253) + } + end + end + + class StoreContext + def cleanup + warn "(#{caller.first}) OpenSSL::X509::StoreContext#cleanup is deprecated with no replacement" if $VERBOSE + end + end + + class Certificate + def pretty_print(q) + q.object_group(self) { + q.breakable + q.text 'subject='; q.pp self.subject; q.text ','; q.breakable + q.text 'issuer='; q.pp self.issuer; q.text ','; q.breakable + q.text 'serial='; q.pp self.serial; q.text ','; q.breakable + q.text 'not_before='; q.pp self.not_before; q.text ','; q.breakable + q.text 'not_after='; q.pp self.not_after + } + end + end + end +end diff --git a/test/ssl_server.rb b/test/ssl_server.rb new file mode 100644 index 00000000..d3ad55d2 --- /dev/null +++ b/test/ssl_server.rb @@ -0,0 +1,81 @@ +require "socket" +require "thread" +require "openssl" +require File.join(File.dirname(__FILE__), "utils.rb") + +def get_pem(io=$stdin) + buf = "" + while line = io.gets + if /^-----BEGIN / =~ line + buf << line + break + end + end + while line = io.gets + buf << line + if /^-----END / =~ line + break + end + end + return buf +end + +def make_key(pem) + begin + return OpenSSL::PKey::RSA.new(pem) + rescue + return OpenSSL::PKey::DSA.new(pem) + end +end + +ca_cert = OpenSSL::X509::Certificate.new(get_pem) +ssl_cert = OpenSSL::X509::Certificate.new(get_pem) +ssl_key = make_key(get_pem) +port = Integer(ARGV.shift) +verify_mode = Integer(ARGV.shift) +start_immediately = (/yes/ =~ ARGV.shift) + +store = OpenSSL::X509::Store.new +store.add_cert(ca_cert) +store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT +ctx = OpenSSL::SSL::SSLContext.new +ctx.cert_store = store +#ctx.extra_chain_cert = [ ca_cert ] +ctx.cert = ssl_cert +ctx.key = ssl_key +ctx.verify_mode = verify_mode + +Socket.do_not_reverse_lookup = true +tcps = nil +100.times{|i| + begin + tcps = TCPServer.new("0.0.0.0", port+i) + port = port + i + break + rescue Errno::EADDRINUSE + next + end +} +ssls = OpenSSL::SSL::SSLServer.new(tcps, ctx) +ssls.start_immediately = start_immediately + +$stdout.sync = true +$stdout.puts Process.pid +$stdout.puts port + +loop do + ssl = ssls.accept rescue next + Thread.start{ + q = Queue.new + th = Thread.start{ ssl.write(q.shift) while true } + while line = ssl.gets + if line =~ /^STARTTLS$/ + ssl.accept + next + end + q.push(line) + end + th.kill if q.empty? + ssl.close + } +end diff --git a/test/test_asn1.rb b/test/test_asn1.rb new file mode 100644 index 00000000..3ea2638b --- /dev/null +++ b/test/test_asn1.rb @@ -0,0 +1,609 @@ +require_relative 'utils' + +class OpenSSL::TestASN1 < Test::Unit::TestCase + def test_decode + subj = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=TestCA") + key = OpenSSL::TestUtils::TEST_KEY_RSA1024 + now = Time.at(Time.now.to_i) # suppress usec + s = 0xdeadbeafdeadbeafdeadbeafdeadbeaf + exts = [ + ["basicConstraints","CA:TRUE,pathlen:1",true], + ["keyUsage","keyCertSign, cRLSign",true], + ["subjectKeyIdentifier","hash",false], + ] + dgst = OpenSSL::Digest::SHA1.new + cert = OpenSSL::TestUtils.issue_cert( + subj, key, s, now, now+3600, exts, nil, nil, dgst) + + + asn1 = OpenSSL::ASN1.decode(cert) + assert_equal(OpenSSL::ASN1::Sequence, asn1.class) + assert_equal(3, asn1.value.size) + tbs_cert, sig_alg, sig_val = *asn1.value + + assert_equal(OpenSSL::ASN1::Sequence, tbs_cert.class) + assert_equal(8, tbs_cert.value.size) + + version = tbs_cert.value[0] + assert_equal(:CONTEXT_SPECIFIC, version.tag_class) + assert_equal(0, version.tag) + assert_equal(1, version.value.size) + assert_equal(OpenSSL::ASN1::Integer, version.value[0].class) + assert_equal(2, version.value[0].value) + + serial = tbs_cert.value[1] + assert_equal(OpenSSL::ASN1::Integer, serial.class) + assert_equal(0xdeadbeafdeadbeafdeadbeafdeadbeaf, serial.value) + + sig = tbs_cert.value[2] + assert_equal(OpenSSL::ASN1::Sequence, sig.class) + assert_equal(2, sig.value.size) + assert_equal(OpenSSL::ASN1::ObjectId, sig.value[0].class) + assert_equal("1.2.840.113549.1.1.5", sig.value[0].oid) + assert_equal(OpenSSL::ASN1::Null, sig.value[1].class) + + dn = tbs_cert.value[3] # issuer + assert_equal(subj.hash, OpenSSL::X509::Name.new(dn).hash) + assert_equal(OpenSSL::ASN1::Sequence, dn.class) + assert_equal(3, dn.value.size) + assert_equal(OpenSSL::ASN1::Set, dn.value[0].class) + assert_equal(OpenSSL::ASN1::Set, dn.value[1].class) + assert_equal(OpenSSL::ASN1::Set, dn.value[2].class) + assert_equal(1, dn.value[0].value.size) + assert_equal(1, dn.value[1].value.size) + assert_equal(1, dn.value[2].value.size) + assert_equal(OpenSSL::ASN1::Sequence, dn.value[0].value[0].class) + assert_equal(OpenSSL::ASN1::Sequence, dn.value[1].value[0].class) + assert_equal(OpenSSL::ASN1::Sequence, dn.value[2].value[0].class) + assert_equal(2, dn.value[0].value[0].value.size) + assert_equal(2, dn.value[1].value[0].value.size) + assert_equal(2, dn.value[2].value[0].value.size) + oid, value = *dn.value[0].value[0].value + assert_equal(OpenSSL::ASN1::ObjectId, oid.class) + assert_equal("0.9.2342.19200300.100.1.25", oid.oid) + assert_equal(OpenSSL::ASN1::IA5String, value.class) + assert_equal("org", value.value) + oid, value = *dn.value[1].value[0].value + assert_equal(OpenSSL::ASN1::ObjectId, oid.class) + assert_equal("0.9.2342.19200300.100.1.25", oid.oid) + assert_equal(OpenSSL::ASN1::IA5String, value.class) + assert_equal("ruby-lang", value.value) + oid, value = *dn.value[2].value[0].value + assert_equal(OpenSSL::ASN1::ObjectId, oid.class) + assert_equal("2.5.4.3", oid.oid) + assert_equal(OpenSSL::ASN1::UTF8String, value.class) + assert_equal("TestCA", value.value) + + validity = tbs_cert.value[4] + assert_equal(OpenSSL::ASN1::Sequence, validity.class) + assert_equal(2, validity.value.size) + assert_equal(OpenSSL::ASN1::UTCTime, validity.value[0].class) + assert_equal(now, validity.value[0].value) + assert_equal(OpenSSL::ASN1::UTCTime, validity.value[1].class) + assert_equal(now+3600, validity.value[1].value) + + dn = tbs_cert.value[5] # subject + assert_equal(subj.hash, OpenSSL::X509::Name.new(dn).hash) + assert_equal(OpenSSL::ASN1::Sequence, dn.class) + assert_equal(3, dn.value.size) + assert_equal(OpenSSL::ASN1::Set, dn.value[0].class) + assert_equal(OpenSSL::ASN1::Set, dn.value[1].class) + assert_equal(OpenSSL::ASN1::Set, dn.value[2].class) + assert_equal(1, dn.value[0].value.size) + assert_equal(1, dn.value[1].value.size) + assert_equal(1, dn.value[2].value.size) + assert_equal(OpenSSL::ASN1::Sequence, dn.value[0].value[0].class) + assert_equal(OpenSSL::ASN1::Sequence, dn.value[1].value[0].class) + assert_equal(OpenSSL::ASN1::Sequence, dn.value[2].value[0].class) + assert_equal(2, dn.value[0].value[0].value.size) + assert_equal(2, dn.value[1].value[0].value.size) + assert_equal(2, dn.value[2].value[0].value.size) + oid, value = *dn.value[0].value[0].value + assert_equal(OpenSSL::ASN1::ObjectId, oid.class) + assert_equal("0.9.2342.19200300.100.1.25", oid.oid) + assert_equal(OpenSSL::ASN1::IA5String, value.class) + assert_equal("org", value.value) + oid, value = *dn.value[1].value[0].value + assert_equal(OpenSSL::ASN1::ObjectId, oid.class) + assert_equal("0.9.2342.19200300.100.1.25", oid.oid) + assert_equal(OpenSSL::ASN1::IA5String, value.class) + assert_equal("ruby-lang", value.value) + oid, value = *dn.value[2].value[0].value + assert_equal(OpenSSL::ASN1::ObjectId, oid.class) + assert_equal("2.5.4.3", oid.oid) + assert_equal(OpenSSL::ASN1::UTF8String, value.class) + assert_equal("TestCA", value.value) + + pkey = tbs_cert.value[6] + assert_equal(OpenSSL::ASN1::Sequence, pkey.class) + assert_equal(2, pkey.value.size) + assert_equal(OpenSSL::ASN1::Sequence, pkey.value[0].class) + assert_equal(2, pkey.value[0].value.size) + assert_equal(OpenSSL::ASN1::ObjectId, pkey.value[0].value[0].class) + assert_equal("1.2.840.113549.1.1.1", pkey.value[0].value[0].oid) + assert_equal(OpenSSL::ASN1::BitString, pkey.value[1].class) + assert_equal(0, pkey.value[1].unused_bits) + spkey = OpenSSL::ASN1.decode(pkey.value[1].value) + assert_equal(OpenSSL::ASN1::Sequence, spkey.class) + assert_equal(2, spkey.value.size) + assert_equal(OpenSSL::ASN1::Integer, spkey.value[0].class) + assert_equal(143085709396403084580358323862163416700436550432664688288860593156058579474547937626086626045206357324274536445865308750491138538454154232826011964045825759324933943290377903384882276841880081931690695505836279972214003660451338124170055999155993192881685495391496854691199517389593073052473319331505702779271, spkey.value[0].value) + assert_equal(OpenSSL::ASN1::Integer, spkey.value[1].class) + assert_equal(65537, spkey.value[1].value) + + extensions = tbs_cert.value[7] + assert_equal(:CONTEXT_SPECIFIC, extensions.tag_class) + assert_equal(3, extensions.tag) + assert_equal(1, extensions.value.size) + assert_equal(OpenSSL::ASN1::Sequence, extensions.value[0].class) + assert_equal(3, extensions.value[0].value.size) + + ext = extensions.value[0].value[0] # basicConstraints + assert_equal(OpenSSL::ASN1::Sequence, ext.class) + assert_equal(3, ext.value.size) + assert_equal(OpenSSL::ASN1::ObjectId, ext.value[0].class) + assert_equal("2.5.29.19", ext.value[0].oid) + assert_equal(OpenSSL::ASN1::Boolean, ext.value[1].class) + assert_equal(true, ext.value[1].value) + assert_equal(OpenSSL::ASN1::OctetString, ext.value[2].class) + extv = OpenSSL::ASN1.decode(ext.value[2].value) + assert_equal(OpenSSL::ASN1::Sequence, extv.class) + assert_equal(2, extv.value.size) + assert_equal(OpenSSL::ASN1::Boolean, extv.value[0].class) + assert_equal(true, extv.value[0].value) + assert_equal(OpenSSL::ASN1::Integer, extv.value[1].class) + assert_equal(1, extv.value[1].value) + + ext = extensions.value[0].value[1] # keyUsage + assert_equal(OpenSSL::ASN1::Sequence, ext.class) + assert_equal(3, ext.value.size) + assert_equal(OpenSSL::ASN1::ObjectId, ext.value[0].class) + assert_equal("2.5.29.15", ext.value[0].oid) + assert_equal(OpenSSL::ASN1::Boolean, ext.value[1].class) + assert_equal(true, ext.value[1].value) + assert_equal(OpenSSL::ASN1::OctetString, ext.value[2].class) + extv = OpenSSL::ASN1.decode(ext.value[2].value) + assert_equal(OpenSSL::ASN1::BitString, extv.class) + str = "\000"; str[0] = 0b00000110.chr + assert_equal(str, extv.value) + + ext = extensions.value[0].value[2] # subjetKeyIdentifier + assert_equal(OpenSSL::ASN1::Sequence, ext.class) + assert_equal(2, ext.value.size) + assert_equal(OpenSSL::ASN1::ObjectId, ext.value[0].class) + assert_equal("2.5.29.14", ext.value[0].oid) + assert_equal(OpenSSL::ASN1::OctetString, ext.value[1].class) + extv = OpenSSL::ASN1.decode(ext.value[1].value) + assert_equal(OpenSSL::ASN1::OctetString, extv.class) + sha1 = OpenSSL::Digest::SHA1.new + sha1.update(pkey.value[1].value) + assert_equal(sha1.digest, extv.value) + + assert_equal(OpenSSL::ASN1::Sequence, sig_alg.class) + assert_equal(2, sig_alg.value.size) + assert_equal(OpenSSL::ASN1::ObjectId, pkey.value[0].value[0].class) + assert_equal("1.2.840.113549.1.1.1", pkey.value[0].value[0].oid) + assert_equal(OpenSSL::ASN1::Null, pkey.value[0].value[1].class) + + assert_equal(OpenSSL::ASN1::BitString, sig_val.class) + cululated_sig = key.sign(OpenSSL::Digest::SHA1.new, tbs_cert.to_der) + assert_equal(cululated_sig, sig_val.value) + end + + def test_encode_boolean + encode_decode_test(OpenSSL::ASN1::Boolean, [true, false]) + end + + def test_encode_integer + encode_decode_test(OpenSSL::ASN1::Integer, [72, -127, -128, 128, -1, 0, 1, -(2**12345), 2**12345]) + end + + def test_encode_nil + m = OpenSSL::ASN1 + [ + m::Boolean, m::Integer, m::BitString, m::OctetString, + m::ObjectId, m::Enumerated, m::UTF8String, m::UTCTime, + m::GeneralizedTime, m::Sequence, m::Set + ].each do |klass| + #Primitives raise TypeError, Constructives NoMethodError + assert_raise(TypeError, NoMethodError) { klass.send(:new, nil).to_der } + end + end + + def encode_decode_test(type, values) + values.each do |v| + assert_equal(v, OpenSSL::ASN1.decode(type.new(v).to_der).value) + end + end + + def test_decode_pem #should fail gracefully (cf. [ruby-dev:44542]) + pem = <<-_EOS_ +-----BEGIN CERTIFICATE----- +MIIC8zCCAdugAwIBAgIBATANBgkqhkiG9w0BAQUFADA9MRMwEQYKCZImiZPyLGQB +GRYDb3JnMRkwFwYKCZImiZPyLGQBGRYJcnVieS1sYW5nMQswCQYDVQQDDAJDQTAe +Fw0xMTA5MjUxMzQ4MjZaFw0xMTA5MjUxNDQ4MjZaMD0xEzARBgoJkiaJk/IsZAEZ +FgNvcmcxGTAXBgoJkiaJk/IsZAEZFglydWJ5LWxhbmcxCzAJBgNVBAMMAkNBMIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuV9ht9J7k4NBs38jOXvvTKY9 +gW8nLICSno5EETR1cuF7i4pNs9I1QJGAFAX0BEO4KbzXmuOvfCpD3CU+Slp1enen +fzq/t/e/1IRW0wkJUJUFQign4CtrkJL+P07yx18UjyPlBXb81ApEmAB5mrJVSrWm +qbjs07JbuS4QQGGXLc+Su96DkYKmSNVjBiLxVVSpyZfAY3hD37d60uG+X8xdW5v6 +8JkRFIhdGlb6JL8fllf/A/blNwdJOhVr9mESHhwGjwfSeTDPfd8ZLE027E5lyAVX +9KZYcU00mOX+fdxOSnGqS/8JDRh0EPHDL15RcJjV2J6vZjPb0rOYGDoMcH+94wID +AQABMA0GCSqGSIb3DQEBBQUAA4IBAQAiAtrIr1pLX4GYN5klviWKb8HC9ICYuAFI +NfE3FwqzErEVXotuMe3yPVyB3Bv6rjYY/x5EtS5+WPTbHlvHZTkfcsnTpizcn4mW +dJ6dDRaFCHt1YKKjUxqBt9lvvrc3nReYZN/P+s1mrDhWzGf8iPZgf8sFUHgnaK7W +CXRVXmPFgCDRNpDDVQ0MQkr509yYfTH+dujNzqTCwSvkyZFyQ7Oe8Yj0VR6kquG3 +rEzBQ0F9dUyqQ9gyRg8KHhDfv9HzT1d/rnUZMkoombwYBRIUChGCYV0GnJcan2Zm +/93PnPG1IvPjYNd5VlV+sXSnaxQn974HRCsMv7jA8BD6IgSaX6WK +-----END CERTIFICATE----- + _EOS_ + assert_raise(OpenSSL::ASN1::ASN1Error) { OpenSSL::ASN1.decode(pem) } + assert_raise(OpenSSL::ASN1::ASN1Error) { OpenSSL::ASN1.decode_all(pem) } + end + + def test_primitive_cannot_set_infinite_length + begin + prim = OpenSSL::ASN1::Integer.new(50) + assert_equal(false, prim.infinite_length) + prim.infinite_length = true + flunk('Could set infinite length on primitive value') + rescue NoMethodError + #ok + end + end + + def test_decode_all + expected = %w{ 02 01 01 02 01 02 02 01 03 } + raw = [expected.join('')].pack('H*') + ary = OpenSSL::ASN1.decode_all(raw) + assert_equal(3, ary.size) + ary.each_with_index do |asn1, i| + assert_universal(OpenSSL::ASN1::INTEGER, asn1) + assert_equal(i + 1, asn1.value) + end + end + + def test_decode_utctime + expected = Time.at 1374535380 + assert_equal expected, OpenSSL::ASN1.decode("\x17\v1307222323Z").value + + expected += 17 + assert_equal expected, OpenSSL::ASN1.decode("\x17\r130722232317Z").value + end + + def test_create_inf_length_primitive + expected = %w{ 24 80 04 01 61 00 00 } + raw = [expected.join('')].pack('H*') + val = OpenSSL::ASN1::OctetString.new('a') + cons = OpenSSL::ASN1::Constructive.new([val, + OpenSSL::ASN1::EndOfContent.new], + OpenSSL::ASN1::OCTET_STRING, + nil, + :UNIVERSAL) + cons.infinite_length = true + assert_equal(nil, cons.tagging) + assert_equal(raw, cons.to_der) + asn1 = OpenSSL::ASN1.decode(raw) + assert(asn1.infinite_length) + assert_equal(raw, asn1.to_der) + end + + def test_cons_without_inf_length_forbidden + assert_raise(OpenSSL::ASN1::ASN1Error) do + val = OpenSSL::ASN1::OctetString.new('a') + cons = OpenSSL::ASN1::Constructive.new([val], + OpenSSL::ASN1::OCTET_STRING, + nil, + :UNIVERSAL) + cons.to_der + end + end + + def test_cons_without_array_forbidden + assert_raise(OpenSSL::ASN1::ASN1Error) do + val = OpenSSL::ASN1::OctetString.new('a') + cons = OpenSSL::ASN1::Constructive.new(val, + OpenSSL::ASN1::OCTET_STRING, + nil, + :UNIVERSAL) + cons.infinite_length = true + cons.to_der + end + end + + def test_parse_empty_sequence + expected = %w{ A0 07 30 02 30 00 02 01 00 } + raw = [expected.join('')].pack('H*') + asn1 = OpenSSL::ASN1.decode(raw) + assert_equal(raw, asn1.to_der) + assert_equal(2, asn1.value.size) + seq = asn1.value[0] + assert_equal(1, seq.value.size) + inner_seq = seq.value[0] + assert_equal(0, inner_seq.value.size) + end + + def test_parse_tagged_0_infinite + expected = %w{ 30 80 02 01 01 80 01 02 00 00 } + raw = [expected.join('')].pack('H*') + asn1 = OpenSSL::ASN1.decode(raw) + assert_equal(3, asn1.value.size) + int = asn1.value[0] + assert_universal(OpenSSL::ASN1::INTEGER, int) + tagged = asn1.value[1] + assert_equal(0, tagged.tag) + assert_universal(OpenSSL::ASN1::EOC, asn1.value[2]) + assert_equal(raw, asn1.to_der) + end + + def test_seq_infinite_length + begin + content = [ OpenSSL::ASN1::Null.new(nil), + OpenSSL::ASN1::EndOfContent.new ] + cons = OpenSSL::ASN1::Sequence.new(content) + cons.infinite_length = true + expected = %w{ 30 80 05 00 00 00 } + raw = [expected.join('')].pack('H*') + assert_equal(raw, cons.to_der) + assert_equal(raw, OpenSSL::ASN1.decode(raw).to_der) + end + end + + def test_set_infinite_length + begin + content = [ OpenSSL::ASN1::Null.new(nil), + OpenSSL::ASN1::EndOfContent.new() ] + cons = OpenSSL::ASN1::Set.new(content) + cons.infinite_length = true + expected = %w{ 31 80 05 00 00 00 } + raw = [expected.join('')].pack('H*') + assert_equal(raw, cons.to_der) + assert_equal(raw, OpenSSL::ASN1.decode(raw).to_der) + end + end + + def test_octet_string_infinite_length + begin + octets = [ OpenSSL::ASN1::OctetString.new('aaa'), + OpenSSL::ASN1::EndOfContent.new() ] + cons = OpenSSL::ASN1::Constructive.new( + octets, + OpenSSL::ASN1::OCTET_STRING, + nil, + :UNIVERSAL) + cons.infinite_length = true + expected = %w{ 24 80 04 03 61 61 61 00 00 } + raw = [expected.join('')].pack('H*') + assert_equal(raw, cons.to_der) + assert_equal(raw, OpenSSL::ASN1.decode(raw).to_der) + end + end + + def test_prim_explicit_tagging + begin + oct_str = OpenSSL::ASN1::OctetString.new("a", 0, :EXPLICIT) + expected = %w{ A0 03 04 01 61 } + raw = [expected.join('')].pack('H*') + assert_equal(raw, oct_str.to_der) + assert_equal(raw, OpenSSL::ASN1.decode(raw).to_der) + end + end + + def test_prim_explicit_tagging_tag_class + begin + oct_str = OpenSSL::ASN1::OctetString.new("a", 0, :EXPLICIT) + oct_str2 = OpenSSL::ASN1::OctetString.new( + "a", + 0, + :EXPLICIT, + :CONTEXT_SPECIFIC) + assert_equal(oct_str.to_der, oct_str2.to_der) + end + end + + def test_prim_implicit_tagging + begin + int = OpenSSL::ASN1::Integer.new(1, 0, :IMPLICIT) + expected = %w{ 80 01 01 } + raw = [expected.join('')].pack('H*') + assert_equal(raw, int.to_der) + assert_equal(raw, OpenSSL::ASN1.decode(raw).to_der) + end + end + + def test_prim_implicit_tagging_tag_class + begin + int = OpenSSL::ASN1::Integer.new(1, 0, :IMPLICIT) + int2 = OpenSSL::ASN1::Integer.new(1, 0, :IMPLICIT, :CONTEXT_SPECIFIC); + assert_equal(int.to_der, int2.to_der) + end + end + + def test_cons_explicit_tagging + begin + content = [ OpenSSL::ASN1::PrintableString.new('abc') ] + seq = OpenSSL::ASN1::Sequence.new(content, 2, :EXPLICIT) + expected = %w{ A2 07 30 05 13 03 61 62 63 } + raw = [expected.join('')].pack('H*') + assert_equal(raw, seq.to_der) + assert_equal(raw, OpenSSL::ASN1.decode(raw).to_der) + end + end + + def test_cons_explicit_tagging_inf_length + begin + content = [ OpenSSL::ASN1::PrintableString.new('abc') , + OpenSSL::ASN1::EndOfContent.new() ] + seq = OpenSSL::ASN1::Sequence.new(content, 2, :EXPLICIT) + seq.infinite_length = true + expected = %w{ A2 80 30 80 13 03 61 62 63 00 00 00 00 } + raw = [expected.join('')].pack('H*') + assert_equal(raw, seq.to_der) + assert_equal(raw, OpenSSL::ASN1.decode(raw).to_der) + end + end + + def test_cons_implicit_tagging + begin + content = [ OpenSSL::ASN1::Null.new(nil) ] + seq = OpenSSL::ASN1::Sequence.new(content, 1, :IMPLICIT) + expected = %w{ A1 02 05 00 } + raw = [expected.join('')].pack('H*') + assert_equal(raw, seq.to_der) + assert_equal(raw, OpenSSL::ASN1.decode(raw).to_der) + end + end + + def test_cons_implicit_tagging_inf_length + begin + content = [ OpenSSL::ASN1::Null.new(nil), + OpenSSL::ASN1::EndOfContent.new() ] + seq = OpenSSL::ASN1::Sequence.new(content, 1, :IMPLICIT) + seq.infinite_length = true + expected = %w{ A1 80 05 00 00 00 } + raw = [expected.join('')].pack('H*') + assert_equal(raw, seq.to_der) + assert_equal(raw, OpenSSL::ASN1.decode(raw).to_der) + end + end + + def test_octet_string_infinite_length_explicit_tagging + begin + octets = [ OpenSSL::ASN1::OctetString.new('aaa'), + OpenSSL::ASN1::EndOfContent.new() ] + cons = OpenSSL::ASN1::Constructive.new( + octets, + 1, + :EXPLICIT) + cons.infinite_length = true + expected = %w{ A1 80 24 80 04 03 61 61 61 00 00 00 00 } + raw = [expected.join('')].pack('H*') + assert_equal(raw, cons.to_der) + assert_equal(raw, OpenSSL::ASN1.decode(raw).to_der) + end + end + + def test_octet_string_infinite_length_implicit_tagging + begin + octets = [ OpenSSL::ASN1::OctetString.new('aaa'), + OpenSSL::ASN1::EndOfContent.new() ] + cons = OpenSSL::ASN1::Constructive.new( + octets, + 0, + :IMPLICIT) + cons.infinite_length = true + expected = %w{ A0 80 04 03 61 61 61 00 00 } + raw = [expected.join('')].pack('H*') + assert_equal(raw, cons.to_der) + assert_equal(raw, OpenSSL::ASN1.decode(raw).to_der) + end + end + + def test_recursive_octet_string_infinite_length + begin + octets_sub1 = [ OpenSSL::ASN1::OctetString.new("\x01"), + OpenSSL::ASN1::EndOfContent.new() ] + octets_sub2 = [ OpenSSL::ASN1::OctetString.new("\x02"), + OpenSSL::ASN1::EndOfContent.new() ] + container1 = OpenSSL::ASN1::Constructive.new( + octets_sub1, + OpenSSL::ASN1::OCTET_STRING, + nil, + :UNIVERSAL) + container1.infinite_length = true + container2 = OpenSSL::ASN1::Constructive.new( + octets_sub2, + OpenSSL::ASN1::OCTET_STRING, + nil, + :UNIVERSAL) + container2.infinite_length = true + octets3 = OpenSSL::ASN1::OctetString.new("\x03") + + octets = [ container1, container2, octets3, + OpenSSL::ASN1::EndOfContent.new() ] + cons = OpenSSL::ASN1::Constructive.new( + octets, + OpenSSL::ASN1::OCTET_STRING, + nil, + :UNIVERSAL) + cons.infinite_length = true + expected = %w{ 24 80 24 80 04 01 01 00 00 24 80 04 01 02 00 00 04 01 03 00 00 } + raw = [expected.join('')].pack('H*') + assert_equal(raw, cons.to_der) + assert_equal(raw, OpenSSL::ASN1.decode(raw).to_der) + end + end + + def test_bit_string_infinite_length + begin + content = [ OpenSSL::ASN1::BitString.new("\x01"), + OpenSSL::ASN1::EndOfContent.new() ] + cons = OpenSSL::ASN1::Constructive.new( + content, + OpenSSL::ASN1::BIT_STRING, + nil, + :UNIVERSAL) + cons.infinite_length = true + expected = %w{ 23 80 03 02 00 01 00 00 } + raw = [expected.join('')].pack('H*') + assert_equal(raw, cons.to_der) + assert_equal(raw, OpenSSL::ASN1.decode(raw).to_der) + end + end + + def test_primitive_inf_length + assert_raises(OpenSSL::ASN1::ASN1Error) do + spec = %w{ 02 80 02 01 01 00 00 } + raw = [spec.join('')].pack('H*') + OpenSSL::ASN1.decode(raw) + OpenSSL::ASN1.decode_all(raw) + end + end + + def test_recursive_octet_string_parse + test = %w{ 24 80 24 80 04 01 01 00 00 24 80 04 01 02 00 00 04 01 03 00 00 } + raw = [test.join('')].pack('H*') + asn1 = OpenSSL::ASN1.decode(raw) + assert_equal(OpenSSL::ASN1::Constructive, asn1.class) + assert_universal(OpenSSL::ASN1::OCTET_STRING, asn1) + assert_equal(true, asn1.infinite_length) + assert_equal(4, asn1.value.size) + nested1 = asn1.value[0] + assert_equal(OpenSSL::ASN1::Constructive, nested1.class) + assert_universal(OpenSSL::ASN1::OCTET_STRING, nested1) + assert_equal(true, nested1.infinite_length) + assert_equal(2, nested1.value.size) + oct1 = nested1.value[0] + assert_universal(OpenSSL::ASN1::OCTET_STRING, oct1) + assert_equal(false, oct1.infinite_length) + assert_universal(OpenSSL::ASN1::EOC, nested1.value[1]) + assert_equal(false, nested1.value[1].infinite_length) + nested2 = asn1.value[1] + assert_equal(OpenSSL::ASN1::Constructive, nested2.class) + assert_universal(OpenSSL::ASN1::OCTET_STRING, nested2) + assert_equal(true, nested2.infinite_length) + assert_equal(2, nested2.value.size) + oct2 = nested2.value[0] + assert_universal(OpenSSL::ASN1::OCTET_STRING, oct2) + assert_equal(false, oct2.infinite_length) + assert_universal(OpenSSL::ASN1::EOC, nested2.value[1]) + assert_equal(false, nested2.value[1].infinite_length) + oct3 = asn1.value[2] + assert_universal(OpenSSL::ASN1::OCTET_STRING, oct3) + assert_equal(false, oct3.infinite_length) + assert_universal(OpenSSL::ASN1::EOC, asn1.value[3]) + assert_equal(false, asn1.value[3].infinite_length) + end + + private + + def assert_universal(tag, asn1) + assert_equal(tag, asn1.tag) + if asn1.respond_to?(:tagging) + assert_nil(asn1.tagging) + end + assert_equal(:UNIVERSAL, asn1.tag_class) + end + +end if defined?(OpenSSL) + diff --git a/test/test_bn.rb b/test/test_bn.rb new file mode 100644 index 00000000..27bbcdfe --- /dev/null +++ b/test/test_bn.rb @@ -0,0 +1,52 @@ +require_relative 'utils' + +if defined?(OpenSSL) + +class OpenSSL::TestBN < Test::Unit::TestCase + def test_new_str + e1 = OpenSSL::BN.new(999.to_s(16), 16) # OpenSSL::BN.new(str, 16) must be most stable + e2 = OpenSSL::BN.new((2**107-1).to_s(16), 16) + assert_equal(e1, OpenSSL::BN.new("999")) + assert_equal(e2, OpenSSL::BN.new((2**107-1).to_s)) + assert_equal(e1, OpenSSL::BN.new("999", 10)) + assert_equal(e2, OpenSSL::BN.new((2**107-1).to_s, 10)) + assert_equal(e1, OpenSSL::BN.new("\x03\xE7", 2)) + assert_equal(e2, OpenSSL::BN.new("\a\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 2)) + assert_equal(e1, OpenSSL::BN.new("\x00\x00\x00\x02\x03\xE7", 0)) + assert_equal(e2, OpenSSL::BN.new("\x00\x00\x00\x0E\a\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 0)) + end + + def test_new_bn + e1 = OpenSSL::BN.new(999.to_s(16), 16) + e2 = OpenSSL::BN.new((2**107-1).to_s(16), 16) + assert_equal(e1, OpenSSL::BN.new(e1)) + assert_equal(e2, OpenSSL::BN.new(e2)) + end + + def test_new_integer + assert_equal(999.to_bn, OpenSSL::BN.new(999)) + assert_equal((2 ** 107 - 1).to_bn, OpenSSL::BN.new(2 ** 107 - 1)) + assert_equal(-999.to_bn, OpenSSL::BN.new(-999)) + assert_equal((-(2 ** 107 - 1)).to_bn, OpenSSL::BN.new(-(2 ** 107 - 1))) + end + + def test_to_bn + e1 = OpenSSL::BN.new(999.to_s(16), 16) + e2 = OpenSSL::BN.new((2**107-1).to_s(16), 16) + assert_equal(e1, 999.to_bn) + assert_equal(e2, (2**107-1).to_bn) + end + + def test_prime_p + assert_equal(true, OpenSSL::BN.new((2 ** 107 - 1).to_s(16), 16).prime?) + assert_equal(true, OpenSSL::BN.new((2 ** 127 - 1).to_s(16), 16).prime?(1)) + end + + def test_cmp_nil + bn = OpenSSL::BN.new('1') + assert_equal(false, bn == nil) + assert_equal(true, bn != nil) + end +end + +end diff --git a/test/test_buffering.rb b/test/test_buffering.rb new file mode 100644 index 00000000..c4894e12 --- /dev/null +++ b/test/test_buffering.rb @@ -0,0 +1,87 @@ +require_relative 'utils' +require 'stringio' + +class OpenSSL::TestBuffering < Test::Unit::TestCase + + class IO + include OpenSSL::Buffering + + attr_accessor :sync + + def initialize + @io = "" + def @io.sync + true + end + + super + + @sync = false + end + + def string + @io + end + + def sysread(size) + str = @io.slice!(0, size) + raise EOFError if str.empty? + str + end + + def syswrite(str) + @io << str + str.size + end + end + + def setup + @io = IO.new + end + + def test_flush + @io.write 'a' + + refute @io.sync + assert_empty @io.string + + assert_equal @io, @io.flush + + refute @io.sync + assert_equal 'a', @io.string + end + + def test_flush_error + @io.write 'a' + + refute @io.sync + assert_empty @io.string + + def @io.syswrite *a + raise SystemCallError, 'fail' + end + + assert_raises SystemCallError do + @io.flush + end + + refute @io.sync, 'sync must not change' + end + + def test_getc + @io.syswrite('abc') + assert_equal(?a, @io.getc) + assert_equal(?b, @io.getc) + assert_equal(?c, @io.getc) + end + + def test_each_byte + @io.syswrite('abc') + res = [] + @io.each_byte do |c| + res << c + end + assert_equal([97, 98, 99], res) + end + +end if defined?(OpenSSL) diff --git a/test/test_cipher.rb b/test/test_cipher.rb new file mode 100644 index 00000000..156fa2a9 --- /dev/null +++ b/test/test_cipher.rb @@ -0,0 +1,255 @@ +require_relative 'utils' + +if defined?(OpenSSL) + +class OpenSSL::TestCipher < Test::Unit::TestCase + + class << self + + def has_cipher?(name) + ciphers = OpenSSL::Cipher.ciphers + # redefine method so we can use the cached ciphers value from the closure + # and need not recompute the list each time + define_singleton_method :has_cipher? do |name| + ciphers.include?(name) + end + has_cipher?(name) + end + + def has_ciphers?(list) + list.all? { |name| has_cipher?(name) } + end + + end + + def setup + @c1 = OpenSSL::Cipher::Cipher.new("DES-EDE3-CBC") + @c2 = OpenSSL::Cipher::DES.new(:EDE3, "CBC") + @key = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + @iv = "\0\0\0\0\0\0\0\0" + @hexkey = "0000000000000000000000000000000000000000000000" + @hexiv = "0000000000000000" + @data = "DATA" + end + + def teardown + @c1 = @c2 = nil + end + + def test_crypt + @c1.encrypt.pkcs5_keyivgen(@key, @iv) + @c2.encrypt.pkcs5_keyivgen(@key, @iv) + s1 = @c1.update(@data) + @c1.final + s2 = @c2.update(@data) + @c2.final + assert_equal(s1, s2, "encrypt") + + @c1.decrypt.pkcs5_keyivgen(@key, @iv) + @c2.decrypt.pkcs5_keyivgen(@key, @iv) + assert_equal(@data, @c1.update(s1)+@c1.final, "decrypt") + assert_equal(@data, @c2.update(s2)+@c2.final, "decrypt") + end + + def test_info + assert_equal("DES-EDE3-CBC", @c1.name, "name") + assert_equal("DES-EDE3-CBC", @c2.name, "name") + assert_kind_of(Fixnum, @c1.key_len, "key_len") + assert_kind_of(Fixnum, @c1.iv_len, "iv_len") + end + + def test_dup + assert_equal(@c1.name, @c1.dup.name, "dup") + assert_equal(@c1.name, @c1.clone.name, "clone") + @c1.encrypt + @c1.key = @key + @c1.iv = @iv + tmpc = @c1.dup + s1 = @c1.update(@data) + @c1.final + s2 = tmpc.update(@data) + tmpc.final + assert_equal(s1, s2, "encrypt dup") + end + + def test_reset + @c1.encrypt + @c1.key = @key + @c1.iv = @iv + s1 = @c1.update(@data) + @c1.final + @c1.reset + s2 = @c1.update(@data) + @c1.final + assert_equal(s1, s2, "encrypt reset") + end + + def test_empty_data + @c1.encrypt + assert_raise(ArgumentError){ @c1.update("") } + end + + def test_initialize + assert_raise(RuntimeError) {@c1.__send__(:initialize, "DES-EDE3-CBC")} + assert_raise(RuntimeError) {OpenSSL::Cipher.allocate.final} + end + + def test_ctr_if_exists + begin + cipher = OpenSSL::Cipher.new('aes-128-ctr') + cipher.encrypt + cipher.pkcs5_keyivgen('password') + c = cipher.update('hello,world') + cipher.final + cipher.decrypt + cipher.pkcs5_keyivgen('password') + assert_equal('hello,world', cipher.update(c) + cipher.final) + end + end if has_cipher?('aes-128-ctr') + + if OpenSSL::OPENSSL_VERSION_NUMBER > 0x00907000 + def test_ciphers + OpenSSL::Cipher.ciphers.each{|name| + next if /netbsd/ =~ RUBY_PLATFORM && /idea|rc5/i =~ name + assert(OpenSSL::Cipher::Cipher.new(name).is_a?(OpenSSL::Cipher::Cipher)) + } + end + + def test_AES + pt = File.read(__FILE__) + %w(ECB CBC CFB OFB).each{|mode| + c1 = OpenSSL::Cipher::AES256.new(mode) + c1.encrypt + c1.pkcs5_keyivgen("passwd") + ct = c1.update(pt) + c1.final + + c2 = OpenSSL::Cipher::AES256.new(mode) + c2.decrypt + c2.pkcs5_keyivgen("passwd") + assert_equal(pt, c2.update(ct) + c2.final) + } + end + + def test_AES_crush + 500.times do + assert_nothing_raised("[Bug #2768]") do + # it caused OpenSSL SEGV by uninitialized key + OpenSSL::Cipher::AES128.new("ECB").update "." * 17 + end + end + end + end + + if has_ciphers?(['aes-128-gcm', 'aes-192-gcm', 'aes-256-gcm']) + + def test_authenticated + cipher = OpenSSL::Cipher.new('aes-128-gcm') + assert(cipher.authenticated?) + cipher = OpenSSL::Cipher.new('aes-128-cbc') + refute(cipher.authenticated?) + end + + def test_aes_gcm + ['aes-128-gcm', 'aes-192-gcm', 'aes-256-gcm'].each do |algo| + pt = "You should all use Authenticated Encryption!" + cipher, key, iv = new_encryptor(algo) + + cipher.auth_data = "aad" + ct = cipher.update(pt) + cipher.final + tag = cipher.auth_tag + assert_equal(16, tag.size) + + decipher = new_decryptor(algo, key, iv) + decipher.auth_tag = tag + decipher.auth_data = "aad" + + assert_equal(pt, decipher.update(ct) + decipher.final) + end + end + + def test_aes_gcm_short_tag + ['aes-128-gcm', 'aes-192-gcm', 'aes-256-gcm'].each do |algo| + pt = "You should all use Authenticated Encryption!" + cipher, key, iv = new_encryptor(algo) + + cipher.auth_data = "aad" + ct = cipher.update(pt) + cipher.final + tag = cipher.auth_tag(8) + assert_equal(8, tag.size) + + decipher = new_decryptor(algo, key, iv) + decipher.auth_tag = tag + decipher.auth_data = "aad" + + assert_equal(pt, decipher.update(ct) + decipher.final) + end + end + + def test_aes_gcm_wrong_tag + pt = "You should all use Authenticated Encryption!" + cipher, key, iv = new_encryptor('aes-128-gcm') + + cipher.auth_data = "aad" + ct = cipher.update(pt) + cipher.final + tag = cipher.auth_tag + + decipher = new_decryptor('aes-128-gcm', key, iv) + tag.setbyte(-1, (tag.getbyte(-1) + 1) & 0xff) + decipher.auth_tag = tag + decipher.auth_data = "aad" + + assert_raise OpenSSL::Cipher::CipherError do + decipher.update(ct) + decipher.final + end + end + + def test_aes_gcm_wrong_auth_data + pt = "You should all use Authenticated Encryption!" + cipher, key, iv = new_encryptor('aes-128-gcm') + + cipher.auth_data = "aad" + ct = cipher.update(pt) + cipher.final + tag = cipher.auth_tag + + decipher = new_decryptor('aes-128-gcm', key, iv) + decipher.auth_tag = tag + decipher.auth_data = "daa" + + assert_raise OpenSSL::Cipher::CipherError do + decipher.update(ct) + decipher.final + end + end + + def test_aes_gcm_wrong_ciphertext + pt = "You should all use Authenticated Encryption!" + cipher, key, iv = new_encryptor('aes-128-gcm') + + cipher.auth_data = "aad" + ct = cipher.update(pt) + cipher.final + tag = cipher.auth_tag + + decipher = new_decryptor('aes-128-gcm', key, iv) + decipher.auth_tag = tag + decipher.auth_data = "aad" + + assert_raise OpenSSL::Cipher::CipherError do + decipher.update(ct[0..-2] << ct[-1].succ) + decipher.final + end + end + + end + + private + + def new_encryptor(algo) + cipher = OpenSSL::Cipher.new(algo) + cipher.encrypt + key = cipher.random_key + iv = cipher.random_iv + [cipher, key, iv] + end + + def new_decryptor(algo, key, iv) + OpenSSL::Cipher.new(algo).tap do |cipher| + cipher.decrypt + cipher.key = key + cipher.iv = iv + end + end + +end + +end diff --git a/test/test_config.rb b/test/test_config.rb new file mode 100644 index 00000000..4ad90c43 --- /dev/null +++ b/test/test_config.rb @@ -0,0 +1,297 @@ +require_relative 'utils' + +class OpenSSL::TestConfig < Test::Unit::TestCase + def setup + file = Tempfile.open("openssl.cnf") + file << <<__EOD__ +HOME = . +[ ca ] +default_ca = CA_default +[ CA_default ] +dir = ./demoCA +certs = ./certs +__EOD__ + file.close + @tmpfile = file + @it = OpenSSL::Config.new(file.path) + end + + def teardown + @tmpfile.close! + end + + def test_constants + assert(defined?(OpenSSL::Config::DEFAULT_CONFIG_FILE)) + config_file = OpenSSL::Config::DEFAULT_CONFIG_FILE + skip "DEFAULT_CONFIG_FILE may return a wrong path on your platforms. [Bug #6830]" unless File.readable?(config_file) + assert_nothing_raised do + OpenSSL::Config.load(config_file) + end + end + + def test_s_parse + c = OpenSSL::Config.parse('') + assert_equal("[ default ]\n\n", c.to_s) + c = OpenSSL::Config.parse(@it.to_s) + assert_equal(['CA_default', 'ca', 'default'], c.sections.sort) + end + + def test_s_parse_format + c = OpenSSL::Config.parse(<<__EOC__) + baz =qx\t # "baz = qx" + +foo::bar = baz # shortcut section::key format + default::bar = baz # ditto +a=\t \t # "a = ": trailing spaces are ignored + =b # " = b": empty key + =c # " = c": empty key (override the above line) + d= # "c = ": trailing comment is ignored + +sq = 'foo''b\\'ar' + dq ="foo""''\\"" + dq2 = foo""bar +esc=a\\r\\n\\b\\tb +foo\\bar = foo\\b\\\\ar +foo\\bar::foo\\bar = baz +[default1 default2]\t\t # space is allowed in section name + fo =b ar # space allowed in value +[emptysection] + [doller ] +foo=bar +bar = $(foo) +baz = 123$(default::bar)456${foo}798 +qux = ${baz} +quxx = $qux.$qux +__EOC__ + assert_equal(['default', 'default1 default2', 'doller', 'emptysection', 'foo', 'foo\\bar'], c.sections.sort) + assert_equal(['', 'a', 'bar', 'baz', 'd', 'dq', 'dq2', 'esc', 'foo\\bar', 'sq'], c['default'].keys.sort) + assert_equal('c', c['default']['']) + assert_equal('', c['default']['a']) + assert_equal('qx', c['default']['baz']) + assert_equal('', c['default']['d']) + assert_equal('baz', c['default']['bar']) + assert_equal("foob'ar", c['default']['sq']) + assert_equal("foo''\"", c['default']['dq']) + assert_equal("foobar", c['default']['dq2']) + assert_equal("a\r\n\b\tb", c['default']['esc']) + assert_equal("foo\b\\ar", c['default']['foo\\bar']) + assert_equal('baz', c['foo']['bar']) + assert_equal('baz', c['foo\\bar']['foo\\bar']) + assert_equal('b ar', c['default1 default2']['fo']) + + # dolloer + assert_equal('bar', c['doller']['foo']) + assert_equal('bar', c['doller']['bar']) + assert_equal('123baz456bar798', c['doller']['baz']) + assert_equal('123baz456bar798', c['doller']['qux']) + assert_equal('123baz456bar798.123baz456bar798', c['doller']['quxx']) + + excn = assert_raise(OpenSSL::ConfigError) do + OpenSSL::Config.parse("foo = $bar") + end + assert_equal("error in line 1: variable has no value", excn.message) + + excn = assert_raise(OpenSSL::ConfigError) do + OpenSSL::Config.parse("foo = $(bar") + end + assert_equal("error in line 1: no close brace", excn.message) + + excn = assert_raise(OpenSSL::ConfigError) do + OpenSSL::Config.parse("f o =b ar # no space in key") + end + assert_equal("error in line 1: missing equal sign", excn.message) + + excn = assert_raise(OpenSSL::ConfigError) do + OpenSSL::Config.parse(<<__EOC__) +# comment 1 # comments + +# + # comment 2 +\t#comment 3 + [second ]\t +[third # section not terminated +__EOC__ + end + assert_equal("error in line 7: missing close square bracket", excn.message) + end + + def test_s_load + # alias of new + c = OpenSSL::Config.load + assert_equal("", c.to_s) + assert_equal([], c.sections) + # + Tempfile.create("openssl.cnf") {|file| + file.close + c = OpenSSL::Config.load(file.path) + assert_equal("[ default ]\n\n", c.to_s) + assert_equal(['default'], c.sections) + } + end + + def test_initialize + c = OpenSSL::Config.new + assert_equal("", c.to_s) + assert_equal([], c.sections) + end + + def test_initialize_with_empty_file + Tempfile.create("openssl.cnf") {|file| + file.close + c = OpenSSL::Config.new(file.path) + assert_equal("[ default ]\n\n", c.to_s) + assert_equal(['default'], c.sections) + } + end + + def test_initialize_with_example_file + assert_equal(['CA_default', 'ca', 'default'], @it.sections.sort) + end + + def test_get_value + assert_equal('CA_default', @it.get_value('ca', 'default_ca')) + assert_equal(nil, @it.get_value('ca', 'no such key')) + assert_equal(nil, @it.get_value('no such section', 'no such key')) + assert_equal('.', @it.get_value('', 'HOME')) + assert_raise(TypeError) do + @it.get_value(nil, 'HOME') # not allowed unlike Config#value + end + # fallback to 'default' ugly... + assert_equal('.', @it.get_value('unknown', 'HOME')) + end + + def test_get_value_ENV + key = ENV.keys.first + assert_not_nil(key) # make sure we have at least one ENV var. + assert_equal(ENV[key], @it.get_value('ENV', key)) + end + + def test_value + # supress deprecation warnings + OpenSSL::TestUtils.silent do + assert_equal('CA_default', @it.value('ca', 'default_ca')) + assert_equal(nil, @it.value('ca', 'no such key')) + assert_equal(nil, @it.value('no such section', 'no such key')) + assert_equal('.', @it.value('', 'HOME')) + assert_equal('.', @it.value(nil, 'HOME')) + assert_equal('.', @it.value('HOME')) + # fallback to 'default' ugly... + assert_equal('.', @it.value('unknown', 'HOME')) + end + end + + def test_value_ENV + OpenSSL::TestUtils.silent do + key = ENV.keys.first + assert_not_nil(key) # make sure we have at least one ENV var. + assert_equal(ENV[key], @it.value('ENV', key)) + end + end + + def test_aref + assert_equal({'HOME' => '.'}, @it['default']) + assert_equal({'dir' => './demoCA', 'certs' => './certs'}, @it['CA_default']) + assert_equal({}, @it['no_such_section']) + assert_equal({}, @it['']) + end + + def test_section + OpenSSL::TestUtils.silent do + assert_equal({'HOME' => '.'}, @it.section('default')) + assert_equal({'dir' => './demoCA', 'certs' => './certs'}, @it.section('CA_default')) + assert_equal({}, @it.section('no_such_section')) + assert_equal({}, @it.section('')) + end + end + + def test_sections + assert_equal(['CA_default', 'ca', 'default'], @it.sections.sort) + @it['new_section'] = {'foo' => 'bar'} + assert_equal(['CA_default', 'ca', 'default', 'new_section'], @it.sections.sort) + @it['new_section'] = {} + assert_equal(['CA_default', 'ca', 'default', 'new_section'], @it.sections.sort) + end + + def test_add_value + c = OpenSSL::Config.new + assert_equal("", c.to_s) + # add key + c.add_value('default', 'foo', 'bar') + assert_equal("[ default ]\nfoo=bar\n\n", c.to_s) + # add another key + c.add_value('default', 'baz', 'qux') + assert_equal('bar', c['default']['foo']) + assert_equal('qux', c['default']['baz']) + # update the value + c.add_value('default', 'baz', 'quxxx') + assert_equal('bar', c['default']['foo']) + assert_equal('quxxx', c['default']['baz']) + # add section and key + c.add_value('section', 'foo', 'bar') + assert_equal('bar', c['default']['foo']) + assert_equal('quxxx', c['default']['baz']) + assert_equal('bar', c['section']['foo']) + end + + def test_aset + @it['foo'] = {'bar' => 'baz'} + assert_equal({'bar' => 'baz'}, @it['foo']) + @it['foo'] = {'bar' => 'qux', 'baz' => 'quxx'} + assert_equal({'bar' => 'qux', 'baz' => 'quxx'}, @it['foo']) + + # OpenSSL::Config is add only for now. + @it['foo'] = {'foo' => 'foo'} + assert_equal({'foo' => 'foo', 'bar' => 'qux', 'baz' => 'quxx'}, @it['foo']) + # you cannot override or remove any section and key. + @it['foo'] = {} + assert_equal({'foo' => 'foo', 'bar' => 'qux', 'baz' => 'quxx'}, @it['foo']) + end + + def test_each + # each returns [section, key, value] array. + ary = @it.map { |e| e }.sort { |a, b| a[0] <=> b[0] } + assert_equal(4, ary.size) + assert_equal('CA_default', ary[0][0]) + assert_equal('CA_default', ary[1][0]) + assert_equal(["ca", "default_ca", "CA_default"], ary[2]) + assert_equal(["default", "HOME", "."], ary[3]) + end + + def test_to_s + c = OpenSSL::Config.parse("[empty]\n") + assert_equal("[ default ]\n\n[ empty ]\n\n", c.to_s) + end + + def test_inspect + assert_match(/#<OpenSSL::Config sections=\[.*\]>/, @it.inspect) + end + + def test_freeze + c = OpenSSL::Config.new + c['foo'] = [['key', 'value']] + c.freeze + + bug = '[ruby-core:18377]' + # RuntimeError for 1.9, TypeError for 1.8 + e = assert_raise(TypeError, bug) do + c['foo'] = [['key', 'wrong']] + end + assert_match(/can't modify/, e.message, bug) + end + + def test_dup + assert(!@it.sections.empty?) + c = @it.dup + assert_equal(@it.sections.sort, c.sections.sort) + @it['newsection'] = {'a' => 'b'} + assert_not_equal(@it.sections.sort, c.sections.sort) + end + + def test_clone + assert(!@it.sections.empty?) + c = @it.clone + assert_equal(@it.sections.sort, c.sections.sort) + @it['newsection'] = {'a' => 'b'} + assert_not_equal(@it.sections.sort, c.sections.sort) + end +end if defined?(OpenSSL) diff --git a/test/test_digest.rb b/test/test_digest.rb new file mode 100644 index 00000000..c2a3f705 --- /dev/null +++ b/test/test_digest.rb @@ -0,0 +1,126 @@ +require_relative 'utils' + +if defined?(OpenSSL) + +class OpenSSL::TestDigest < Test::Unit::TestCase + def setup + @d1 = OpenSSL::Digest.new("MD5") + @d2 = OpenSSL::Digest::MD5.new + @md = Digest::MD5.new + @data = "DATA" + end + + def teardown + @d1 = @d2 = @md = nil + end + + def test_digest + assert_equal(@md.digest, @d1.digest) + assert_equal(@md.hexdigest, @d1.hexdigest) + @d1 << @data + @d2 << @data + @md << @data + assert_equal(@md.digest, @d1.digest) + assert_equal(@md.hexdigest, @d1.hexdigest) + assert_equal(@d1.digest, @d2.digest) + assert_equal(@d1.hexdigest, @d2.hexdigest) + assert_equal(@md.digest, OpenSSL::Digest::MD5.digest(@data)) + assert_equal(@md.hexdigest, OpenSSL::Digest::MD5.hexdigest(@data)) + end + + def test_eql + assert(@d1 == @d2, "==") + d = @d1.clone + assert(d == @d1, "clone") + end + + def test_info + assert_equal("MD5", @d1.name, "name") + assert_equal("MD5", @d2.name, "name") + assert_equal(16, @d1.size, "size") + end + + def test_dup + @d1.update(@data) + assert_equal(@d1.name, @d1.dup.name, "dup") + assert_equal(@d1.name, @d1.clone.name, "clone") + assert_equal(@d1.digest, @d1.clone.digest, "clone .digest") + end + + def test_reset + @d1.update(@data) + dig1 = @d1.digest + @d1.reset + @d1.update(@data) + dig2 = @d1.digest + assert_equal(dig1, dig2, "reset") + end + + def test_digest_constants + algs = %w(DSS1 MD4 MD5 RIPEMD160 SHA SHA1) + if OpenSSL::OPENSSL_VERSION_NUMBER > 0x00908000 + algs += %w(SHA224 SHA256 SHA384 SHA512) + end + algs.each do |alg| + assert_not_nil(OpenSSL::Digest.new(alg)) + klass = OpenSSL::Digest.const_get(alg) + assert_not_nil(klass.new) + end + end + + def test_digest_by_oid_and_name + check_digest(OpenSSL::ASN1::ObjectId.new("MD5")) + check_digest(OpenSSL::ASN1::ObjectId.new("SHA1")) + end + + if OpenSSL::OPENSSL_VERSION_NUMBER > 0x00908000 + def encode16(str) + str.unpack("H*").first + end + + def test_098_features + sha224_a = "abd37534c7d9a2efb9465de931cd7055ffdb8879563ae98078d6d6d5" + sha256_a = "ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb" + sha384_a = "54a59b9f22b0b80880d8427e548b7c23abd873486e1f035dce9cd697e85175033caa88e6d57bc35efae0b5afd3145f31" + sha512_a = "1f40fc92da241694750979ee6cf582f2d5d7d28e18335de05abc54d0560e0f5302860c652bf08d560252aa5e74210546f369fbbbce8c12cfc7957b2652fe9a75" + + assert_equal(sha224_a, OpenSSL::Digest::SHA224.hexdigest("a")) + assert_equal(sha256_a, OpenSSL::Digest::SHA256.hexdigest("a")) + assert_equal(sha384_a, OpenSSL::Digest::SHA384.hexdigest("a")) + assert_equal(sha512_a, OpenSSL::Digest::SHA512.hexdigest("a")) + + assert_equal(sha224_a, encode16(OpenSSL::Digest::SHA224.digest("a"))) + assert_equal(sha256_a, encode16(OpenSSL::Digest::SHA256.digest("a"))) + assert_equal(sha384_a, encode16(OpenSSL::Digest::SHA384.digest("a"))) + assert_equal(sha512_a, encode16(OpenSSL::Digest::SHA512.digest("a"))) + end + + def test_digest_by_oid_and_name_sha2 + check_digest(OpenSSL::ASN1::ObjectId.new("SHA224")) + check_digest(OpenSSL::ASN1::ObjectId.new("SHA256")) + check_digest(OpenSSL::ASN1::ObjectId.new("SHA384")) + check_digest(OpenSSL::ASN1::ObjectId.new("SHA512")) + end + end + + def test_openssl_digest + assert_equal OpenSSL::Digest::MD5, OpenSSL::Digest("MD5") + + assert_raises NameError do + OpenSSL::Digest("no such digest") + end + end + + private + + def check_digest(oid) + d = OpenSSL::Digest.new(oid.sn) + assert_not_nil(d) + d = OpenSSL::Digest.new(oid.ln) + assert_not_nil(d) + d = OpenSSL::Digest.new(oid.oid) + assert_not_nil(d) + end +end + +end diff --git a/test/test_engine.rb b/test/test_engine.rb new file mode 100644 index 00000000..46a2948c --- /dev/null +++ b/test/test_engine.rb @@ -0,0 +1,75 @@ +require_relative 'utils' + +class OpenSSL::TestEngine < Test::Unit::TestCase + + def teardown + OpenSSL::Engine.cleanup # [ruby-core:40669] + assert_equal(0, OpenSSL::Engine.engines.size) + end + + def test_engines_free # [ruby-dev:44173] + OpenSSL::Engine.load("openssl") + OpenSSL::Engine.engines + OpenSSL::Engine.engines + end + + def test_openssl_engine_builtin + engine = OpenSSL::Engine.load("openssl") + assert_equal(true, engine) + assert_equal(1, OpenSSL::Engine.engines.size) + end + + def test_openssl_engine_by_id_string + engine = get_engine + assert_not_nil(engine) + assert_equal(1, OpenSSL::Engine.engines.size) + end + + def test_openssl_engine_id_name_inspect + engine = get_engine + assert_equal("openssl", engine.id) + assert_not_nil(engine.name) + assert_not_nil(engine.inspect) + end + + def test_openssl_engine_digest_sha1 + engine = get_engine + digest = engine.digest("SHA1") + assert_not_nil(digest) + data = "test" + assert_equal(OpenSSL::Digest::SHA1.digest(data), digest.digest(data)) + end + + def test_openssl_engine_cipher_rc4 + engine = get_engine + algo = "RC4" #AES is not supported by openssl Engine (<=1.0.0e) + data = "a" * 1000 + key = OpenSSL::Random.random_bytes(16) + # suppress message from openssl Engine's RC4 cipher [ruby-core:41026] + err_back = $stderr.dup + $stderr.reopen(IO::NULL) + encrypted = crypt_data(data, key, :encrypt) { engine.cipher(algo) } + decrypted = crypt_data(encrypted, key, :decrypt) { OpenSSL::Cipher.new(algo) } + assert_equal(data, decrypted) + ensure + if err_back + $stderr.reopen(err_back) + err_back.close + end + end + + private + + def get_engine + OpenSSL::Engine.by_id("openssl") + end + + def crypt_data(data, key, mode) + cipher = yield + cipher.send mode + cipher.key = key + cipher.update(data) + cipher.final + end + +end if defined?(OpenSSL) + diff --git a/test/test_fips.rb b/test/test_fips.rb new file mode 100644 index 00000000..882647f7 --- /dev/null +++ b/test/test_fips.rb @@ -0,0 +1,14 @@ +require_relative 'utils' + +if defined?(OpenSSL) + +class OpenSSL::TestFIPS < Test::Unit::TestCase + + def test_fips_mode_is_reentrant + OpenSSL.fips_mode = false + OpenSSL.fips_mode = false + end + +end + +end diff --git a/test/test_hmac.rb b/test/test_hmac.rb new file mode 100644 index 00000000..f1e45365 --- /dev/null +++ b/test/test_hmac.rb @@ -0,0 +1,41 @@ +# coding: UTF-8 + +require_relative 'utils' + +class OpenSSL::TestHMAC < Test::Unit::TestCase + def setup + @digest = OpenSSL::Digest::MD5 + @key = "KEY" + @data = "DATA" + @h1 = OpenSSL::HMAC.new(@key, @digest.new) + @h2 = OpenSSL::HMAC.new(@key, "MD5") + end + + def teardown + end + + def test_hmac + @h1.update(@data) + @h2.update(@data) + assert_equal(@h1.digest, @h2.digest) + + assert_equal(OpenSSL::HMAC.digest(@digest.new, @key, @data), @h1.digest, "digest") + assert_equal(OpenSSL::HMAC.hexdigest(@digest.new, @key, @data), @h1.hexdigest, "hexdigest") + + assert_equal(OpenSSL::HMAC.digest("MD5", @key, @data), @h2.digest, "digest") + assert_equal(OpenSSL::HMAC.hexdigest("MD5", @key, @data), @h2.hexdigest, "hexdigest") + end + + def test_dup + @h1.update(@data) + h = @h1.dup + assert_equal(@h1.digest, h.digest, "dup digest") + end + + def test_binary_update + data = "LücÃllé: Bût... yøü sáîd hé wÃ¥s âlrîght.\nDr. FÃshmÃ¥n: Yés. Hé's løst hîs léft hÃ¥nd, sø hé's gøîng tø bé Ã¥ll rîght" + hmac = OpenSSL::HMAC.new("qShkcwN92rsM9nHfdnP4ugcVU2iI7iM/trovs01ZWok", "SHA256") + result = hmac.update(data).hexdigest + assert_equal "a13984b929a07912e4e21c5720876a8e150d6f67f854437206e7f86547248396", result + end +end if defined?(OpenSSL) diff --git a/test/test_ns_spki.rb b/test/test_ns_spki.rb new file mode 100644 index 00000000..7cddefad --- /dev/null +++ b/test/test_ns_spki.rb @@ -0,0 +1,51 @@ +require_relative 'utils' + +if defined?(OpenSSL) + +class OpenSSL::TestNSSPI < Test::Unit::TestCase + def setup + # This request data is adopt from the specification of + # "Netscape Extensions for User Key Generation". + # -- http://wp.netscape.com/eng/security/comm4-keygen.html + @b64 = "MIHFMHEwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAnX0TILJrOMUue+PtwBRE6XfV" + @b64 << "WtKQbsshxk5ZhcUwcwyvcnIq9b82QhJdoACdD34rqfCAIND46fXKQUnb0mvKzQID" + @b64 << "AQABFhFNb3ppbGxhSXNNeUZyaWVuZDANBgkqhkiG9w0BAQQFAANBAAKv2Eex2n/S" + @b64 << "r/7iJNroWlSzSMtTiQTEB+ADWHGj9u1xrUrOilq/o2cuQxIfZcNZkYAkWP4DubqW" + @b64 << "i0//rgBvmco=" + end + + def test_build_data + key1 = OpenSSL::TestUtils::TEST_KEY_RSA1024 + key2 = OpenSSL::TestUtils::TEST_KEY_RSA2048 + spki = OpenSSL::Netscape::SPKI.new + spki.challenge = "RandomString" + spki.public_key = key1.public_key + spki.sign(key1, OpenSSL::Digest::SHA1.new) + assert(spki.verify(spki.public_key)) + assert(spki.verify(key1.public_key)) + assert(!spki.verify(key2.public_key)) + + der = spki.to_der + spki = OpenSSL::Netscape::SPKI.new(der) + assert_equal("RandomString", spki.challenge) + assert_equal(key1.public_key.to_der, spki.public_key.to_der) + assert(spki.verify(spki.public_key)) + assert_not_nil(spki.to_text) + end + + def test_decode_data + spki = OpenSSL::Netscape::SPKI.new(@b64) + assert_equal(@b64, spki.to_pem) + assert_equal(@b64.unpack("m").first, spki.to_der) + assert_equal("MozillaIsMyFriend", spki.challenge) + assert_equal(OpenSSL::PKey::RSA, spki.public_key.class) + + spki = OpenSSL::Netscape::SPKI.new(@b64.unpack("m").first) + assert_equal(@b64, spki.to_pem) + assert_equal(@b64.unpack("m").first, spki.to_der) + assert_equal("MozillaIsMyFriend", spki.challenge) + assert_equal(OpenSSL::PKey::RSA, spki.public_key.class) + end +end + +end diff --git a/test/test_ocsp.rb b/test/test_ocsp.rb new file mode 100644 index 00000000..b42b57d4 --- /dev/null +++ b/test/test_ocsp.rb @@ -0,0 +1,47 @@ +require_relative "utils" + +if defined?(OpenSSL) + +class OpenSSL::TestOCSP < Test::Unit::TestCase + def setup + ca_subj = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=TestCA") + ca_key = OpenSSL::TestUtils::TEST_KEY_RSA1024 + ca_serial = 0xabcabcabcabc + + subj = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=TestCert") + @key = OpenSSL::TestUtils::TEST_KEY_RSA1024 + serial = 0xabcabcabcabd + + now = Time.at(Time.now.to_i) # suppress usec + dgst = OpenSSL::Digest::SHA1.new + + @ca_cert = OpenSSL::TestUtils.issue_cert( + ca_subj, ca_key, ca_serial, now, now+3600, [], nil, nil, dgst) + @cert = OpenSSL::TestUtils.issue_cert( + subj, @key, serial, now, now+3600, [], @ca_cert, nil, dgst) + end + + def test_new_certificate_id + cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert) + assert_kind_of OpenSSL::OCSP::CertificateId, cid + assert_equal @cert.serial, cid.serial + end + + def test_new_certificate_id_with_digest + cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert, OpenSSL::Digest::SHA256.new) + assert_kind_of OpenSSL::OCSP::CertificateId, cid + assert_equal @cert.serial, cid.serial + end if defined?(OpenSSL::Digest::SHA256) + + def test_new_ocsp_request + request = OpenSSL::OCSP::Request.new + cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert, OpenSSL::Digest::SHA1.new) + request.add_certid(cid) + request.sign(@cert, @key, [@cert]) + assert_kind_of OpenSSL::OCSP::Request, request + # in current implementation not same instance of certificate id, but should contain same data + assert_equal cid.serial, request.certid.first.serial + end +end + +end diff --git a/test/test_pair.rb b/test/test_pair.rb new file mode 100644 index 00000000..b9206dae --- /dev/null +++ b/test/test_pair.rb @@ -0,0 +1,372 @@ +require_relative 'utils' + +if defined?(OpenSSL) + +require 'socket' +require_relative '../ruby/ut_eof' + +module OpenSSL::SSLPairM + def server + host = "127.0.0.1" + port = 0 + ctx = OpenSSL::SSL::SSLContext.new() + ctx.ciphers = "ADH" + ctx.tmp_dh_callback = proc { OpenSSL::TestUtils::TEST_KEY_DH1024 } + tcps = create_tcp_server(host, port) + ssls = OpenSSL::SSL::SSLServer.new(tcps, ctx) + return ssls + end + + def client(port) + host = "127.0.0.1" + ctx = OpenSSL::SSL::SSLContext.new() + ctx.ciphers = "ADH" + s = create_tcp_client(host, port) + ssl = OpenSSL::SSL::SSLSocket.new(s, ctx) + ssl.connect + ssl.sync_close = true + ssl + end + + def ssl_pair + ssls = server + th = Thread.new { + ns = ssls.accept + ssls.close + ns + } + port = ssls.to_io.local_address.ip_port + c = client(port) + s = th.value + if block_given? + begin + yield c, s + ensure + c.close unless c.closed? + s.close unless s.closed? + end + else + return c, s + end + ensure + if th && th.alive? + th.kill + th.join + end + end +end + +module OpenSSL::SSLPair + include OpenSSL::SSLPairM + + def create_tcp_server(host, port) + TCPServer.new(host, port) + end + + def create_tcp_client(host, port) + TCPSocket.new(host, port) + end +end + +module OpenSSL::SSLPairLowlevelSocket + include OpenSSL::SSLPairM + + def create_tcp_server(host, port) + Addrinfo.tcp(host, port).listen + end + + def create_tcp_client(host, port) + Addrinfo.tcp(host, port).connect + end +end + +module OpenSSL::TestEOF1M + def open_file(content) + s1, s2 = ssl_pair + th = Thread.new { s2 << content; s2.close } + yield s1 + ensure + th.join + s1.close + end +end + +module OpenSSL::TestEOF2M + def open_file(content) + s1, s2 = ssl_pair + th = Thread.new { s1 << content; s1.close } + yield s2 + ensure + th.join + s2.close + end +end + +module OpenSSL::TestPairM + def test_getc + ssl_pair {|s1, s2| + s1 << "a" + assert_equal(?a, s2.getc) + } + end + + def test_readpartial + ssl_pair {|s1, s2| + s2.write "a\nbcd" + assert_equal("a\n", s1.gets) + result = "" + result << s1.readpartial(10) until result.length == 3 + assert_equal("bcd", result) + s2.write "efg" + result = "" + result << s1.readpartial(10) until result.length == 3 + assert_equal("efg", result) + s2.close + assert_raise(EOFError) { s1.readpartial(10) } + assert_raise(EOFError) { s1.readpartial(10) } + assert_equal("", s1.readpartial(0)) + } + end + + def test_readall + ssl_pair {|s1, s2| + s2.close + assert_equal("", s1.read) + } + end + + def test_readline + ssl_pair {|s1, s2| + s2.close + assert_raise(EOFError) { s1.readline } + } + end + + def test_puts_meta + ssl_pair {|s1, s2| + begin + old = $/ + $/ = '*' + s1.puts 'a' + ensure + $/ = old + end + s1.close + assert_equal("a\n", s2.read) + } + end + + def test_puts_empty + ssl_pair {|s1, s2| + s1.puts + s1.close + assert_equal("\n", s2.read) + } + end + + def test_read_nonblock + ssl_pair {|s1, s2| + err = nil + assert_raise(OpenSSL::SSL::SSLErrorWaitReadable) { + begin + s2.read_nonblock(10) + ensure + err = $! + end + } + assert_kind_of(IO::WaitReadable, err) + s1.write "abc\ndef\n" + IO.select([s2]) + assert_equal("ab", s2.read_nonblock(2)) + assert_equal("c\n", s2.gets) + ret = nil + assert_nothing_raised("[ruby-core:20298]") { ret = s2.read_nonblock(10) } + assert_equal("def\n", ret) + s1.close + sleep 0.1 + assert_raise(EOFError) { s2.read_nonblock(10) } + } + end + + def test_read_nonblock_no_exception + ssl_pair {|s1, s2| + assert_equal :wait_readable, s2.read_nonblock(10, exception: false) + s1.write "abc\ndef\n" + IO.select([s2]) + assert_equal("ab", s2.read_nonblock(2, exception: false)) + assert_equal("c\n", s2.gets) + ret = nil + assert_nothing_raised("[ruby-core:20298]") { ret = s2.read_nonblock(10, exception: false) } + assert_equal("def\n", ret) + s1.close + sleep 0.1 + assert_equal(nil, s2.read_nonblock(10, exception: false)) + } + end + + def write_nonblock(socket, meth, str) + ret = socket.send(meth, str) + ret.is_a?(Symbol) ? 0 : ret + end + + def write_nonblock_no_ex(socket, str) + ret = socket.write_nonblock str, exception: false + ret.is_a?(Symbol) ? 0 : ret + end + + def test_write_nonblock + ssl_pair {|s1, s2| + n = 0 + begin + n += write_nonblock s1, :write_nonblock, "a" * 100000 + n += write_nonblock s1, :write_nonblock, "b" * 100000 + n += write_nonblock s1, :write_nonblock, "c" * 100000 + n += write_nonblock s1, :write_nonblock, "d" * 100000 + n += write_nonblock s1, :write_nonblock, "e" * 100000 + n += write_nonblock s1, :write_nonblock, "f" * 100000 + rescue IO::WaitWritable + end + s1.close + assert_equal(n, s2.read.length) + } + end + + def test_write_nonblock_no_exceptions + ssl_pair {|s1, s2| + n = 0 + begin + n += write_nonblock_no_ex s1, "a" * 100000 + n += write_nonblock_no_ex s1, "b" * 100000 + n += write_nonblock_no_ex s1, "c" * 100000 + n += write_nonblock_no_ex s1, "d" * 100000 + n += write_nonblock_no_ex s1, "e" * 100000 + n += write_nonblock_no_ex s1, "f" * 100000 + rescue OpenSSL::SSL::SSLError => e + # on some platforms (maybe depend on OpenSSL version), writing to + # SSLSocket after SSL_ERROR_WANT_WRITE causes this error. + raise e if n == 0 + end + s1.close + assert_equal(n, s2.read.length) + } + end + + def test_write_nonblock_with_buffered_data + ssl_pair {|s1, s2| + s1.write "foo" + s1.write_nonblock("bar") + s1.write "baz" + s1.close + assert_equal("foobarbaz", s2.read) + } + end + + def test_write_nonblock_with_buffered_data_no_exceptions + ssl_pair {|s1, s2| + s1.write "foo" + s1.write_nonblock("bar", exception: false) + s1.write "baz" + s1.close + assert_equal("foobarbaz", s2.read) + } + end + + def tcp_pair + host = "127.0.0.1" + serv = TCPServer.new(host, 0) + port = serv.connect_address.ip_port + sock1 = TCPSocket.new(host, port) + sock2 = serv.accept + serv.close + [sock1, sock2] + ensure + serv.close if serv && !serv.closed? + end + + def test_connect_accept_nonblock + ctx = OpenSSL::SSL::SSLContext.new() + ctx.ciphers = "ADH" + ctx.tmp_dh_callback = proc { OpenSSL::TestUtils::TEST_KEY_DH1024 } + + sock1, sock2 = tcp_pair + + th = Thread.new { + s2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx) + s2.sync_close = true + begin + sleep 0.2 + s2.accept_nonblock + rescue IO::WaitReadable + IO.select([s2]) + retry + rescue IO::WaitWritable + IO.select(nil, [s2]) + retry + end + s2 + } + + sleep 0.1 + ctx = OpenSSL::SSL::SSLContext.new() + ctx.ciphers = "ADH" + s1 = OpenSSL::SSL::SSLSocket.new(sock1, ctx) + begin + sleep 0.2 + s1.connect_nonblock + rescue IO::WaitReadable + IO.select([s1]) + retry + rescue IO::WaitWritable + IO.select(nil, [s1]) + retry + end + s1.sync_close = true + + s2 = th.value + + s1.print "a\ndef" + assert_equal("a\n", s2.gets) + ensure + th.join + s1.close if s1 && !s1.closed? + s2.close if s2 && !s2.closed? + sock1.close if sock1 && !sock1.closed? + sock2.close if sock2 && !sock2.closed? + end +end + +class OpenSSL::TestEOF1 < Test::Unit::TestCase + include TestEOF + include OpenSSL::SSLPair + include OpenSSL::TestEOF1M +end + +class OpenSSL::TestEOF1LowlevelSocket < Test::Unit::TestCase + include TestEOF + include OpenSSL::SSLPairLowlevelSocket + include OpenSSL::TestEOF1M +end + +class OpenSSL::TestEOF2 < Test::Unit::TestCase + include TestEOF + include OpenSSL::SSLPair + include OpenSSL::TestEOF2M +end + +class OpenSSL::TestEOF2LowlevelSocket < Test::Unit::TestCase + include TestEOF + include OpenSSL::SSLPairLowlevelSocket + include OpenSSL::TestEOF2M +end + +class OpenSSL::TestPair < Test::Unit::TestCase + include OpenSSL::SSLPair + include OpenSSL::TestPairM +end + +class OpenSSL::TestPairLowlevelSocket < Test::Unit::TestCase + include OpenSSL::SSLPairLowlevelSocket + include OpenSSL::TestPairM +end + +end diff --git a/test/test_partial_record_read.rb b/test/test_partial_record_read.rb new file mode 100644 index 00000000..f3d83c69 --- /dev/null +++ b/test/test_partial_record_read.rb @@ -0,0 +1,36 @@ +require_relative "utils" + +if defined?(OpenSSL) + + class OpenSSL::TestPartialRecordRead < OpenSSL::SSLTestCase + def test_partial_tls_record_read_nonblock + port = 12345 + + start_server(port, OpenSSL::SSL::VERIFY_NONE, true, :server_proc => + Proc.new do |server_ctx, server_ssl| + begin + server_ssl.io.write("\x01") # the beginning of a TLS record + sleep 6 # do not finish prematurely before the read by the client is attempted + ensure + server_ssl.close + end + end + ) do |server, port| + sock = TCPSocket.new("127.0.0.1", port) + ssl = OpenSSL::SSL::SSLSocket.new(sock) + ssl.sync_close = true + begin + ssl.connect + sleep 3 # wait is required for the (incomplete) TLS record to arrive at the client socket + + # Should raise a IO::WaitReadable since a full TLS record is not available for reading. + assert_raise(IO::WaitReadable) { ssl.read_nonblock(1) } + ensure + ssl.close + end + end + end + + end + +end diff --git a/test/test_pkcs12.rb b/test/test_pkcs12.rb new file mode 100644 index 00000000..25ff6063 --- /dev/null +++ b/test/test_pkcs12.rb @@ -0,0 +1,209 @@ +require_relative "utils" + +if defined?(OpenSSL) + +module OpenSSL + class TestPKCS12 < Test::Unit::TestCase + include OpenSSL::TestUtils + + def setup + ca = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=CA") + + now = Time.now + ca_exts = [ + ["basicConstraints","CA:TRUE",true], + ["keyUsage","keyCertSign, cRLSign",true], + ["subjectKeyIdentifier","hash",false], + ["authorityKeyIdentifier","keyid:always",false], + ] + + @cacert = issue_cert(ca, TEST_KEY_RSA2048, 1, now, now+3600, ca_exts, + nil, nil, OpenSSL::Digest::SHA1.new) + + inter_ca = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=Intermediate CA") + inter_ca_key = OpenSSL::PKey.read <<-_EOS_ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQDp7hIG0SFMG/VWv1dBUWziAPrNmkMXJgTCAoB7jffzRtyyN04K +oq/89HAszTMStZoMigQURfokzKsjpUp8OYCAEsBtt9d5zPndWMz/gHN73GrXk3LT +ZsxEn7Xv5Da+Y9F/Hx2QZUHarV5cdZixq2NbzWGwrToogOQMh2pxN3Z/0wIDAQAB +AoGBAJysUyx3olpsGzv3OMRJeahASbmsSKTXVLZvoIefxOINosBFpCIhZccAG6UV +5c/xCvS89xBw8aD15uUfziw3AuT8QPEtHCgfSjeT7aWzBfYswEgOW4XPuWr7EeI9 +iNHGD6z+hCN/IQr7FiEBgTp6A+i/hffcSdR83fHWKyb4M7TRAkEA+y4BNd668HmC +G5MPRx25n6LixuBxrNp1umfjEI6UZgEFVpYOg4agNuimN6NqM253kcTR94QNTUs5 +Kj3EhG1YWwJBAO5rUjiOyCNVX2WUQrOMYK/c1lU7fvrkdygXkvIGkhsPoNRzLPeA +HGJszKtrKD8bNihWpWNIyqKRHfKVD7yXT+kCQGCAhVCIGTRoypcDghwljHqLnysf +ci0h5ZdPcIqc7ODfxYhFsJ/Rql5ONgYsT5Ig/+lOQAkjf+TRYM4c2xKx2/8CQBvG +jv6dy70qDgIUgqzONtlmHeYyFzn9cdBO5sShdVYHvRHjFSMEXsosqK9zvW2UqvuK +FJx7d3f29gkzynCLJDkCQGQZlEZJC4vWmWJGRKJ24P6MyQn3VsPfErSKOg4lvyM3 +Li8JsX5yIiuVYaBg/6ha3tOg4TCa5K/3r3tVliRZ2Es= +-----END RSA PRIVATE KEY----- + _EOS_ + + @inter_cacert = issue_cert(inter_ca, inter_ca_key, 2, now, now+3600, ca_exts, + @cacert, TEST_KEY_RSA2048, OpenSSL::Digest::SHA1.new) + + exts = [ + ["keyUsage","digitalSignature",true], + ["subjectKeyIdentifier","hash",false], + ] + ee = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=Ruby PKCS12 Test Certificate") + @mycert = issue_cert(ee, TEST_KEY_RSA1024, 3, now, now+3600, exts, + @inter_cacert, inter_ca_key, OpenSSL::Digest::SHA1.new) + end + + def test_create + pkcs12 = OpenSSL::PKCS12.create( + "omg", + "hello", + TEST_KEY_RSA1024, + @mycert + ) + assert_equal @mycert, pkcs12.certificate + assert_equal TEST_KEY_RSA1024, pkcs12.key + assert_nil pkcs12.ca_certs + end + + def test_create_no_pass + pkcs12 = OpenSSL::PKCS12.create( + nil, + "hello", + TEST_KEY_RSA1024, + @mycert + ) + assert_equal @mycert, pkcs12.certificate + assert_equal TEST_KEY_RSA1024, pkcs12.key + assert_nil pkcs12.ca_certs + + decoded = OpenSSL::PKCS12.new(pkcs12.to_der) + assert_cert @mycert, decoded.certificate + end + + def test_create_with_chain + chain = [@inter_cacert, @cacert] + + pkcs12 = OpenSSL::PKCS12.create( + "omg", + "hello", + TEST_KEY_RSA1024, + @mycert, + chain + ) + assert_equal chain, pkcs12.ca_certs + end + + def test_create_with_chain_decode + chain = [@cacert, @inter_cacert] + + passwd = "omg" + + pkcs12 = OpenSSL::PKCS12.create( + passwd, + "hello", + TEST_KEY_RSA1024, + @mycert, + chain + ) + + decoded = OpenSSL::PKCS12.new(pkcs12.to_der, passwd) + assert_equal chain.size, decoded.ca_certs.size + assert_include_cert @cacert, decoded.ca_certs + assert_include_cert @inter_cacert, decoded.ca_certs + assert_cert @mycert, decoded.certificate + assert_equal TEST_KEY_RSA1024.to_der, decoded.key.to_der + end + + def test_create_with_bad_nid + assert_raises(ArgumentError) do + OpenSSL::PKCS12.create( + "omg", + "hello", + TEST_KEY_RSA1024, + @mycert, + [], + "foo" + ) + end + end + + def test_create_with_itr + OpenSSL::PKCS12.create( + "omg", + "hello", + TEST_KEY_RSA1024, + @mycert, + [], + nil, + nil, + 2048 + ) + + assert_raises(TypeError) do + OpenSSL::PKCS12.create( + "omg", + "hello", + TEST_KEY_RSA1024, + @mycert, + [], + nil, + nil, + "omg" + ) + end + end + + def test_create_with_mac_itr + OpenSSL::PKCS12.create( + "omg", + "hello", + TEST_KEY_RSA1024, + @mycert, + [], + nil, + nil, + nil, + 2048 + ) + + assert_raises(TypeError) do + OpenSSL::PKCS12.create( + "omg", + "hello", + TEST_KEY_RSA1024, + @mycert, + [], + nil, + nil, + nil, + "omg" + ) + end + end + + private + def assert_cert expected, actual + [ + :subject, + :issuer, + :serial, + :not_before, + :not_after, + ].each do |attribute| + assert_equal expected.send(attribute), actual.send(attribute) + end + assert_equal expected.to_der, actual.to_der + end + + def assert_include_cert cert, ary + der = cert.to_der + ary.each do |candidate| + if candidate.to_der == der + return true + end + end + false + end + + end +end + +end diff --git a/test/test_pkcs5.rb b/test/test_pkcs5.rb new file mode 100644 index 00000000..30fa3e5b --- /dev/null +++ b/test/test_pkcs5.rb @@ -0,0 +1,97 @@ +require_relative 'utils' + +class OpenSSL::TestPKCS5 < Test::Unit::TestCase + + def test_pbkdf2_hmac_sha1_rfc6070_c_1_len_20 + p ="password" + s = "salt" + c = 1 + dk_len = 20 + raw = %w{ 0c 60 c8 0f 96 1f 0e 71 + f3 a9 b5 24 af 60 12 06 + 2f e0 37 a6 } + expected = [raw.join('')].pack('H*') + value = OpenSSL::PKCS5.pbkdf2_hmac_sha1(p, s, c, dk_len) + assert_equal(expected, value) + end + + def test_pbkdf2_hmac_sha1_rfc6070_c_2_len_20 + p ="password" + s = "salt" + c = 2 + dk_len = 20 + raw = %w{ ea 6c 01 4d c7 2d 6f 8c + cd 1e d9 2a ce 1d 41 f0 + d8 de 89 57 } + expected = [raw.join('')].pack('H*') + value = OpenSSL::PKCS5.pbkdf2_hmac_sha1(p, s, c, dk_len) + assert_equal(expected, value) + end + + def test_pbkdf2_hmac_sha1_rfc6070_c_4096_len_20 + p ="password" + s = "salt" + c = 4096 + dk_len = 20 + raw = %w{ 4b 00 79 01 b7 65 48 9a + be ad 49 d9 26 f7 21 d0 + 65 a4 29 c1 } + expected = [raw.join('')].pack('H*') + value = OpenSSL::PKCS5.pbkdf2_hmac_sha1(p, s, c, dk_len) + assert_equal(expected, value) + end + +# takes too long! +# def test_pbkdf2_hmac_sha1_rfc6070_c_16777216_len_20 +# p ="password" +# s = "salt" +# c = 16777216 +# dk_len = 20 +# raw = %w{ ee fe 3d 61 cd 4d a4 e4 +# e9 94 5b 3d 6b a2 15 8c +# 26 34 e9 84 } +# expected = [raw.join('')].pack('H*') +# value = OpenSSL::PKCS5.pbkdf2_hmac_sha1(p, s, c, dk_len) +# assert_equal(expected, value) +# end + + def test_pbkdf2_hmac_sha1_rfc6070_c_4096_len_25 + p ="passwordPASSWORDpassword" + s = "saltSALTsaltSALTsaltSALTsaltSALTsalt" + c = 4096 + dk_len = 25 + + raw = %w{ 3d 2e ec 4f e4 1c 84 9b + 80 c8 d8 36 62 c0 e4 4a + 8b 29 1a 96 4c f2 f0 70 + 38 } + expected = [raw.join('')].pack('H*') + value = OpenSSL::PKCS5.pbkdf2_hmac_sha1(p, s, c, dk_len) + assert_equal(expected, value) + end + + def test_pbkdf2_hmac_sha1_rfc6070_c_4096_len_16 + p ="pass\0word" + s = "sa\0lt" + c = 4096 + dk_len = 16 + raw = %w{ 56 fa 6a a7 55 48 09 9d + cc 37 d7 f0 34 25 e0 c3 } + expected = [raw.join('')].pack('H*') + value = OpenSSL::PKCS5.pbkdf2_hmac_sha1(p, s, c, dk_len) + assert_equal(expected, value) + end + + def test_pbkdf2_hmac_sha256_c_20000_len_32 + #unfortunately no official test vectors available yet for SHA-2 + p ="password" + s = OpenSSL::Random.random_bytes(16) + c = 20000 + dk_len = 32 + digest = OpenSSL::Digest::SHA256.new + value1 = OpenSSL::PKCS5.pbkdf2_hmac(p, s, c, dk_len, digest) + value2 = OpenSSL::PKCS5.pbkdf2_hmac(p, s, c, dk_len, digest) + assert_equal(value1, value2) + end if OpenSSL::PKCS5.respond_to?(:pbkdf2_hmac) + +end if defined?(OpenSSL) diff --git a/test/test_pkcs7.rb b/test/test_pkcs7.rb new file mode 100644 index 00000000..a1ff0485 --- /dev/null +++ b/test/test_pkcs7.rb @@ -0,0 +1,297 @@ +require_relative 'utils' + +if defined?(OpenSSL) + +class OpenSSL::TestPKCS7 < Test::Unit::TestCase + def setup + @rsa1024 = OpenSSL::TestUtils::TEST_KEY_RSA1024 + @rsa2048 = OpenSSL::TestUtils::TEST_KEY_RSA2048 + ca = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=CA") + ee1 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=EE1") + ee2 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=EE2") + + now = Time.now + ca_exts = [ + ["basicConstraints","CA:TRUE",true], + ["keyUsage","keyCertSign, cRLSign",true], + ["subjectKeyIdentifier","hash",false], + ["authorityKeyIdentifier","keyid:always",false], + ] + @ca_cert = issue_cert(ca, @rsa2048, 1, now, now+3600, ca_exts, + nil, nil, OpenSSL::Digest::SHA1.new) + ee_exts = [ + ["keyUsage","Non Repudiation, Digital Signature, Key Encipherment",true], + ["authorityKeyIdentifier","keyid:always",false], + ["extendedKeyUsage","clientAuth, emailProtection, codeSigning",false], + ] + @ee1_cert = issue_cert(ee1, @rsa1024, 2, now, now+1800, ee_exts, + @ca_cert, @rsa2048, OpenSSL::Digest::SHA1.new) + @ee2_cert = issue_cert(ee2, @rsa1024, 3, now, now+1800, ee_exts, + @ca_cert, @rsa2048, OpenSSL::Digest::SHA1.new) + end + + def issue_cert(*args) + OpenSSL::TestUtils.issue_cert(*args) + end + + def test_signed + store = OpenSSL::X509::Store.new + store.add_cert(@ca_cert) + ca_certs = [@ca_cert] + + data = "aaaaa\r\nbbbbb\r\nccccc\r\n" + tmp = OpenSSL::PKCS7.sign(@ee1_cert, @rsa1024, data, ca_certs) + p7 = OpenSSL::PKCS7.new(tmp.to_der) + certs = p7.certificates + signers = p7.signers + assert(p7.verify([], store)) + assert_equal(data, p7.data) + assert_equal(2, certs.size) + assert_equal(@ee1_cert.subject.to_s, certs[0].subject.to_s) + assert_equal(@ca_cert.subject.to_s, certs[1].subject.to_s) + assert_equal(1, signers.size) + assert_equal(@ee1_cert.serial, signers[0].serial) + assert_equal(@ee1_cert.issuer.to_s, signers[0].issuer.to_s) + + # Normaly OpenSSL tries to translate the supplied content into canonical + # MIME format (e.g. a newline character is converted into CR+LF). + # If the content is a binary, PKCS7::BINARY flag should be used. + + data = "aaaaa\nbbbbb\nccccc\n" + flag = OpenSSL::PKCS7::BINARY + tmp = OpenSSL::PKCS7.sign(@ee1_cert, @rsa1024, data, ca_certs, flag) + p7 = OpenSSL::PKCS7.new(tmp.to_der) + certs = p7.certificates + signers = p7.signers + assert(p7.verify([], store)) + assert_equal(data, p7.data) + assert_equal(2, certs.size) + assert_equal(@ee1_cert.subject.to_s, certs[0].subject.to_s) + assert_equal(@ca_cert.subject.to_s, certs[1].subject.to_s) + assert_equal(1, signers.size) + assert_equal(@ee1_cert.serial, signers[0].serial) + assert_equal(@ee1_cert.issuer.to_s, signers[0].issuer.to_s) + + # A signed-data which have multiple signatures can be created + # through the following steps. + # 1. create two signed-data + # 2. copy signerInfo and certificate from one to another + + tmp1 = OpenSSL::PKCS7.sign(@ee1_cert, @rsa1024, data, [], flag) + tmp2 = OpenSSL::PKCS7.sign(@ee2_cert, @rsa1024, data, [], flag) + tmp1.add_signer(tmp2.signers[0]) + tmp1.add_certificate(@ee2_cert) + + p7 = OpenSSL::PKCS7.new(tmp1.to_der) + certs = p7.certificates + signers = p7.signers + assert(p7.verify([], store)) + assert_equal(data, p7.data) + assert_equal(2, certs.size) + assert_equal(2, signers.size) + assert_equal(@ee1_cert.serial, signers[0].serial) + assert_equal(@ee1_cert.issuer.to_s, signers[0].issuer.to_s) + assert_equal(@ee2_cert.serial, signers[1].serial) + assert_equal(@ee2_cert.issuer.to_s, signers[1].issuer.to_s) + end + + def test_detached_sign + store = OpenSSL::X509::Store.new + store.add_cert(@ca_cert) + ca_certs = [@ca_cert] + + data = "aaaaa\nbbbbb\nccccc\n" + flag = OpenSSL::PKCS7::BINARY|OpenSSL::PKCS7::DETACHED + tmp = OpenSSL::PKCS7.sign(@ee1_cert, @rsa1024, data, ca_certs, flag) + p7 = OpenSSL::PKCS7.new(tmp.to_der) + assert_nothing_raised do + OpenSSL::ASN1.decode(p7) + end + + certs = p7.certificates + signers = p7.signers + assert(!p7.verify([], store)) + assert(p7.verify([], store, data)) + assert_equal(data, p7.data) + assert_equal(2, certs.size) + assert_equal(@ee1_cert.subject.to_s, certs[0].subject.to_s) + assert_equal(@ca_cert.subject.to_s, certs[1].subject.to_s) + assert_equal(1, signers.size) + assert_equal(@ee1_cert.serial, signers[0].serial) + assert_equal(@ee1_cert.issuer.to_s, signers[0].issuer.to_s) + end + + def test_enveloped + if OpenSSL::OPENSSL_VERSION_NUMBER <= 0x0090704f + # PKCS7_encrypt() of OpenSSL-0.9.7d goes to SEGV. + # http://www.mail-archive.com/openssl-dev@openssl.org/msg17376.html + return + end + + certs = [@ee1_cert, @ee2_cert] + cipher = OpenSSL::Cipher::AES.new("128-CBC") + data = "aaaaa\nbbbbb\nccccc\n" + + tmp = OpenSSL::PKCS7.encrypt(certs, data, cipher, OpenSSL::PKCS7::BINARY) + p7 = OpenSSL::PKCS7.new(tmp.to_der) + recip = p7.recipients + assert_equal(:enveloped, p7.type) + assert_equal(2, recip.size) + + assert_equal(@ca_cert.subject.to_s, recip[0].issuer.to_s) + assert_equal(2, recip[0].serial) + assert_equal(data, p7.decrypt(@rsa1024, @ee1_cert)) + + assert_equal(@ca_cert.subject.to_s, recip[1].issuer.to_s) + assert_equal(3, recip[1].serial) + assert_equal(data, p7.decrypt(@rsa1024, @ee2_cert)) + end + + def test_graceful_parsing_failure #[ruby-core:43250] + contents = File.read(__FILE__) + assert_raise(ArgumentError) { OpenSSL::PKCS7.new(contents) } + end + + def test_set_type_signed + p7 = OpenSSL::PKCS7.new + p7.type = "signed" + assert_equal(:signed, p7.type) + end + + def test_set_type_data + p7 = OpenSSL::PKCS7.new + p7.type = "data" + assert_equal(:data, p7.type) + end + + def test_set_type_signed_and_enveloped + p7 = OpenSSL::PKCS7.new + p7.type = "signedAndEnveloped" + assert_equal(:signedAndEnveloped, p7.type) + end + + def test_set_type_enveloped + p7 = OpenSSL::PKCS7.new + p7.type = "enveloped" + assert_equal(:enveloped, p7.type) + end + + def test_set_type_encrypted + p7 = OpenSSL::PKCS7.new + p7.type = "encrypted" + assert_equal(:encrypted, p7.type) + end + + def test_degenerate_pkcs7 + ca_cert_pem = <<END +-----BEGIN CERTIFICATE----- +MIID4DCCAsigAwIBAgIJAL1oVI72wmQwMA0GCSqGSIb3DQEBBQUAMFMxCzAJBgNV +BAYTAkFVMQ4wDAYDVQQIEwVTdGF0ZTENMAsGA1UEBxMEQ2l0eTEQMA4GA1UEChMH +RXhhbXBsZTETMBEGA1UEAxMKRXhhbXBsZSBDQTAeFw0xMjEwMTgwOTE2NTBaFw0y +MjEwMTYwOTE2NTBaMFMxCzAJBgNVBAYTAkFVMQ4wDAYDVQQIEwVTdGF0ZTENMAsG +A1UEBxMEQ2l0eTEQMA4GA1UEChMHRXhhbXBsZTETMBEGA1UEAxMKRXhhbXBsZSBD +QTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMTSPNxOkd5NN19XO0fJ +tGVlWN4DWuvVL9WbWnXJXX9rU6X8sSOL9RrRA64eEZf2UBFjz9fMHZj/OGcxZpus +4YtzfSrMU6xfvsIHeqX+mT60ms2RfX4UXab50MQArBin3JVKHGnOi25uyAOylVFU +TuzzQJvKyB67vjuRPMlVAgVAZAP07ru9gW0ajt/ODxvUfvXxp5SFF68mVP2ipMBr +4fujUwQC6cVHmnuL6p87VFoo9uk87TSQVDOQGL8MK4moMFtEW9oUTU22CgnxnCsS +sCCELYhy9BdaTWQH26LzMfhnwSuIRHZyprW4WZtU0akrYXNiCj8o92rZmQWXJDbl +qNECAwEAAaOBtjCBszAdBgNVHQ4EFgQUNtVw4jvkZZbkdQbkYi2/F4QN79owgYMG +A1UdIwR8MHqAFDbVcOI75GWW5HUG5GItvxeEDe/aoVekVTBTMQswCQYDVQQGEwJB +VTEOMAwGA1UECBMFU3RhdGUxDTALBgNVBAcTBENpdHkxEDAOBgNVBAoTB0V4YW1w +bGUxEzARBgNVBAMTCkV4YW1wbGUgQ0GCCQC9aFSO9sJkMDAMBgNVHRMEBTADAQH/ +MA0GCSqGSIb3DQEBBQUAA4IBAQBvJIsY9bIqliZ3WD1KoN4cvAQeRAPsoLXQkkHg +P6Nrcw9rJ5JvoHfYbo5aNlwbnkbt/B2xlVEXUYpJoBZFXafgxG2gJleioIgnaDS4 +FPPwZf1C5ZrOgUBfxTGjHex4ghSAoNGOd35jQzin5NGKOvZclPjZ2vQ++LP3aA2l +9Fn2qASS46IzMGJlC75mlTOTQwDM16UunMAK26lNG9J6q02o4d/oU2a7x0fD80yF +64kNA1wDAwaVCYiUH541qKp+b4iDqer8nf8HqzYDFlpje18xYZMEd1hj8dVOharM +pISJ+D52hV/BGEYF8r5k3hpC5d76gSP2oCcaY0XvLBf97qik +-----END CERTIFICATE----- +END + p7 = OpenSSL::PKCS7.new + p7.type = "signed" + ca_cert = OpenSSL::X509::Certificate.new(ca_cert_pem) + p7.add_certificate ca_cert + p7.add_data "" + + assert_nothing_raised do + p7.to_pem + end + end + + def test_split_content + pki_message_pem = <<END +-----BEGIN PKCS7----- +MIIHSwYJKoZIhvcNAQcCoIIHPDCCBzgCAQExCzAJBgUrDgMCGgUAMIIDiAYJKoZI +hvcNAQcBoIIDeQSCA3UwgAYJKoZIhvcNAQcDoIAwgAIBADGCARAwggEMAgEAMHUw +cDEQMA4GA1UECgwHZXhhbXBsZTEXMBUGA1UEAwwOVEFSTUFDIFJPT1QgQ0ExIjAg +BgkqhkiG9w0BCQEWE3NvbWVvbmVAZXhhbXBsZS5vcmcxCzAJBgNVBAYTAlVTMRIw +EAYDVQQHDAlUb3duIEhhbGwCAWYwDQYJKoZIhvcNAQEBBQAEgYBspXXse8ZhG1FE +E3PVAulbvrdR52FWPkpeLvSjgEkYzTiUi0CC3poUL1Ku5mOlavWAJgoJpFICDbvc +N4ZNDCwOhnzoI9fMGmm1gvPQy15BdhhZRo9lP7Ga/Hg2APKT0/0yhPsmJ+w+u1e7 +OoJEVeEZ27x3+u745bGEcu8of5th6TCABgkqhkiG9w0BBwEwFAYIKoZIhvcNAwcE +CBNs2U5mMsd/oIAEggIQU6cur8QBz02/4eMpHdlU9IkyrRMiaMZ/ky9zecOAjnvY +d2jZqS7RhczpaNJaSli3GmDsKrF+XqE9J58s9ScGqUigzapusTsxIoRUPr7Ztb0a +pg8VWDipAsuw7GfEkgx868sV93uC4v6Isfjbhd+JRTFp/wR1kTi7YgSXhES+RLUW +gQbDIDgEQYxJ5U951AJtnSpjs9za2ZkTdd8RSEizJK0bQ1vqLoApwAVgZqluATqQ +AHSDCxhweVYw6+y90B9xOrqPC0eU7Wzryq2+Raq5ND2Wlf5/N11RQ3EQdKq/l5Te +ijp9PdWPlkUhWVoDlOFkysjk+BE+7AkzgYvz9UvBjmZsMsWqf+KsZ4S8/30ndLzu +iucsu6eOnFLLX8DKZxV6nYffZOPzZZL8hFBcE7PPgSdBEkazMrEBXq1j5mN7exbJ +NOA5uGWyJNBMOCe+1JbxG9UeoqvCCTHESxEeDu7xR3NnSOD47n7cXwHr81YzK2zQ +5oWpP3C8jzI7tUjLd1S0Z3Psd17oaCn+JOfUtuB0nc3wfPF/WPo0xZQodWxp2/Cl +EltR6qr1zf5C7GwmLzBZ6bHFAIT60/JzV0/56Pn8ztsRFtI4cwaBfTfvnwi8/sD9 +/LYOMY+/b6UDCUSR7RTN7XfrtAqDEzSdzdJkOWm1jvM8gkLmxpZdvxG3ZvDYnEQE +5Nq+un5nAny1wf3rWierBAjE5ntiAmgs5AAAAAAAAAAAAACgggHqMIIB5jCCAU+g +AwIBAgIBATANBgkqhkiG9w0BAQUFADAvMS0wKwYDVQQDEyQwQUM5RjAyNi1EQ0VB +LTRDMTItOTEyNy1DMEZEN0QyQThCNUEwHhcNMTIxMDE5MDk0NTQ3WhcNMTMxMDE5 +MDk0NTQ3WjAvMS0wKwYDVQQDEyQwQUM5RjAyNi1EQ0VBLTRDMTItOTEyNy1DMEZE +N0QyQThCNUEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALTsTNyGIsKvyw56 +WI3Gll/RmjsupkrdEtPbx7OjS9MEgyhOAf9+u6CV0LJGHpy7HUeROykF6xpbSdCm +Mr6kNObl5N0ljOb8OmV4atKjmGg1rWawDLyDQ9Dtuby+dzfHtzAzP+J/3ZoOtSqq +AHVTnCclU1pm/uHN0HZ5nL5iLJTvAgMBAAGjEjAQMA4GA1UdDwEB/wQEAwIFoDAN +BgkqhkiG9w0BAQUFAAOBgQA8K+BouEV04HRTdMZd3akjTQOm6aEGW4nIRnYIf8ZV +mvUpLirVlX/unKtJinhGisFGpuYLMpemx17cnGkBeLCQRvHQjC+ho7l8/LOGheMS +nvu0XHhvmJtRbm8MKHhogwZqHFDnXonvjyqhnhEtK5F2Fimcce3MoF2QtEe0UWv/ +8DGCAaowggGmAgEBMDQwLzEtMCsGA1UEAxMkMEFDOUYwMjYtRENFQS00QzEyLTkx +MjctQzBGRDdEMkE4QjVBAgEBMAkGBSsOAwIaBQCggc0wEgYKYIZIAYb4RQEJAjEE +EwIxOTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0x +MjEwMTkwOTQ1NDdaMCAGCmCGSAGG+EUBCQUxEgQQ2EFUJdQNwQDxclIQ8qNyYzAj +BgkqhkiG9w0BCQQxFgQUy8GFXPpAwRJUT3rdvNC9Pn+4eoswOAYKYIZIAYb4RQEJ +BzEqEygwRkU3QzJEQTVEMDc2NzFFOTcxNDlCNUE3MDRCMERDNkM4MDYwRDJBMA0G +CSqGSIb3DQEBAQUABIGAWUNdzvU2iiQOtihBwF0h48Nnw/2qX8uRjg6CVTOMcGji +BxjUMifEbT//KJwljshl4y3yBLqeVYLOd04k6aKSdjgdZnrnUPI6p5tL5PfJkTAE +L6qflZ9YCU5erE4T5U98hCQBMh4nOYxgaTjnZzhpkKQuEiKq/755cjzTzlI/eok= +-----END PKCS7----- +END + pki_message_content_pem = <<END +-----BEGIN PKCS7----- +MIIDawYJKoZIhvcNAQcDoIIDXDCCA1gCAQAxggEQMIIBDAIBADB1MHAxEDAOBgNV +BAoMB2V4YW1wbGUxFzAVBgNVBAMMDlRBUk1BQyBST09UIENBMSIwIAYJKoZIhvcN +AQkBFhNzb21lb25lQGV4YW1wbGUub3JnMQswCQYDVQQGEwJVUzESMBAGA1UEBwwJ +VG93biBIYWxsAgFmMA0GCSqGSIb3DQEBAQUABIGAbKV17HvGYRtRRBNz1QLpW763 +UedhVj5KXi70o4BJGM04lItAgt6aFC9SruZjpWr1gCYKCaRSAg273DeGTQwsDoZ8 +6CPXzBpptYLz0MteQXYYWUaPZT+xmvx4NgDyk9P9MoT7JifsPrtXuzqCRFXhGdu8 +d/ru+OWxhHLvKH+bYekwggI9BgkqhkiG9w0BBwEwFAYIKoZIhvcNAwcECBNs2U5m +Msd/gIICGFOnLq/EAc9Nv+HjKR3ZVPSJMq0TImjGf5Mvc3nDgI572Hdo2aku0YXM +6WjSWkpYtxpg7Cqxfl6hPSefLPUnBqlIoM2qbrE7MSKEVD6+2bW9GqYPFVg4qQLL +sOxnxJIMfOvLFfd7guL+iLH424XfiUUxaf8EdZE4u2IEl4REvkS1FoEGwyA4BEGM +SeVPedQCbZ0qY7Pc2tmZE3XfEUhIsyStG0Nb6i6AKcAFYGapbgE6kAB0gwsYcHlW +MOvsvdAfcTq6jwtHlO1s68qtvkWquTQ9lpX+fzddUUNxEHSqv5eU3oo6fT3Vj5ZF +IVlaA5ThZMrI5PgRPuwJM4GL8/VLwY5mbDLFqn/irGeEvP99J3S87ornLLunjpxS +y1/AymcVep2H32Tj82WS/IRQXBOzz4EnQRJGszKxAV6tY+Zje3sWyTTgObhlsiTQ +TDgnvtSW8RvVHqKrwgkxxEsRHg7u8UdzZ0jg+O5+3F8B6/NWMyts0OaFqT9wvI8y +O7VIy3dUtGdz7Hde6Ggp/iTn1LbgdJ3N8Hzxf1j6NMWUKHVsadvwpRJbUeqq9c3+ +QuxsJi8wWemxxQCE+tPyc1dP+ej5/M7bERbSOHMGgX03758IvP7A/fy2DjGPv2+l +AwlEke0Uze1367QKgxM0nc3SZDlptY7zPIJC5saWXb8Rt2bw2JxEBOTavrp+ZwJ8 +tcH961onq8Tme2ICaCzk +-----END PKCS7----- +END + pki_msg = OpenSSL::PKCS7.new(pki_message_pem) + store = OpenSSL::X509::Store.new + pki_msg.verify(nil, store, nil, OpenSSL::PKCS7::NOVERIFY) + p7enc = OpenSSL::PKCS7.new(pki_msg.data) + assert_equal(pki_message_content_pem, p7enc.to_pem) + end +end + +end diff --git a/test/test_pkey_dh.rb b/test/test_pkey_dh.rb new file mode 100644 index 00000000..160a131c --- /dev/null +++ b/test/test_pkey_dh.rb @@ -0,0 +1,82 @@ +require_relative 'utils' + +if defined?(OpenSSL) + +class OpenSSL::TestPKeyDH < Test::Unit::TestCase + + NEW_KEYLEN = 256 + + def test_new + dh = OpenSSL::PKey::DH.new(NEW_KEYLEN) + assert_key(dh) + end + + def test_new_break + assert_nil(OpenSSL::PKey::DH.new(NEW_KEYLEN) { break }) + assert_raises(RuntimeError) do + OpenSSL::PKey::DH.new(NEW_KEYLEN) { raise } + end + end + + def test_to_der + dh = OpenSSL::TestUtils::TEST_KEY_DH1024 + der = dh.to_der + dh2 = OpenSSL::PKey::DH.new(der) + assert_equal_params(dh, dh2) + assert_no_key(dh2) + end + + def test_to_pem + dh = OpenSSL::TestUtils::TEST_KEY_DH1024 + pem = dh.to_pem + dh2 = OpenSSL::PKey::DH.new(pem) + assert_equal_params(dh, dh2) + assert_no_key(dh2) + end + + def test_public_key + dh = OpenSSL::TestUtils::TEST_KEY_DH1024 + public_key = dh.public_key + assert_no_key(public_key) #implies public_key.public? is false! + assert_equal(dh.to_der, public_key.to_der) + assert_equal(dh.to_pem, public_key.to_pem) + end + + def test_generate_key + dh = OpenSSL::TestUtils::TEST_KEY_DH512_PUB.public_key # creates a copy + assert_no_key(dh) + dh.generate_key! + assert_key(dh) + end + + def test_key_exchange + dh = OpenSSL::TestUtils::TEST_KEY_DH512_PUB + dh2 = dh.public_key + dh.generate_key! + dh2.generate_key! + assert_equal(dh.compute_key(dh2.pub_key), dh2.compute_key(dh.pub_key)) + end + + private + + def assert_equal_params(dh1, dh2) + assert_equal(dh1.g, dh2.g) + assert_equal(dh1.p, dh2.p) + end + + def assert_no_key(dh) + assert_equal(false, dh.public?) + assert_equal(false, dh.private?) + assert_equal(nil, dh.pub_key) + assert_equal(nil, dh.priv_key) + end + + def assert_key(dh) + assert(dh.public?) + assert(dh.private?) + assert(dh.pub_key) + assert(dh.priv_key) + end +end + +end diff --git a/test/test_pkey_dsa.rb b/test/test_pkey_dsa.rb new file mode 100644 index 00000000..555637e7 --- /dev/null +++ b/test/test_pkey_dsa.rb @@ -0,0 +1,240 @@ +require_relative 'utils' +require 'base64' + +if defined?(OpenSSL) + +class OpenSSL::TestPKeyDSA < Test::Unit::TestCase + def test_private + key = OpenSSL::PKey::DSA.new(256) + assert(key.private?) + key2 = OpenSSL::PKey::DSA.new(key.to_der) + assert(key2.private?) + key3 = key.public_key + assert(!key3.private?) + key4 = OpenSSL::PKey::DSA.new(key3.to_der) + assert(!key4.private?) + end + + def test_new + key = OpenSSL::PKey::DSA.new 256 + pem = key.public_key.to_pem + OpenSSL::PKey::DSA.new pem + assert_equal([], OpenSSL.errors) + end + + def test_new_break + assert_nil(OpenSSL::PKey::DSA.new(512) { break }) + assert_raise(RuntimeError) do + OpenSSL::PKey::DSA.new(512) { raise } + end + end + + def test_sys_sign_verify + key = OpenSSL::TestUtils::TEST_KEY_DSA256 + data = 'Sign me!' + digest = OpenSSL::Digest::SHA1.digest(data) + sig = key.syssign(digest) + assert(key.sysverify(digest, sig)) + end + + def test_sign_verify + check_sign_verify(OpenSSL::Digest::DSS1.new) + end + +if (OpenSSL::OPENSSL_VERSION_NUMBER > 0x10000000) + def test_sign_verify_sha1 + check_sign_verify(OpenSSL::Digest::SHA1.new) + end + + def test_sign_verify_sha256 + check_sign_verify(OpenSSL::Digest::SHA256.new) + end +end + + def test_digest_state_irrelevant_verify + key = OpenSSL::TestUtils::TEST_KEY_DSA256 + digest1 = OpenSSL::Digest::DSS1.new + digest2 = OpenSSL::Digest::DSS1.new + data = 'Sign me!' + sig = key.sign(digest1, data) + digest1.reset + digest1 << 'Change state of digest1' + assert(key.verify(digest1, sig, data)) + assert(key.verify(digest2, sig, data)) + end + + def test_read_DSA_PUBKEY + p = 7188211954100152441468596248707152960171255279130004340103875772401008316444412091945435731597638374542374929457672178957081124632837356913990200866056699 + q = 957032439192465935099784319494405376402293318491 + g = 122928973717064636255205666162891733518376475981809749897454444301389338825906076467196186192907631719698166056821519884939865041993585844526937010746285 + y = 1235756183583465414789073313502727057075641172514181938731172021825149551960029708596057102104063395063907739571546165975727369183495540798749742124846271 + algo = OpenSSL::ASN1::ObjectId.new('DSA') + params = OpenSSL::ASN1::Sequence.new([OpenSSL::ASN1::Integer.new(p), + OpenSSL::ASN1::Integer.new(q), + OpenSSL::ASN1::Integer.new(g)]) + algo_id = OpenSSL::ASN1::Sequence.new ([algo, params]) + pub_key = OpenSSL::ASN1::Integer.new(y) + seq = OpenSSL::ASN1::Sequence.new([algo_id, OpenSSL::ASN1::BitString.new(pub_key.to_der)]) + key = OpenSSL::PKey::DSA.new(seq.to_der) + assert(key.public?) + assert(!key.private?) + assert_equal(p, key.p) + assert_equal(q, key.q) + assert_equal(g, key.g) + assert_equal(y, key.pub_key) + assert_equal(nil, key.priv_key) + assert_equal([], OpenSSL.errors) + end + + def test_read_DSAPublicKey_pem + p = 12260055936871293565827712385212529106400444521449663325576634579961635627321079536132296996623400607469624537382977152381984332395192110731059176842635699 + q = 979494906553787301107832405790107343409973851677 + g = 3731695366899846297271147240305742456317979984190506040697507048095553842519347835107669437969086119948785140453492839427038591924536131566350847469993845 + y = 10505239074982761504240823422422813362721498896040719759460296306305851824586095328615844661273887569281276387605297130014564808567159023649684010036304695 + pem = <<-EOF +-----BEGIN DSA PUBLIC KEY----- +MIHfAkEAyJSJ+g+P/knVcgDwwTzC7Pwg/pWs2EMd/r+lYlXhNfzg0biuXRul8VR4 +VUC/phySExY0PdcqItkR/xYAYNMbNwJBAOoV57X0FxKO/PrNa/MkoWzkCKV/hzhE +p0zbFdsicw+hIjJ7S6Sd/FlDlo89HQZ2FuvWJ6wGLM1j00r39+F2qbMCFQCrkhIX +SG+is37hz1IaBeEudjB2HQJAR0AloavBvtsng8obsjLb7EKnB+pSeHr/BdIQ3VH7 +fWLOqqkzFeRrYMDzUpl36XktY6Yq8EJYlW9pCMmBVNy/dQ== +-----END DSA PUBLIC KEY----- + EOF + key = OpenSSL::PKey::DSA.new(pem) + assert(key.public?) + assert(!key.private?) + assert_equal(p, key.p) + assert_equal(q, key.q) + assert_equal(g, key.g) + assert_equal(y, key.pub_key) + assert_equal(nil, key.priv_key) + assert_equal([], OpenSSL.errors) + end + + def test_read_DSA_PUBKEY_pem + p = 12260055936871293565827712385212529106400444521449663325576634579961635627321079536132296996623400607469624537382977152381984332395192110731059176842635699 + q = 979494906553787301107832405790107343409973851677 + g = 3731695366899846297271147240305742456317979984190506040697507048095553842519347835107669437969086119948785140453492839427038591924536131566350847469993845 + y = 10505239074982761504240823422422813362721498896040719759460296306305851824586095328615844661273887569281276387605297130014564808567159023649684010036304695 + pem = <<-EOF +-----BEGIN PUBLIC KEY----- +MIHxMIGoBgcqhkjOOAQBMIGcAkEA6hXntfQXEo78+s1r8yShbOQIpX+HOESnTNsV +2yJzD6EiMntLpJ38WUOWjz0dBnYW69YnrAYszWPTSvf34XapswIVAKuSEhdIb6Kz +fuHPUhoF4S52MHYdAkBHQCWhq8G+2yeDyhuyMtvsQqcH6lJ4ev8F0hDdUft9Ys6q +qTMV5GtgwPNSmXfpeS1jpirwQliVb2kIyYFU3L91A0QAAkEAyJSJ+g+P/knVcgDw +wTzC7Pwg/pWs2EMd/r+lYlXhNfzg0biuXRul8VR4VUC/phySExY0PdcqItkR/xYA +YNMbNw== +-----END PUBLIC KEY----- + EOF + key = OpenSSL::PKey::DSA.new(pem) + assert(key.public?) + assert(!key.private?) + assert_equal(p, key.p) + assert_equal(q, key.q) + assert_equal(g, key.g) + assert_equal(y, key.pub_key) + assert_equal(nil, key.priv_key) + assert_equal([], OpenSSL.errors) + end + + def test_export_format_is_DSA_PUBKEY_pem + key = OpenSSL::TestUtils::TEST_KEY_DSA256 + pem = key.public_key.to_pem + pem.gsub!(/^-+(\w|\s)+-+$/, "") # eliminate --------BEGIN...------- + asn1 = OpenSSL::ASN1.decode(Base64.decode64(pem)) + assert_equal(OpenSSL::ASN1::SEQUENCE, asn1.tag) + assert_equal(2, asn1.value.size) + seq = asn1.value + assert_equal(OpenSSL::ASN1::SEQUENCE, seq[0].tag) + assert_equal(2, seq[0].value.size) + algo_id = seq[0].value + assert_equal(OpenSSL::ASN1::OBJECT, algo_id[0].tag) + assert_equal('DSA', algo_id[0].value) + assert_equal(OpenSSL::ASN1::SEQUENCE, algo_id[1].tag) + assert_equal(3, algo_id[1].value.size) + params = algo_id[1].value + assert_equal(OpenSSL::ASN1::INTEGER, params[0].tag) + assert_equal(key.p, params[0].value) + assert_equal(OpenSSL::ASN1::INTEGER, params[1].tag) + assert_equal(key.q, params[1].value) + assert_equal(OpenSSL::ASN1::INTEGER, params[2].tag) + assert_equal(key.g, params[2].value) + assert_equal(OpenSSL::ASN1::BIT_STRING, seq[1].tag) + assert_equal(0, seq[1].unused_bits) + pub_key = OpenSSL::ASN1.decode(seq[1].value) + assert_equal(OpenSSL::ASN1::INTEGER, pub_key.tag) + assert_equal(key.pub_key, pub_key.value) + assert_equal([], OpenSSL.errors) + end + + def test_read_private_key_der + key = OpenSSL::TestUtils::TEST_KEY_DSA256 + der = key.to_der + key2 = OpenSSL::PKey.read(der) + assert(key2.private?) + assert_equal(der, key2.to_der) + assert_equal([], OpenSSL.errors) + end + + def test_read_private_key_pem + key = OpenSSL::TestUtils::TEST_KEY_DSA256 + pem = key.to_pem + key2 = OpenSSL::PKey.read(pem) + assert(key2.private?) + assert_equal(pem, key2.to_pem) + assert_equal([], OpenSSL.errors) + end + + def test_read_public_key_der + key = OpenSSL::TestUtils::TEST_KEY_DSA256.public_key + der = key.to_der + key2 = OpenSSL::PKey.read(der) + assert(!key2.private?) + assert_equal(der, key2.to_der) + assert_equal([], OpenSSL.errors) + end + + def test_read_public_key_pem + key = OpenSSL::TestUtils::TEST_KEY_DSA256.public_key + pem = key.to_pem + key2 = OpenSSL::PKey.read(pem) + assert(!key2.private?) + assert_equal(pem, key2.to_pem) + assert_equal([], OpenSSL.errors) + end + + def test_read_private_key_pem_pw + key = OpenSSL::TestUtils::TEST_KEY_DSA256 + pem = key.to_pem(OpenSSL::Cipher.new('AES-128-CBC'), 'secret') + #callback form for password + key2 = OpenSSL::PKey.read(pem) do + 'secret' + end + assert(key2.private?) + # pass password directly + key2 = OpenSSL::PKey.read(pem, 'secret') + assert(key2.private?) + #omit pem equality check, will be different due to cipher iv + assert_equal([], OpenSSL.errors) + end + + def test_export_password_length + key = OpenSSL::TestUtils::TEST_KEY_DSA256 + assert_raise(OpenSSL::OpenSSLError) do + key.export(OpenSSL::Cipher.new('AES-128-CBC'), 'sec') + end + pem = key.export(OpenSSL::Cipher.new('AES-128-CBC'), 'secr') + assert(pem) + end + + private + + def check_sign_verify(digest) + key = OpenSSL::TestUtils::TEST_KEY_DSA256 + data = 'Sign me!' + sig = key.sign(digest, data) + assert(key.verify(digest, sig, data)) + end +end + +end diff --git a/test/test_pkey_ec.rb b/test/test_pkey_ec.rb new file mode 100644 index 00000000..5ceea4c8 --- /dev/null +++ b/test/test_pkey_ec.rb @@ -0,0 +1,211 @@ +require_relative 'utils' + +if defined?(OpenSSL::PKey::EC) + +class OpenSSL::TestEC < Test::Unit::TestCase + def setup + @data1 = 'foo' + @data2 = 'bar' * 1000 # data too long for DSA sig + + @groups = [] + @keys = [] + + OpenSSL::PKey::EC.builtin_curves.each do |curve, comment| + next if curve.start_with?("Oakley") # Oakley curves are not suitable for ECDSA + group = OpenSSL::PKey::EC::Group.new(curve) + + key = OpenSSL::PKey::EC.new(group) + key.generate_key + + @groups << group + @keys << key + end + end + + def compare_keys(k1, k2) + assert_equal(k1.to_pem, k2.to_pem) + end + + def test_builtin_curves + assert(!OpenSSL::PKey::EC.builtin_curves.empty?) + end + + def test_curve_names + @groups.each_with_index do |group, idx| + key = @keys[idx] + assert_equal(group.curve_name, key.group.curve_name) + end + end + + def test_check_key + for key in @keys + assert_equal(key.check_key, true) + assert_equal(key.private_key?, true) + assert_equal(key.public_key?, true) + end + end + + def test_group_encoding + for group in @groups + for meth in [:to_der, :to_pem] + txt = group.send(meth) + gr = OpenSSL::PKey::EC::Group.new(txt) + + assert_equal(txt, gr.send(meth)) + + assert_equal(group.generator.to_bn, gr.generator.to_bn) + assert_equal(group.cofactor, gr.cofactor) + assert_equal(group.order, gr.order) + assert_equal(group.seed, gr.seed) + assert_equal(group.degree, gr.degree) + end + end + end + + def test_key_encoding + for key in @keys + group = key.group + + for meth in [:to_der, :to_pem] + txt = key.send(meth) + assert_equal(txt, OpenSSL::PKey::EC.new(txt).send(meth)) + end + + bn = key.public_key.to_bn + assert_equal(bn, OpenSSL::PKey::EC::Point.new(group, bn).to_bn) + end + end + + def test_set_keys + for key in @keys + k = OpenSSL::PKey::EC.new + k.group = key.group + k.private_key = key.private_key + k.public_key = key.public_key + + compare_keys(key, k) + end + end + + def test_dsa_sign_verify + for key in @keys + sig = key.dsa_sign_asn1(@data1) + assert(key.dsa_verify_asn1(@data1, sig)) + end + end + + def test_dsa_sign_asn1_FIPS186_3 + for key in @keys + size = key.group.order.num_bits / 8 + 1 + dgst = (1..size).to_a.pack('C*') + begin + sig = key.dsa_sign_asn1(dgst) + # dgst is auto-truncated according to FIPS186-3 after openssl-0.9.8m + assert(key.dsa_verify_asn1(dgst + "garbage", sig)) + rescue OpenSSL::PKey::ECError => e + # just an exception for longer dgst before openssl-0.9.8m + assert_equal('ECDSA_sign: data too large for key size', e.message) + # no need to do following tests + return + end + end + end + + def test_dh_compute_key + for key in @keys + k = OpenSSL::PKey::EC.new(key.group) + k.generate_key + + puba = key.public_key + pubb = k.public_key + a = key.dh_compute_key(pubb) + b = k.dh_compute_key(puba) + assert_equal(a, b) + end + end + + def test_read_private_key_der + ec = OpenSSL::TestUtils::TEST_KEY_EC_P256V1 + der = ec.to_der + ec2 = OpenSSL::PKey.read(der) + assert(ec2.private_key?) + assert_equal(der, ec2.to_der) + assert_equal([], OpenSSL.errors) + end + + def test_read_private_key_pem + ec = OpenSSL::TestUtils::TEST_KEY_EC_P256V1 + pem = ec.to_pem + ec2 = OpenSSL::PKey.read(pem) + assert(ec2.private_key?) + assert_equal(pem, ec2.to_pem) + assert_equal([], OpenSSL.errors) + end + + def test_read_public_key_der + ec = OpenSSL::TestUtils::TEST_KEY_EC_P256V1 + ec2 = OpenSSL::PKey::EC.new(ec.group) + ec2.public_key = ec.public_key + der = ec2.to_der + ec3 = OpenSSL::PKey.read(der) + assert(!ec3.private_key?) + assert_equal(der, ec3.to_der) + assert_equal([], OpenSSL.errors) + end + + def test_read_public_key_pem + ec = OpenSSL::TestUtils::TEST_KEY_EC_P256V1 + ec2 = OpenSSL::PKey::EC.new(ec.group) + ec2.public_key = ec.public_key + pem = ec2.to_pem + ec3 = OpenSSL::PKey.read(pem) + assert(!ec3.private_key?) + assert_equal(pem, ec3.to_pem) + assert_equal([], OpenSSL.errors) + end + + def test_read_private_key_pem_pw + ec = OpenSSL::TestUtils::TEST_KEY_EC_P256V1 + pem = ec.to_pem(OpenSSL::Cipher.new('AES-128-CBC'), 'secret') + #callback form for password + ec2 = OpenSSL::PKey.read(pem) do + 'secret' + end + assert(ec2.private_key?) + # pass password directly + ec2 = OpenSSL::PKey.read(pem, 'secret') + assert(ec2.private_key?) + #omit pem equality check, will be different due to cipher iv + assert_equal([], OpenSSL.errors) + end + + def test_export_password_length + key = OpenSSL::TestUtils::TEST_KEY_EC_P256V1 + assert_raise(OpenSSL::OpenSSLError) do + key.export(OpenSSL::Cipher.new('AES-128-CBC'), 'sec') + end + pem = key.export(OpenSSL::Cipher.new('AES-128-CBC'), 'secr') + assert(pem) + end + + def test_ec_point_mul + ec = OpenSSL::TestUtils::TEST_KEY_EC_P256V1 + p1 = ec.public_key + bn1 = OpenSSL::BN.new('10') + bn2 = OpenSSL::BN.new('20') + + p2 = p1.mul(bn1) + assert(p1.group == p2.group) + p2 = p1.mul(bn1, bn2) + assert(p1.group == p2.group) + p2 = p1.mul([bn1, bn2], [p1]) + assert(p1.group == p2.group) + p2 = p1.mul([bn1, bn2], [p1], bn2) + assert(p1.group == p2.group) + end + +# test Group: asn1_flag, point_conversion + +end + +end diff --git a/test/test_pkey_rsa.rb b/test/test_pkey_rsa.rb new file mode 100644 index 00000000..df0c6090 --- /dev/null +++ b/test/test_pkey_rsa.rb @@ -0,0 +1,313 @@ +require_relative 'utils' +require 'base64' + +if defined?(OpenSSL) + +class OpenSSL::TestPKeyRSA < Test::Unit::TestCase + def test_padding + key = OpenSSL::PKey::RSA.new(512, 3) + + # Need right size for raw mode + plain0 = "x" * (512/8) + cipher = key.private_encrypt(plain0, OpenSSL::PKey::RSA::NO_PADDING) + plain1 = key.public_decrypt(cipher, OpenSSL::PKey::RSA::NO_PADDING) + assert_equal(plain0, plain1) + + # Need smaller size for pkcs1 mode + plain0 = "x" * (512/8 - 11) + cipher1 = key.private_encrypt(plain0, OpenSSL::PKey::RSA::PKCS1_PADDING) + plain1 = key.public_decrypt(cipher1, OpenSSL::PKey::RSA::PKCS1_PADDING) + assert_equal(plain0, plain1) + + cipherdef = key.private_encrypt(plain0) # PKCS1_PADDING is default + plain1 = key.public_decrypt(cipherdef) + assert_equal(plain0, plain1) + assert_equal(cipher1, cipherdef) + + # Failure cases + assert_raise(ArgumentError){ key.private_encrypt() } + assert_raise(ArgumentError){ key.private_encrypt("hi", 1, nil) } + assert_raise(OpenSSL::PKey::RSAError){ key.private_encrypt(plain0, 666) } + end + + def test_private + key = OpenSSL::PKey::RSA.new(512, 3) + assert(key.private?) + key2 = OpenSSL::PKey::RSA.new(key.to_der) + assert(key2.private?) + key3 = key.public_key + assert(!key3.private?) + key4 = OpenSSL::PKey::RSA.new(key3.to_der) + assert(!key4.private?) + end + + def test_new + key = OpenSSL::PKey::RSA.new 512 + pem = key.public_key.to_pem + OpenSSL::PKey::RSA.new pem + assert_equal([], OpenSSL.errors) + end + + def test_new_exponent_default + assert_equal(65537, OpenSSL::PKey::RSA.new(512).e) + end + + def test_new_with_exponent + 1.upto(30) do |idx| + e = (2 ** idx) + 1 + key = OpenSSL::PKey::RSA.new(512, e) + assert_equal(e, key.e) + end + end + + def test_new_break + assert_nil(OpenSSL::PKey::RSA.new(1024) { break }) + assert_raise(RuntimeError) do + OpenSSL::PKey::RSA.new(1024) { raise } + end + end + + def test_sign_verify + key = OpenSSL::TestUtils::TEST_KEY_RSA1024 + digest = OpenSSL::Digest::SHA1.new + data = 'Sign me!' + sig = key.sign(digest, data) + assert(key.verify(digest, sig, data)) + end + + def test_sign_verify_memory_leak + bug9743 = '[ruby-core:62038] [Bug #9743]' + assert_no_memory_leak(%w[-ropenssl], <<-PREP, <<-CODE, bug9743, rss: true, timeout: 30) + data = 'Sign me!' + digest = OpenSSL::Digest::SHA512.new + pkey = OpenSSL::PKey::RSA.new(2048) + signature = pkey.sign(digest, data) + pub_key = pkey.public_key + PREP + 20_000.times { + pub_key.verify(digest, signature, data) + } + CODE + + assert_no_memory_leak(%w[-ropenssl], <<-PREP, <<-CODE, bug9743, rss: true, timeout: 30) + data = 'Sign me!' + digest = OpenSSL::Digest::SHA512.new + pkey = OpenSSL::PKey::RSA.new(2048) + signature = pkey.sign(digest, data) + pub_key = pkey.public_key + PREP + 20_000.times { + begin + pub_key.verify(digest, signature, 1) + rescue TypeError + end + } + CODE + end + + def test_digest_state_irrelevant_sign + key = OpenSSL::TestUtils::TEST_KEY_RSA1024 + digest1 = OpenSSL::Digest::SHA1.new + digest2 = OpenSSL::Digest::SHA1.new + data = 'Sign me!' + digest1 << 'Change state of digest1' + sig1 = key.sign(digest1, data) + sig2 = key.sign(digest2, data) + assert_equal(sig1, sig2) + end + + def test_digest_state_irrelevant_verify + key = OpenSSL::TestUtils::TEST_KEY_RSA1024 + digest1 = OpenSSL::Digest::SHA1.new + digest2 = OpenSSL::Digest::SHA1.new + data = 'Sign me!' + sig = key.sign(digest1, data) + digest1.reset + digest1 << 'Change state of digest1' + assert(key.verify(digest1, sig, data)) + assert(key.verify(digest2, sig, data)) + end + + def test_read_RSAPublicKey + modulus = 10664264882656732240315063514678024569492171560814833397008094754351396057398262071307709191731289492697968568138092052265293364132872019762410446076526351 + exponent = 65537 + seq = OpenSSL::ASN1::Sequence.new([OpenSSL::ASN1::Integer.new(modulus), OpenSSL::ASN1::Integer.new(exponent)]) + key = OpenSSL::PKey::RSA.new(seq.to_der) + assert(key.public?) + assert(!key.private?) + assert_equal(modulus, key.n) + assert_equal(exponent, key.e) + assert_equal(nil, key.d) + assert_equal(nil, key.p) + assert_equal(nil, key.q) + assert_equal([], OpenSSL.errors) + end + + def test_read_RSA_PUBKEY + modulus = 10664264882656732240315063514678024569492171560814833397008094754351396057398262071307709191731289492697968568138092052265293364132872019762410446076526351 + exponent = 65537 + algo = OpenSSL::ASN1::ObjectId.new('rsaEncryption') + null_params = OpenSSL::ASN1::Null.new(nil) + algo_id = OpenSSL::ASN1::Sequence.new ([algo, null_params]) + pub_key = OpenSSL::ASN1::Sequence.new([OpenSSL::ASN1::Integer.new(modulus), OpenSSL::ASN1::Integer.new(exponent)]) + seq = OpenSSL::ASN1::Sequence.new([algo_id, OpenSSL::ASN1::BitString.new(pub_key.to_der)]) + key = OpenSSL::PKey::RSA.new(seq.to_der) + assert(key.public?) + assert(!key.private?) + assert_equal(modulus, key.n) + assert_equal(exponent, key.e) + assert_equal(nil, key.d) + assert_equal(nil, key.p) + assert_equal(nil, key.q) + assert_equal([], OpenSSL.errors) + end + + def test_read_RSAPublicKey_pem + modulus = 9416340886363418692990906464787534854462163316648195510702927337693641649864839352187127240942127674615733815606532506566068276485089353644309497938966061 + exponent = 65537 + pem = <<-EOF +-----BEGIN RSA PUBLIC KEY----- +MEgCQQCzyh2RIZK62E2PbTWqUljD+K23XR9AGBKNtXjal6WD2yRGcLqzPJLNCa60 +AudJR1JobbIbDJrQu6AXnWh5k/YtAgMBAAE= +-----END RSA PUBLIC KEY----- + EOF + key = OpenSSL::PKey::RSA.new(pem) + assert(key.public?) + assert(!key.private?) + assert_equal(modulus, key.n) + assert_equal(exponent, key.e) + assert_equal(nil, key.d) + assert_equal(nil, key.p) + assert_equal(nil, key.q) + assert_equal([], OpenSSL.errors) + end + + def test_read_RSA_PUBKEY_pem + modulus = 9416340886363418692990906464787534854462163316648195510702927337693641649864839352187127240942127674615733815606532506566068276485089353644309497938966061 + exponent = 65537 + pem = <<-EOF +-----BEGIN PUBLIC KEY----- +MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALPKHZEhkrrYTY9tNapSWMP4rbdd +H0AYEo21eNqXpYPbJEZwurM8ks0JrrQC50lHUmhtshsMmtC7oBedaHmT9i0C +AwEAAQ== +-----END PUBLIC KEY----- + EOF + key = OpenSSL::PKey::RSA.new(pem) + assert(key.public?) + assert(!key.private?) + assert_equal(modulus, key.n) + assert_equal(exponent, key.e) + assert_equal(nil, key.d) + assert_equal(nil, key.p) + assert_equal(nil, key.q) + assert_equal([], OpenSSL.errors) + end + + def test_export_format_is_RSA_PUBKEY + key = OpenSSL::PKey::RSA.new(512) + asn1 = OpenSSL::ASN1.decode(key.public_key.to_der) + check_PUBKEY(asn1, key) + end + + def test_export_format_is_RSA_PUBKEY_pem + key = OpenSSL::PKey::RSA.new(512) + pem = key.public_key.to_pem + pem.gsub!(/^-+(\w|\s)+-+$/, "") # eliminate --------BEGIN...------- + asn1 = OpenSSL::ASN1.decode(Base64.decode64(pem)) + check_PUBKEY(asn1, key) + end + + def test_read_private_key_der + der = OpenSSL::TestUtils::TEST_KEY_RSA1024.to_der + key = OpenSSL::PKey.read(der) + assert(key.private?) + assert_equal(der, key.to_der) + assert_equal([], OpenSSL.errors) + end + + def test_read_private_key_pem + pem = OpenSSL::TestUtils::TEST_KEY_RSA1024.to_pem + key = OpenSSL::PKey.read(pem) + assert(key.private?) + assert_equal(pem, key.to_pem) + assert_equal([], OpenSSL.errors) + end + + def test_read_public_key_der + der = OpenSSL::TestUtils::TEST_KEY_RSA1024.public_key.to_der + key = OpenSSL::PKey.read(der) + assert(!key.private?) + assert_equal(der, key.to_der) + assert_equal([], OpenSSL.errors) + end + + def test_read_public_key_pem + pem = OpenSSL::TestUtils::TEST_KEY_RSA1024.public_key.to_pem + key = OpenSSL::PKey.read(pem) + assert(!key.private?) + assert_equal(pem, key.to_pem) + assert_equal([], OpenSSL.errors) + end + + def test_read_private_key_pem_pw + pem = OpenSSL::TestUtils::TEST_KEY_RSA1024.to_pem(OpenSSL::Cipher.new('AES-128-CBC'), 'secret') + #callback form for password + key = OpenSSL::PKey.read(pem) do + 'secret' + end + assert(key.private?) + # pass password directly + key = OpenSSL::PKey.read(pem, 'secret') + assert(key.private?) + #omit pem equality check, will be different due to cipher iv + assert_equal([], OpenSSL.errors) + end + + def test_read_private_key_pem_pw_exception + pem = OpenSSL::TestUtils::TEST_KEY_RSA1024.to_pem(OpenSSL::Cipher.new('AES-128-CBC'), 'secret') + # it raises an ArgumentError from PEM reading. The exception raised inside are ignored for now. + assert_raise(ArgumentError) do + OpenSSL::PKey.read(pem) do + raise RuntimeError + end + end + assert_equal([], OpenSSL.errors) + end + + def test_export_password_length + key = OpenSSL::TestUtils::TEST_KEY_RSA1024 + assert_raise(OpenSSL::OpenSSLError) do + key.export(OpenSSL::Cipher.new('AES-128-CBC'), 'sec') + end + pem = key.export(OpenSSL::Cipher.new('AES-128-CBC'), 'secr') + assert(pem) + end + + private + + def check_PUBKEY(asn1, key) + assert_equal(OpenSSL::ASN1::SEQUENCE, asn1.tag) + assert_equal(2, asn1.value.size) + seq = asn1.value + assert_equal(OpenSSL::ASN1::SEQUENCE, seq[0].tag) + assert_equal(2, seq[0].value.size) + algo_id = seq[0].value + assert_equal(OpenSSL::ASN1::OBJECT, algo_id[0].tag) + assert_equal('rsaEncryption', algo_id[0].value) + assert_equal(OpenSSL::ASN1::NULL, algo_id[1].tag) + assert_equal(nil, algo_id[1].value) + assert_equal(OpenSSL::ASN1::BIT_STRING, seq[1].tag) + assert_equal(0, seq[1].unused_bits) + pub_key = OpenSSL::ASN1.decode(seq[1].value) + assert_equal(OpenSSL::ASN1::SEQUENCE, pub_key.tag) + assert_equal(2, pub_key.value.size) + assert_equal(OpenSSL::ASN1::INTEGER, pub_key.value[0].tag) + assert_equal(key.n, pub_key.value[0].value) + assert_equal(OpenSSL::ASN1::INTEGER, pub_key.value[1].tag) + assert_equal(key.e, pub_key.value[1].value) + assert_equal([], OpenSSL.errors) + end + +end + +end diff --git a/test/test_ssl.rb b/test/test_ssl.rb new file mode 100644 index 00000000..ddc3be5b --- /dev/null +++ b/test/test_ssl.rb @@ -0,0 +1,753 @@ +require_relative "utils" + +if defined?(OpenSSL) + +class OpenSSL::TestSSL < OpenSSL::SSLTestCase + + def test_ctx_setup + ctx = OpenSSL::SSL::SSLContext.new + assert_equal(ctx.setup, true) + assert_equal(ctx.setup, nil) + end + + def test_ctx_setup_no_compression + ctx = OpenSSL::SSL::SSLContext.new + ctx.options = OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_COMPRESSION + assert_equal(ctx.setup, true) + assert_equal(ctx.setup, nil) + assert_equal(OpenSSL::SSL::OP_NO_COMPRESSION, + ctx.options & OpenSSL::SSL::OP_NO_COMPRESSION) + end if defined?(OpenSSL::SSL::OP_NO_COMPRESSION) + + def test_not_started_session + skip "non socket argument of SSLSocket.new is not supported on this platform" if /mswin|mingw/ =~ RUBY_PLATFORM + open(__FILE__) do |f| + assert_nil OpenSSL::SSL::SSLSocket.new(f).cert + end + end + + def test_ssl_gets + start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true) { |server, port| + server_connect(port) { |ssl| + ssl.write "abc\n" + IO.select [ssl] + + line = ssl.gets + + assert_equal "abc\n", line + assert_equal Encoding::BINARY, line.encoding + } + } + end + + def test_ssl_read_nonblock + start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true) { |server, port| + server_connect(port) { |ssl| + assert_raise(IO::WaitReadable) { ssl.read_nonblock(100) } + ssl.write("abc\n") + IO.select [ssl] + assert_equal('a', ssl.read_nonblock(1)) + assert_equal("bc\n", ssl.read_nonblock(100)) + assert_raise(IO::WaitReadable) { ssl.read_nonblock(100) } + } + } + end + + def test_connect_and_close + start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true){|server, port| + sock = TCPSocket.new("127.0.0.1", port) + ssl = OpenSSL::SSL::SSLSocket.new(sock) + assert(ssl.connect) + ssl.close + assert(!sock.closed?) + sock.close + + sock = TCPSocket.new("127.0.0.1", port) + ssl = OpenSSL::SSL::SSLSocket.new(sock) + ssl.sync_close = true # !! + assert(ssl.connect) + ssl.close + assert(sock.closed?) + } + end + + def test_read_and_write + start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true){|server, port| + server_connect(port) { |ssl| + # syswrite and sysread + ITERATIONS.times{|i| + str = "x" * 100 + "\n" + ssl.syswrite(str) + newstr = '' + newstr << ssl.sysread(str.size - newstr.size) until newstr.size == str.size + assert_equal(str, newstr) + + str = "x" * i * 100 + "\n" + buf = "" + ssl.syswrite(str) + assert_equal(buf.object_id, ssl.sysread(str.size, buf).object_id) + newstr = buf + newstr << ssl.sysread(str.size - newstr.size) until newstr.size == str.size + assert_equal(str, newstr) + } + + # puts and gets + ITERATIONS.times{ + str = "x" * 100 + "\n" + ssl.puts(str) + assert_equal(str, ssl.gets) + + str = "x" * 100 + ssl.puts(str) + assert_equal(str, ssl.gets("\n", 100)) + assert_equal("\n", ssl.gets) + } + + # read and write + ITERATIONS.times{|i| + str = "x" * 100 + "\n" + ssl.write(str) + assert_equal(str, ssl.read(str.size)) + + str = "x" * i * 100 + "\n" + buf = "" + ssl.write(str) + assert_equal(buf.object_id, ssl.read(str.size, buf).object_id) + assert_equal(str, buf) + } + } + } + end + + def test_client_auth + vflag = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT + start_server(PORT, vflag, true){|server, port| + assert_raise(OpenSSL::SSL::SSLError, Errno::ECONNRESET){ + sock = TCPSocket.new("127.0.0.1", port) + ssl = OpenSSL::SSL::SSLSocket.new(sock) + ssl.sync_close = true + begin + ssl.connect + ensure + ssl.close + end + } + + ctx = OpenSSL::SSL::SSLContext.new + ctx.key = @cli_key + ctx.cert = @cli_cert + + server_connect(port, ctx) { |ssl| + ssl.puts("foo") + assert_equal("foo\n", ssl.gets) + } + + called = nil + ctx = OpenSSL::SSL::SSLContext.new + ctx.client_cert_cb = Proc.new{ |sslconn| + called = true + [@cli_cert, @cli_key] + } + + server_connect(port, ctx) { |ssl| + assert(called) + ssl.puts("foo") + assert_equal("foo\n", ssl.gets) + } + } + end + + def test_client_ca + ctx_proc = Proc.new do |ctx| + ctx.client_ca = [@ca_cert] + end + + vflag = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT + start_server(PORT, vflag, true, :ctx_proc => ctx_proc){|server, port| + ctx = OpenSSL::SSL::SSLContext.new + client_ca_from_server = nil + ctx.client_cert_cb = Proc.new do |sslconn| + client_ca_from_server = sslconn.client_ca + [@cli_cert, @cli_key] + end + server_connect(port, ctx) { |ssl| assert_equal([@ca], client_ca_from_server) } + } + end + + def test_read_nonblock_without_session + OpenSSL::TestUtils.silent do + start_server(PORT, OpenSSL::SSL::VERIFY_NONE, false){|server, port| + sock = TCPSocket.new("127.0.0.1", port) + ssl = OpenSSL::SSL::SSLSocket.new(sock) + ssl.sync_close = true + + assert_equal :wait_readable, ssl.read_nonblock(100, exception: false) + ssl.write("abc\n") + IO.select [ssl] + assert_equal('a', ssl.read_nonblock(1)) + assert_equal("bc\n", ssl.read_nonblock(100)) + assert_equal :wait_readable, ssl.read_nonblock(100, exception: false) + ssl.close + } + end + end + + def test_starttls + OpenSSL::TestUtils.silent do + start_server(PORT, OpenSSL::SSL::VERIFY_NONE, false){|server, port| + sock = TCPSocket.new("127.0.0.1", port) + ssl = OpenSSL::SSL::SSLSocket.new(sock) + ssl.sync_close = true + str = "x" * 1000 + "\n" + + ITERATIONS.times{ + ssl.puts(str) + assert_equal(str, ssl.gets) + } + starttls(ssl) + + ITERATIONS.times{ + ssl.puts(str) + assert_equal(str, ssl.gets) + } + + ssl.close + } + end + end + + def test_parallel + GC.start + start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true){|server, port| + ssls = [] + 10.times{ + sock = TCPSocket.new("127.0.0.1", port) + ssl = OpenSSL::SSL::SSLSocket.new(sock) + ssl.connect + ssl.sync_close = true + ssls << ssl + } + str = "x" * 1000 + "\n" + ITERATIONS.times{ + ssls.each{|ssl| + ssl.puts(str) + assert_equal(str, ssl.gets) + } + } + ssls.each{|ssl| ssl.close } + } + end + + def test_verify_result + start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true){|server, port| + sock = TCPSocket.new("127.0.0.1", port) + ctx = OpenSSL::SSL::SSLContext.new + ctx.set_params + ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) + ssl.sync_close = true + begin + assert_raise(OpenSSL::SSL::SSLError){ ssl.connect } + assert_equal(OpenSSL::X509::V_ERR_SELF_SIGNED_CERT_IN_CHAIN, ssl.verify_result) + ensure + ssl.close + end + } + + start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true){|server, port| + sock = TCPSocket.new("127.0.0.1", port) + ctx = OpenSSL::SSL::SSLContext.new + ctx.set_params( + :verify_callback => Proc.new do |preverify_ok, store_ctx| + store_ctx.error = OpenSSL::X509::V_OK + true + end + ) + ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) + ssl.sync_close = true + begin + ssl.connect + assert_equal(OpenSSL::X509::V_OK, ssl.verify_result) + ensure + ssl.close + end + } + + start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true){|server, port| + sock = TCPSocket.new("127.0.0.1", port) + ctx = OpenSSL::SSL::SSLContext.new + ctx.set_params( + :verify_callback => Proc.new do |preverify_ok, store_ctx| + store_ctx.error = OpenSSL::X509::V_ERR_APPLICATION_VERIFICATION + false + end + ) + ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) + ssl.sync_close = true + begin + assert_raise(OpenSSL::SSL::SSLError){ ssl.connect } + assert_equal(OpenSSL::X509::V_ERR_APPLICATION_VERIFICATION, ssl.verify_result) + ensure + ssl.close + end + } + end + + def test_exception_in_verify_callback_is_ignored + start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true){|server, port| + sock = TCPSocket.new("127.0.0.1", port) + ctx = OpenSSL::SSL::SSLContext.new + ctx.set_params( + :verify_callback => Proc.new do |preverify_ok, store_ctx| + store_ctx.error = OpenSSL::X509::V_OK + raise RuntimeError + end + ) + ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) + ssl.sync_close = true + begin + OpenSSL::TestUtils.silent do + # SSLError, not RuntimeError + assert_raise(OpenSSL::SSL::SSLError) { ssl.connect } + end + assert_equal(OpenSSL::X509::V_ERR_CERT_REJECTED, ssl.verify_result) + ensure + ssl.close + end + } + end + + def test_sslctx_set_params + start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true){|server, port| + sock = TCPSocket.new("127.0.0.1", port) + ctx = OpenSSL::SSL::SSLContext.new + ctx.set_params + assert_equal(OpenSSL::SSL::VERIFY_PEER, ctx.verify_mode) + assert_equal(OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options], ctx.options) + ciphers = ctx.ciphers + ciphers_versions = ciphers.collect{|_, v, _, _| v } + ciphers_names = ciphers.collect{|v, _, _, _| v } + assert(ciphers_names.all?{|v| /ADH/ !~ v }) + assert(ciphers_versions.all?{|v| /SSLv2/ !~ v }) + ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) + ssl.sync_close = true + begin + assert_raise(OpenSSL::SSL::SSLError){ ssl.connect } + assert_equal(OpenSSL::X509::V_ERR_SELF_SIGNED_CERT_IN_CHAIN, ssl.verify_result) + ensure + ssl.close + end + } + end + + def test_post_connection_check + sslerr = OpenSSL::SSL::SSLError + + start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true){|server, port| + server_connect(port) { |ssl| + assert_raise(sslerr){ssl.post_connection_check("localhost.localdomain")} + assert_raise(sslerr){ssl.post_connection_check("127.0.0.1")} + assert(ssl.post_connection_check("localhost")) + assert_raise(sslerr){ssl.post_connection_check("foo.example.com")} + + cert = ssl.peer_cert + assert(!OpenSSL::SSL.verify_certificate_identity(cert, "localhost.localdomain")) + assert(!OpenSSL::SSL.verify_certificate_identity(cert, "127.0.0.1")) + assert(OpenSSL::SSL.verify_certificate_identity(cert, "localhost")) + assert(!OpenSSL::SSL.verify_certificate_identity(cert, "foo.example.com")) + } + } + + now = Time.now + exts = [ + ["keyUsage","keyEncipherment,digitalSignature",true], + ["subjectAltName","DNS:localhost.localdomain",false], + ["subjectAltName","IP:127.0.0.1",false], + ] + @svr_cert = issue_cert(@svr, @svr_key, 4, now, now+1800, exts, + @ca_cert, @ca_key, OpenSSL::Digest::SHA1.new) + start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true){|server, port| + server_connect(port) { |ssl| + assert(ssl.post_connection_check("localhost.localdomain")) + assert(ssl.post_connection_check("127.0.0.1")) + assert_raise(sslerr){ssl.post_connection_check("localhost")} + assert_raise(sslerr){ssl.post_connection_check("foo.example.com")} + + cert = ssl.peer_cert + assert(OpenSSL::SSL.verify_certificate_identity(cert, "localhost.localdomain")) + assert(OpenSSL::SSL.verify_certificate_identity(cert, "127.0.0.1")) + assert(!OpenSSL::SSL.verify_certificate_identity(cert, "localhost")) + assert(!OpenSSL::SSL.verify_certificate_identity(cert, "foo.example.com")) + } + } + + now = Time.now + exts = [ + ["keyUsage","keyEncipherment,digitalSignature",true], + ["subjectAltName","DNS:*.localdomain",false], + ] + @svr_cert = issue_cert(@svr, @svr_key, 5, now, now+1800, exts, + @ca_cert, @ca_key, OpenSSL::Digest::SHA1.new) + start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true){|server, port| + server_connect(port) { |ssl| + assert(ssl.post_connection_check("localhost.localdomain")) + assert_raise(sslerr){ssl.post_connection_check("127.0.0.1")} + assert_raise(sslerr){ssl.post_connection_check("localhost")} + assert_raise(sslerr){ssl.post_connection_check("foo.example.com")} + cert = ssl.peer_cert + assert(OpenSSL::SSL.verify_certificate_identity(cert, "localhost.localdomain")) + assert(!OpenSSL::SSL.verify_certificate_identity(cert, "127.0.0.1")) + assert(!OpenSSL::SSL.verify_certificate_identity(cert, "localhost")) + assert(!OpenSSL::SSL.verify_certificate_identity(cert, "foo.example.com")) + } + } + end + + def test_verify_certificate_identity + [true, false].each do |criticality| + cert = create_null_byte_SAN_certificate(criticality) + assert_equal(false, OpenSSL::SSL.verify_certificate_identity(cert, 'www.example.com')) + assert_equal(true, OpenSSL::SSL.verify_certificate_identity(cert, "www.example.com\0.evil.com")) + assert_equal(false, OpenSSL::SSL.verify_certificate_identity(cert, '192.168.7.255')) + assert_equal(true, OpenSSL::SSL.verify_certificate_identity(cert, '192.168.7.1')) + assert_equal(false, OpenSSL::SSL.verify_certificate_identity(cert, '13::17')) + assert_equal(true, OpenSSL::SSL.verify_certificate_identity(cert, '13:0:0:0:0:0:0:17')) + end + end + + # Create NULL byte SAN certificate + def create_null_byte_SAN_certificate(critical = false) + ef = OpenSSL::X509::ExtensionFactory.new + cert = OpenSSL::X509::Certificate.new + cert.subject = OpenSSL::X509::Name.parse "/DC=some/DC=site/CN=Some Site" + ext = ef.create_ext('subjectAltName', 'DNS:placeholder,IP:192.168.7.1,IP:13::17', critical) + ext_asn1 = OpenSSL::ASN1.decode(ext.to_der) + san_list_der = ext_asn1.value.reduce(nil) { |memo,val| val.tag == 4 ? val.value : memo } + san_list_asn1 = OpenSSL::ASN1.decode(san_list_der) + san_list_asn1.value[0].value = "www.example.com\0.evil.com" + pos = critical ? 2 : 1 + ext_asn1.value[pos].value = san_list_asn1.to_der + real_ext = OpenSSL::X509::Extension.new ext_asn1 + cert.add_extension(real_ext) + cert + end + + def test_tlsext_hostname + return unless OpenSSL::SSL::SSLSocket.instance_methods.include?(:hostname) + + ctx_proc = Proc.new do |ctx, ssl| + foo_ctx = ctx.dup + + ctx.servername_cb = Proc.new do |ssl2, hostname| + case hostname + when 'foo.example.com' + foo_ctx + when 'bar.example.com' + nil + else + raise "unknown hostname #{hostname.inspect}" + end + end + end + + server_proc = Proc.new do |ctx, ssl| + readwrite_loop(ctx, ssl) + end + + start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true, :ctx_proc => ctx_proc, :server_proc => server_proc) do |server, port| + 2.times do |i| + ctx = OpenSSL::SSL::SSLContext.new + if defined?(OpenSSL::SSL::OP_NO_TICKET) + # disable RFC4507 support + ctx.options = OpenSSL::SSL::OP_NO_TICKET + end + server_connect(port, ctx) { |ssl| + ssl.hostname = (i & 1 == 0) ? 'foo.example.com' : 'bar.example.com' + str = "x" * 100 + "\n" + ssl.puts(str) + assert_equal(str, ssl.gets) + } + end + end + end + + def test_multibyte_read_write + #German a umlaut + auml = [%w{ C3 A4 }.join('')].pack('H*') + auml.force_encoding(Encoding::UTF_8) + + [10, 1000, 100000].each {|i| + str = nil + num_written = nil + server_proc = Proc.new {|ctx, ssl| + cmp = ssl.read + raw_size = cmp.size + cmp.force_encoding(Encoding::UTF_8) + assert_equal(str, cmp) + assert_equal(num_written, raw_size) + ssl.close + } + start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true, :server_proc => server_proc){|server, port| + server_connect(port) { |ssl| + str = auml * i + num_written = ssl.write(str) + } + } + } + end + + def test_unset_OP_ALL + ctx_proc = Proc.new { |ctx| + # If OP_DONT_INSERT_EMPTY_FRAGMENTS is not defined, this test is + # redundant because the default options already are equal to OP_ALL. + # But it also degrades gracefully, so keep it + ctx.options = OpenSSL::SSL::OP_ALL + } + start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true, :ctx_proc => ctx_proc){|server, port| + server_connect(port) { |ssl| + ssl.puts('hello') + assert_equal("hello\n", ssl.gets) + } + } + end + + # different OpenSSL versions react differently when facing a SSL/TLS version + # that has been marked as forbidden, therefore either of these may be raised + HANDSHAKE_ERRORS = [OpenSSL::SSL::SSLError, Errno::ECONNRESET] + +if OpenSSL::SSL::SSLContext::METHODS.include? :TLSv1 + + def test_forbid_ssl_v3_for_client + ctx_proc = Proc.new { |ctx| ctx.options = OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_SSLv3 } + start_server_version(:SSLv23, ctx_proc) { |server, port| + ctx = OpenSSL::SSL::SSLContext.new + ctx.ssl_version = :SSLv3 + assert_raise(*HANDSHAKE_ERRORS) { server_connect(port, ctx) } + } + end + + def test_forbid_ssl_v3_from_server + start_server_version(:SSLv3) { |server, port| + ctx = OpenSSL::SSL::SSLContext.new + ctx.options = OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_SSLv3 + assert_raise(*HANDSHAKE_ERRORS) { server_connect(port, ctx) } + } + end + +end + +if OpenSSL::SSL::SSLContext::METHODS.include? :TLSv1_1 + + def test_tls_v1_1 + start_server_version(:TLSv1_1) { |server, port| + server_connect(port) { |ssl| assert_equal("TLSv1.1", ssl.ssl_version) } + } + end + + def test_forbid_tls_v1_for_client + ctx_proc = Proc.new { |ctx| ctx.options = OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_TLSv1 } + start_server_version(:SSLv23, ctx_proc) { |server, port| + ctx = OpenSSL::SSL::SSLContext.new + ctx.ssl_version = :TLSv1 + assert_raise(*HANDSHAKE_ERRORS) { server_connect(port, ctx) } + } + end + + def test_forbid_tls_v1_from_server + start_server_version(:TLSv1) { |server, port| + ctx = OpenSSL::SSL::SSLContext.new + ctx.options = OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_TLSv1 + assert_raise(*HANDSHAKE_ERRORS) { server_connect(port, ctx) } + } + end + +end + +if OpenSSL::SSL::SSLContext::METHODS.include? :TLSv1_2 + + def test_tls_v1_2 + start_server_version(:TLSv1_2) { |server, port| + ctx = OpenSSL::SSL::SSLContext.new + ctx.ssl_version = :TLSv1_2_client + server_connect(port, ctx) { |ssl| assert_equal("TLSv1.2", ssl.ssl_version) } + } + end if OpenSSL::OPENSSL_VERSION_NUMBER > 0x10001000 + + def test_forbid_tls_v1_1_for_client + ctx_proc = Proc.new { |ctx| ctx.options = OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_TLSv1_1 } + start_server_version(:SSLv23, ctx_proc) { |server, port| + ctx = OpenSSL::SSL::SSLContext.new + ctx.ssl_version = :TLSv1_1 + assert_raise(*HANDSHAKE_ERRORS) { server_connect(port, ctx) } + } + end if defined?(OpenSSL::SSL::OP_NO_TLSv1_1) + + def test_forbid_tls_v1_1_from_server + start_server_version(:TLSv1_1) { |server, port| + ctx = OpenSSL::SSL::SSLContext.new + ctx.options = OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_TLSv1_1 + assert_raise(*HANDSHAKE_ERRORS) { server_connect(port, ctx) } + } + end if defined?(OpenSSL::SSL::OP_NO_TLSv1_1) + + def test_forbid_tls_v1_2_for_client + ctx_proc = Proc.new { |ctx| ctx.options = OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_TLSv1_2 } + start_server_version(:SSLv23, ctx_proc) { |server, port| + ctx = OpenSSL::SSL::SSLContext.new + ctx.ssl_version = :TLSv1_2 + assert_raise(*HANDSHAKE_ERRORS) { server_connect(port, ctx) } + } + end if defined?(OpenSSL::SSL::OP_NO_TLSv1_2) + + def test_forbid_tls_v1_2_from_server + start_server_version(:TLSv1_2) { |server, port| + ctx = OpenSSL::SSL::SSLContext.new + ctx.options = OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_TLSv1_2 + assert_raise(*HANDSHAKE_ERRORS) { server_connect(port, ctx) } + } + end if defined?(OpenSSL::SSL::OP_NO_TLSv1_2) + +end + + def test_renegotiation_cb + num_handshakes = 0 + renegotiation_cb = Proc.new { |ssl| num_handshakes += 1 } + ctx_proc = Proc.new { |ctx| ctx.renegotiation_cb = renegotiation_cb } + start_server_version(:SSLv23, ctx_proc) { |server, port| + server_connect(port) { |ssl| + assert_equal(1, num_handshakes) + } + } + end + +if OpenSSL::OPENSSL_VERSION_NUMBER > 0x10001000 + + def test_npn_protocol_selection_ary + advertised = ["http/1.1", "spdy/2"] + ctx_proc = Proc.new { |ctx| ctx.npn_protocols = advertised } + start_server_version(:SSLv23, ctx_proc) { |server, port| + selector = lambda { |which| + ctx = OpenSSL::SSL::SSLContext.new + ctx.npn_select_cb = -> (protocols) { protocols.send(which) } + server_connect(port, ctx) { |ssl| + assert_equal(advertised.send(which), ssl.npn_protocol) + } + } + selector.call(:first) + selector.call(:last) + } + end + + def test_npn_protocol_selection_enum + advertised = Object.new + def advertised.each + yield "http/1.1" + yield "spdy/2" + end + ctx_proc = Proc.new { |ctx| ctx.npn_protocols = advertised } + start_server_version(:SSLv23, ctx_proc) { |server, port| + selector = lambda { |selected, which| + ctx = OpenSSL::SSL::SSLContext.new + ctx.npn_select_cb = -> (protocols) { protocols.to_a.send(which) } + server_connect(port, ctx) { |ssl| + assert_equal(selected, ssl.npn_protocol) + } + } + selector.call("http/1.1", :first) + selector.call("spdy/2", :last) + } + end + + def test_npn_protocol_selection_cancel + ctx_proc = Proc.new { |ctx| ctx.npn_protocols = ["http/1.1"] } + start_server_version(:SSLv23, ctx_proc) { |server, port| + ctx = OpenSSL::SSL::SSLContext.new + ctx.npn_select_cb = -> (protocols) { raise RuntimeError.new } + assert_raise(RuntimeError) { server_connect(port, ctx) } + } + end + + def test_npn_advertised_protocol_too_long + ctx_proc = Proc.new { |ctx| ctx.npn_protocols = ["a" * 256] } + start_server_version(:SSLv23, ctx_proc) { |server, port| + ctx = OpenSSL::SSL::SSLContext.new + ctx.npn_select_cb = -> (protocols) { protocols.first } + assert_raise(*HANDSHAKE_ERRORS) { server_connect(port, ctx) } + } + end + + def test_npn_selected_protocol_too_long + ctx_proc = Proc.new { |ctx| ctx.npn_protocols = ["http/1.1"] } + start_server_version(:SSLv23, ctx_proc) { |server, port| + ctx = OpenSSL::SSL::SSLContext.new + ctx.npn_select_cb = -> (protocols) { "a" * 256 } + assert_raise(*HANDSHAKE_ERRORS) { server_connect(port, ctx) } + } + end + +end + + def test_invalid_shutdown_by_gc + assert_nothing_raised { + start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true){|server, port| + 10.times { + sock = TCPSocket.new("127.0.0.1", port) + ssl = OpenSSL::SSL::SSLSocket.new(sock) + GC.start + ssl.connect + sock.close + } + } + } + end + + def test_close_after_socket_close + start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true){|server, port| + sock = TCPSocket.new("127.0.0.1", port) + ssl = OpenSSL::SSL::SSLSocket.new(sock) + ssl.sync_close = true + ssl.connect + sock.close + assert_nothing_raised do + ssl.close + end + } + end + + def test_sync_close_without_connect + Socket.open(:INET, :STREAM) {|s| + ssl = OpenSSL::SSL::SSLSocket.new(s) + ssl.sync_close = true + ssl.close + assert(s.closed?) + } + end + + private + + def start_server_version(version, ctx_proc=nil, server_proc=nil, &blk) + ctx_wrap = Proc.new { |ctx| + ctx.ssl_version = version + ctx_proc.call(ctx) if ctx_proc + } + start_server( + PORT, + OpenSSL::SSL::VERIFY_NONE, + true, + :ctx_proc => ctx_wrap, + :server_proc => server_proc, + &blk + ) + end + + def server_connect(port, ctx=nil) + sock = TCPSocket.new("127.0.0.1", port) + ssl = ctx ? OpenSSL::SSL::SSLSocket.new(sock, ctx) : OpenSSL::SSL::SSLSocket.new(sock) + ssl.sync_close = true + ssl.connect + yield ssl + ensure + ssl.close + end +end + +end diff --git a/test/test_ssl_session.rb b/test/test_ssl_session.rb new file mode 100644 index 00000000..3e89633f --- /dev/null +++ b/test/test_ssl_session.rb @@ -0,0 +1,369 @@ +require_relative "utils" + +if defined?(OpenSSL) + +class OpenSSL::TestSSLSession < OpenSSL::SSLTestCase + def test_session_equals + session = OpenSSL::SSL::Session.new <<-SESSION +-----BEGIN SSL SESSION PARAMETERS----- +MIIDFgIBAQICAwEEAgA5BCCY3pW6iTkPoD5SENuztz/gZjhvey6XnHbsxd22k0Ol +dgQw8uaN3hCRnlhoIKPWInCFzrp/tQsDRFs9jDjc9pwpy/oKHmJdQQMQA1g8FYnO +gpdVoQYCBE52ikKiBAICASyjggKOMIICijCCAXKgAwIBAgIBAjANBgkqhkiG9w0B +AQUFADA9MRMwEQYKCZImiZPyLGQBGRYDb3JnMRkwFwYKCZImiZPyLGQBGRYJcnVi +eS1sYW5nMQswCQYDVQQDDAJDQTAeFw0xMTA5MTkwMDE4MTBaFw0xMTA5MTkwMDQ4 +MTBaMEQxEzARBgoJkiaJk/IsZAEZFgNvcmcxGTAXBgoJkiaJk/IsZAEZFglydWJ5 +LWxhbmcxEjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAw +gYkCgYEAy8LEsNRApz7U/j5DoB4XBgO9Z8Atv5y/OVQRp0ag8Tqo1YewsWijxEWB +7JOATwpBN267U4T1nPZIxxEEO7n/WNa2ws9JWsjah8ssEBFSxZqdXKSLf0N4Hi7/ +GQ/aYoaMCiQ8jA4jegK2FJmXM71uPe+jFN/peeBOpRfyXxRFOYcCAwEAAaMSMBAw +DgYDVR0PAQH/BAQDAgWgMA0GCSqGSIb3DQEBBQUAA4IBAQARC7GP7InX1t7VEXz2 +I8RI57S0/HSJL4fDIYP3zFpitHX1PZeo+7XuzMilvPjjBo/ky9Jzo8TYiY+N+JEz +mY/A/zPA4ZsJ7KYj6/FEdIc/vRlS0CvsbClbNjw1jl/PoB2FLr2b3uuBcZEsyZeP +yq154ijq37Ajf8K5Mi5FgshoP41BPtRPj+VVf61rv1IcEnNWdDCS6DR4XsaNC+zt +G6AqCqkytIXWRuDw6n6vYLF3A/tn2sldLo7/scY0PMDNbo63O/LTxkDHmPhSkD68 +8m9SsMeTR+RCiDEZWFPVcAH/8mDfi+5k8uN3qS+gOU/PPrmHGgl5ykiSFgqs4v61 +tddwpBAEDjcwMzA5NTYzMTU1MzAwpQMCARM= +-----END SSL SESSION PARAMETERS----- + SESSION + + start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true) { |_, port| + ctx = OpenSSL::SSL::SSLContext.new + ctx.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT + ctx.session_id_context = self.object_id.to_s + + sock = TCPSocket.new '127.0.0.1', port + ssl = OpenSSL::SSL::SSLSocket.new sock, ctx + ssl.session = session + + assert_equal session, ssl.session + sock.close + } + end + + def test_session + timeout(5) do + start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true) do |server, port| + sock = TCPSocket.new("127.0.0.1", port) + ctx = OpenSSL::SSL::SSLContext.new("TLSv1") + ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) + ssl.sync_close = true + ssl.connect + session = ssl.session + assert(session == OpenSSL::SSL::Session.new(session.to_pem)) + assert(session == OpenSSL::SSL::Session.new(ssl)) + assert_equal(300, session.timeout) + session.timeout = 5 + assert_equal(5, session.timeout) + assert_not_nil(session.time) + # SSL_SESSION_time keeps long value so we can't keep nsec fragment. + session.time = t1 = Time.now.to_i + assert_equal(Time.at(t1), session.time) + if session.respond_to?(:id) + assert_not_nil(session.id) + end + pem = session.to_pem + assert_match(/\A-----BEGIN SSL SESSION PARAMETERS-----/, pem) + assert_match(/-----END SSL SESSION PARAMETERS-----\Z/, pem) + pem.gsub!(/-----(BEGIN|END) SSL SESSION PARAMETERS-----/, '').gsub!(/[\r\n]+/m, '') + assert_equal(session.to_der, pem.unpack('m*')[0]) + assert_not_nil(session.to_text) + ssl.close + end + end + end + + DUMMY_SESSION = <<__EOS__ +-----BEGIN SSL SESSION PARAMETERS----- +MIIDzQIBAQICAwEEAgA5BCAF219w9ZEV8dNA60cpEGOI34hJtIFbf3bkfzSgMyad +MQQwyGLbkCxE4OiMLdKKem+pyh8V7ifoP7tCxhdmwoDlJxI1v6nVCjai+FGYuncy +NNSWoQYCBE4DDWuiAwIBCqOCAo4wggKKMIIBcqADAgECAgECMA0GCSqGSIb3DQEB +BQUAMD0xEzARBgoJkiaJk/IsZAEZFgNvcmcxGTAXBgoJkiaJk/IsZAEZFglydWJ5 +LWxhbmcxCzAJBgNVBAMMAkNBMB4XDTExMDYyMzA5NTQ1MVoXDTExMDYyMzEwMjQ1 +MVowRDETMBEGCgmSJomT8ixkARkWA29yZzEZMBcGCgmSJomT8ixkARkWCXJ1Ynkt +bGFuZzESMBAGA1UEAwwJbG9jYWxob3N0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB +iQKBgQDLwsSw1ECnPtT+PkOgHhcGA71nwC2/nL85VBGnRqDxOqjVh7CxaKPERYHs +k4BPCkE3brtThPWc9kjHEQQ7uf9Y1rbCz0layNqHyywQEVLFmp1cpIt/Q3geLv8Z +D9pihowKJDyMDiN6ArYUmZczvW4976MU3+l54E6lF/JfFEU5hwIDAQABoxIwEDAO +BgNVHQ8BAf8EBAMCBaAwDQYJKoZIhvcNAQEFBQADggEBACj5WhoZ/ODVeHpwgq1d +8fW/13ICRYHYpv6dzlWihyqclGxbKMlMnaVCPz+4JaVtMz3QB748KJQgL3Llg3R1 +ek+f+n1MBCMfFFsQXJ2gtLB84zD6UCz8aaCWN5/czJCd7xMz7fRLy3TOIW5boXAU +zIa8EODk+477K1uznHm286ab0Clv+9d304hwmBZgkzLg6+31Of6d6s0E0rwLGiS2 +sOWYg34Y3r4j8BS9Ak4jzpoLY6cJ0QAKCOJCgmjGr4XHpyXMLbicp3ga1uSbwtVO +gF/gTfpLhJC+y0EQ5x3Ftl88Cq7ZJuLBDMo/TLIfReJMQu/HlrTT7+LwtneSWGmr +KkSkAgQApQMCAROqgcMEgcAuDkAVfj6QAJMz9yqTzW5wPFyty7CxUEcwKjUqj5UP +/Yvky1EkRuM/eQfN7ucY+MUvMqv+R8ZSkHPsnjkBN5ChvZXjrUSZKFVjR4eFVz2V +jismLEJvIFhQh6pqTroRrOjMfTaM5Lwoytr2FTGobN9rnjIRsXeFQW1HLFbXn7Dh +8uaQkMwIVVSGRB8T7t6z6WIdWruOjCZ6G5ASI5XoqAHwGezhLodZuvJEfsVyCF9y +j+RBGfCFrrQbBdnkFI/ztgM= +-----END SSL SESSION PARAMETERS----- +__EOS__ + + DUMMY_SESSION_NO_EXT = <<-__EOS__ +-----BEGIN SSL SESSION PARAMETERS----- +MIIDCAIBAQICAwAEAgA5BCDyAW7rcpzMjDSosH+Tv6sukymeqgq3xQVVMez628A+ +lAQw9TrKzrIqlHEh6ltuQaqv/Aq83AmaAlogYktZgXAjOGnhX7ifJDNLMuCfQq53 +hPAaoQYCBE4iDeeiBAICASyjggKOMIICijCCAXKgAwIBAgIBAjANBgkqhkiG9w0B +AQUFADA9MRMwEQYKCZImiZPyLGQBGRYDb3JnMRkwFwYKCZImiZPyLGQBGRYJcnVi +eS1sYW5nMQswCQYDVQQDDAJDQTAeFw0xMTA3MTYyMjE3MTFaFw0xMTA3MTYyMjQ3 +MTFaMEQxEzARBgoJkiaJk/IsZAEZFgNvcmcxGTAXBgoJkiaJk/IsZAEZFglydWJ5 +LWxhbmcxEjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAw +gYkCgYEAy8LEsNRApz7U/j5DoB4XBgO9Z8Atv5y/OVQRp0ag8Tqo1YewsWijxEWB +7JOATwpBN267U4T1nPZIxxEEO7n/WNa2ws9JWsjah8ssEBFSxZqdXKSLf0N4Hi7/ +GQ/aYoaMCiQ8jA4jegK2FJmXM71uPe+jFN/peeBOpRfyXxRFOYcCAwEAAaMSMBAw +DgYDVR0PAQH/BAQDAgWgMA0GCSqGSIb3DQEBBQUAA4IBAQA3TRzABRG3kz8jEEYr +tDQqXgsxwTsLhTT5d1yF0D8uFw+y15hJAJnh6GJHjqhWBrF4zNoTApFo+4iIL6g3 +q9C3mUsxIVAHx41DwZBh/FI7J4FqlAoGOguu7892CNVY3ZZjc3AXMTdKjcNoWPzz +FCdj5fNT24JMMe+ZdGZK97ChahJsdn/6B3j6ze9NK9mfYEbiJhejGTPLOFVHJCGR +KYYZ3ZcKhLDr9ql4d7cCo1gBtemrmFQGPui7GttNEqmXqUKvV8mYoa8farf5i7T4 +L6a/gp2cVZTaDIS1HjbJsA/Ag7AajZqiN6LfqShNUVsrMZ+5CoV8EkBDTZPJ9MSr +a3EqpAIEAKUDAgET +-----END SSL SESSION PARAMETERS----- +__EOS__ + + + def test_session_time + sess = OpenSSL::SSL::Session.new(DUMMY_SESSION_NO_EXT) + sess.time = (now = Time.now) + assert_equal(now.to_i, sess.time.to_i) + sess.time = 1 + assert_equal(1, sess.time.to_i) + sess.time = 1.2345 + assert_equal(1, sess.time.to_i) + # Can OpenSSL handle t>2038y correctly? Version? + sess.time = 2**31 - 1 + assert_equal(2**31 - 1, sess.time.to_i) + end + + def test_session_timeout + sess = OpenSSL::SSL::Session.new(DUMMY_SESSION_NO_EXT) + assert_raise(TypeError) do + sess.timeout = Time.now + end + sess.timeout = 1 + assert_equal(1, sess.timeout.to_i) + sess.timeout = 1.2345 + assert_equal(1, sess.timeout.to_i) + sess.timeout = 2**31 - 1 + assert_equal(2**31 - 1, sess.timeout.to_i) + end + + def test_session_exts_read + assert(OpenSSL::SSL::Session.new(DUMMY_SESSION)) + end if OpenSSL::OPENSSL_VERSION_NUMBER >= 0x009080bf + + def test_client_session + last_session = nil + start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true) do |server, port| + 2.times do + sock = TCPSocket.new("127.0.0.1", port) + # Debian's openssl 0.9.8g-13 failed at assert(ssl.session_reused?), + # when use default SSLContext. [ruby-dev:36167] + ctx = OpenSSL::SSL::SSLContext.new("TLSv1") + ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) + ssl.sync_close = true + ssl.session = last_session if last_session + ssl.connect + + session = ssl.session + if last_session + assert(ssl.session_reused?) + + if session.respond_to?(:id) + assert_equal(session.id, last_session.id) + end + assert_equal(session.to_pem, last_session.to_pem) + assert_equal(session.to_der, last_session.to_der) + # Older version of OpenSSL may not be consistent. Look up which versions later. + assert_equal(session.to_text, last_session.to_text) + else + assert(!ssl.session_reused?) + end + last_session = session + + str = "x" * 100 + "\n" + ssl.puts(str) + assert_equal(str, ssl.gets) + + ssl.close + end + end + end + + def test_server_session + connections = 0 + saved_session = nil + + ctx_proc = Proc.new do |ctx, ssl| +# add test for session callbacks here + end + + server_proc = Proc.new do |ctx, ssl| + session = ssl.session + stats = ctx.session_cache_stats + + case connections + when 0 + assert_equal(stats[:cache_num], 1) + assert_equal(stats[:cache_hits], 0) + assert_equal(stats[:cache_misses], 0) + assert(!ssl.session_reused?) + when 1 + assert_equal(stats[:cache_num], 1) + assert_equal(stats[:cache_hits], 1) + assert_equal(stats[:cache_misses], 0) + assert(ssl.session_reused?) + ctx.session_remove(session) + saved_session = session + when 2 + assert_equal(stats[:cache_num], 1) + assert_equal(stats[:cache_hits], 1) + assert_equal(stats[:cache_misses], 1) + assert(!ssl.session_reused?) + ctx.session_add(saved_session) + when 3 + assert_equal(stats[:cache_num], 2) + assert_equal(stats[:cache_hits], 2) + assert_equal(stats[:cache_misses], 1) + assert(ssl.session_reused?) + ctx.flush_sessions(Time.now + 5000) + when 4 + assert_equal(stats[:cache_num], 1) + assert_equal(stats[:cache_hits], 2) + assert_equal(stats[:cache_misses], 2) + assert(!ssl.session_reused?) + ctx.session_add(saved_session) + end + connections += 1 + + readwrite_loop(ctx, ssl) + end + + first_session = nil + start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true, :ctx_proc => ctx_proc, :server_proc => server_proc) do |server, port| + 10.times do |i| + sock = TCPSocket.new("127.0.0.1", port) + ctx = OpenSSL::SSL::SSLContext.new + if defined?(OpenSSL::SSL::OP_NO_TICKET) + # disable RFC4507 support + ctx.options = OpenSSL::SSL::OP_NO_TICKET + end + ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) + ssl.sync_close = true + ssl.session = first_session if first_session + ssl.connect + + session = ssl.session + if first_session + case i + when 1; assert(ssl.session_reused?) + when 2; assert(!ssl.session_reused?) + when 3; assert(ssl.session_reused?) + when 4; assert(!ssl.session_reused?) + when 5..10; assert(ssl.session_reused?) + end + end + first_session ||= session + + str = "x" * 100 + "\n" + ssl.puts(str) + assert_equal(str, ssl.gets) + + ssl.close + end + end + end + + def test_ctx_client_session_cb + called = {} + ctx = OpenSSL::SSL::SSLContext.new("SSLv3") + ctx.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT + + ctx.session_new_cb = lambda { |ary| + sock, sess = ary + called[:new] = [sock, sess] + } + + ctx.session_remove_cb = lambda { |ary| + ctx, sess = ary + called[:remove] = [ctx, sess] + # any resulting value is OK (ignored) + } + + start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true) do |server, port| + sock = TCPSocket.new("127.0.0.1", port) + ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) + ssl.sync_close = true + ssl.connect + assert_equal(1, ctx.session_cache_stats[:cache_num]) + assert_equal(1, ctx.session_cache_stats[:connect_good]) + assert_equal([ssl, ssl.session], called[:new]) + assert(ctx.session_remove(ssl.session)) + assert(!ctx.session_remove(ssl.session)) + assert_equal([ctx, ssl.session], called[:remove]) + ssl.close + end + end + + def test_ctx_server_session_cb + called = {} + + ctx_proc = Proc.new { |ctx, ssl| + ctx.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_SERVER + last_server_session = nil + + # get_cb is called whenever a client proposed to resume a session but + # the session could not be found in the internal session cache. + ctx.session_get_cb = lambda { |ary| + sess, data = ary + if last_server_session + called[:get2] = [sess, data] + last_server_session + else + called[:get1] = [sess, data] + last_server_session = sess + nil + end + } + + ctx.session_new_cb = lambda { |ary| + sock, sess = ary + called[:new] = [sock, sess] + # SSL server doesn't cache sessions so get_cb is called next time. + ctx.session_remove(sess) + } + + ctx.session_remove_cb = lambda { |ary| + ctx, sess = ary + called[:remove] = [ctx, sess] + } + } + + server_proc = Proc.new { |c, ssl| + ssl.session + c.session_cache_stats + readwrite_loop(c, ssl) + } + start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true, :ctx_proc => ctx_proc, :server_proc => server_proc) do |server, port| + last_client_session = nil + 3.times do + sock = TCPSocket.new("127.0.0.1", port) + ssl = OpenSSL::SSL::SSLSocket.new(sock, OpenSSL::SSL::SSLContext.new("SSLv3")) + ssl.sync_close = true + ssl.session = last_client_session if last_client_session + ssl.connect + last_client_session = ssl.session + ssl.close + timeout(5) do + Thread.pass until called.key?(:new) + assert(called.delete(:new)) + Thread.pass until called.key?(:remove) + assert(called.delete(:remove)) + end + end + end + assert(called[:get1]) + assert(called[:get2]) + end +end + +end diff --git a/test/test_x509cert.rb b/test/test_x509cert.rb new file mode 100644 index 00000000..f13d6456 --- /dev/null +++ b/test/test_x509cert.rb @@ -0,0 +1,226 @@ +require_relative "utils" + +if defined?(OpenSSL) + +class OpenSSL::TestX509Certificate < Test::Unit::TestCase + def setup + @rsa1024 = OpenSSL::TestUtils::TEST_KEY_RSA1024 + @rsa2048 = OpenSSL::TestUtils::TEST_KEY_RSA2048 + @dsa256 = OpenSSL::TestUtils::TEST_KEY_DSA256 + @dsa512 = OpenSSL::TestUtils::TEST_KEY_DSA512 + @ca = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=CA") + @ee1 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=EE1") + @ee2 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=EE2") + end + + def teardown + end + + def issue_cert(*args) + OpenSSL::TestUtils.issue_cert(*args) + end + + def test_serial + [1, 2**32, 2**100].each{|s| + cert = issue_cert(@ca, @rsa2048, s, Time.now, Time.now+3600, [], + nil, nil, OpenSSL::Digest::SHA1.new) + assert_equal(s, cert.serial) + cert = OpenSSL::X509::Certificate.new(cert.to_der) + assert_equal(s, cert.serial) + } + end + + def test_public_key + exts = [ + ["basicConstraints","CA:TRUE",true], + ["subjectKeyIdentifier","hash",false], + ["authorityKeyIdentifier","keyid:always",false], + ] + + sha1 = OpenSSL::Digest::SHA1.new + dsa_digest = OpenSSL::TestUtils::DSA_SIGNATURE_DIGEST.new + + [ + [@rsa1024, sha1], [@rsa2048, sha1], [@dsa256, dsa_digest], [@dsa512, dsa_digest] + ].each{|pk, digest| + cert = issue_cert(@ca, pk, 1, Time.now, Time.now+3600, exts, + nil, nil, digest) + assert_equal(cert.extensions.sort_by(&:to_s)[2].value, + OpenSSL::TestUtils.get_subject_key_id(cert)) + cert = OpenSSL::X509::Certificate.new(cert.to_der) + assert_equal(cert.extensions.sort_by(&:to_s)[2].value, + OpenSSL::TestUtils.get_subject_key_id(cert)) + } + end + + def test_validity + now = Time.now until now && now.usec != 0 + cert = issue_cert(@ca, @rsa2048, 1, now, now+3600, [], + nil, nil, OpenSSL::Digest::SHA1.new) + assert_not_equal(now, cert.not_before) + assert_not_equal(now+3600, cert.not_after) + + now = Time.at(now.to_i) + cert = issue_cert(@ca, @rsa2048, 1, now, now+3600, [], + nil, nil, OpenSSL::Digest::SHA1.new) + assert_equal(now.getutc, cert.not_before) + assert_equal((now+3600).getutc, cert.not_after) + + now = Time.at(0) + cert = issue_cert(@ca, @rsa2048, 1, now, now, [], + nil, nil, OpenSSL::Digest::SHA1.new) + assert_equal(now.getutc, cert.not_before) + assert_equal(now.getutc, cert.not_after) + + now = Time.at(0x7fffffff) + cert = issue_cert(@ca, @rsa2048, 1, now, now, [], + nil, nil, OpenSSL::Digest::SHA1.new) + assert_equal(now.getutc, cert.not_before) + assert_equal(now.getutc, cert.not_after) + end + + def test_extension + ca_exts = [ + ["basicConstraints","CA:TRUE",true], + ["keyUsage","keyCertSign, cRLSign",true], + ["subjectKeyIdentifier","hash",false], + ["authorityKeyIdentifier","keyid:always",false], + ] + ca_cert = issue_cert(@ca, @rsa2048, 1, Time.now, Time.now+3600, ca_exts, + nil, nil, OpenSSL::Digest::SHA1.new) + ca_cert.extensions.each_with_index{|ext, i| + assert_equal(ca_exts[i].first, ext.oid) + assert_equal(ca_exts[i].last, ext.critical?) + } + + ee1_exts = [ + ["keyUsage","Non Repudiation, Digital Signature, Key Encipherment",true], + ["subjectKeyIdentifier","hash",false], + ["authorityKeyIdentifier","keyid:always",false], + ["extendedKeyUsage","clientAuth, emailProtection, codeSigning",false], + ["subjectAltName","email:ee1@ruby-lang.org",false], + ] + ee1_cert = issue_cert(@ee1, @rsa1024, 2, Time.now, Time.now+1800, ee1_exts, + ca_cert, @rsa2048, OpenSSL::Digest::SHA1.new) + assert_equal(ca_cert.subject.to_der, ee1_cert.issuer.to_der) + ee1_cert.extensions.each_with_index{|ext, i| + assert_equal(ee1_exts[i].first, ext.oid) + assert_equal(ee1_exts[i].last, ext.critical?) + } + + ee2_exts = [ + ["keyUsage","Non Repudiation, Digital Signature, Key Encipherment",true], + ["subjectKeyIdentifier","hash",false], + ["authorityKeyIdentifier","issuer:always",false], + ["extendedKeyUsage","clientAuth, emailProtection, codeSigning",false], + ["subjectAltName","email:ee2@ruby-lang.org",false], + ] + ee2_cert = issue_cert(@ee2, @rsa1024, 3, Time.now, Time.now+1800, ee2_exts, + ca_cert, @rsa2048, OpenSSL::Digest::MD5.new) + assert_equal(ca_cert.subject.to_der, ee2_cert.issuer.to_der) + ee2_cert.extensions.each_with_index{|ext, i| + assert_equal(ee2_exts[i].first, ext.oid) + assert_equal(ee2_exts[i].last, ext.critical?) + } + + end + + def test_sign_and_verify_rsa_sha1 + cert = issue_cert(@ca, @rsa2048, 1, Time.now, Time.now+3600, [], + nil, nil, OpenSSL::Digest::SHA1.new) + assert_equal(false, cert.verify(@rsa1024)) + assert_equal(true, cert.verify(@rsa2048)) + assert_equal(false, certificate_error_returns_false { cert.verify(@dsa256) }) + assert_equal(false, certificate_error_returns_false { cert.verify(@dsa512) }) + cert.serial = 2 + assert_equal(false, cert.verify(@rsa2048)) + end + + def test_sign_and_verify_rsa_md5 + cert = issue_cert(@ca, @rsa2048, 1, Time.now, Time.now+3600, [], + nil, nil, OpenSSL::Digest::MD5.new) + assert_equal(false, cert.verify(@rsa1024)) + assert_equal(true, cert.verify(@rsa2048)) + + assert_equal(false, certificate_error_returns_false { cert.verify(@dsa256) }) + assert_equal(false, certificate_error_returns_false { cert.verify(@dsa512) }) + cert.subject = @ee1 + assert_equal(false, cert.verify(@rsa2048)) + rescue OpenSSL::X509::CertificateError # RHEL7 disables MD5 + end + + def test_sign_and_verify_dsa + cert = issue_cert(@ca, @dsa512, 1, Time.now, Time.now+3600, [], + nil, nil, OpenSSL::TestUtils::DSA_SIGNATURE_DIGEST.new) + assert_equal(false, certificate_error_returns_false { cert.verify(@rsa1024) }) + assert_equal(false, certificate_error_returns_false { cert.verify(@rsa2048) }) + assert_equal(false, cert.verify(@dsa256)) + assert_equal(true, cert.verify(@dsa512)) + cert.not_after = Time.now + assert_equal(false, cert.verify(@dsa512)) + end + + def test_sign_and_verify_rsa_dss1 + cert = issue_cert(@ca, @rsa2048, 1, Time.now, Time.now+3600, [], + nil, nil, OpenSSL::Digest::DSS1.new) + assert_equal(false, cert.verify(@rsa1024)) + assert_equal(true, cert.verify(@rsa2048)) + assert_equal(false, certificate_error_returns_false { cert.verify(@dsa256) }) + assert_equal(false, certificate_error_returns_false { cert.verify(@dsa512) }) + cert.subject = @ee1 + assert_equal(false, cert.verify(@rsa2048)) + rescue OpenSSL::X509::CertificateError + end + + def test_sign_and_verify_dsa_md5 + assert_raise(OpenSSL::X509::CertificateError){ + issue_cert(@ca, @dsa512, 1, Time.now, Time.now+3600, [], + nil, nil, OpenSSL::Digest::MD5.new) + } + end + + def test_dsig_algorithm_mismatch + assert_raise(OpenSSL::X509::CertificateError) do + issue_cert(@ca, @rsa2048, 1, Time.now, Time.now+3600, [], + nil, nil, OpenSSL::Digest::DSS1.new) + end if OpenSSL::OPENSSL_VERSION_NUMBER < 0x10001000 # [ruby-core:42949] + + assert_raise(OpenSSL::X509::CertificateError) do + issue_cert(@ca, @dsa512, 1, Time.now, Time.now+3600, [], + nil, nil, OpenSSL::Digest::MD5.new) + end + end + + def test_dsa_with_sha2 + begin + cert = issue_cert(@ca, @dsa256, 1, Time.now, Time.now+3600, [], + nil, nil, OpenSSL::Digest::SHA256.new) + assert_equal("dsa_with_SHA256", cert.signature_algorithm) + rescue OpenSSL::X509::CertificateError + # dsa_with_sha2 not supported. skip following test. + return + end + # TODO: need more tests for dsa + sha2 + + # SHA1 is allowed from OpenSSL 1.0.0 (0.9.8 requires DSS1) + cert = issue_cert(@ca, @dsa256, 1, Time.now, Time.now+3600, [], + nil, nil, OpenSSL::Digest::SHA1.new) + assert_equal("dsaWithSHA1", cert.signature_algorithm) + end if defined?(OpenSSL::Digest::SHA256) + + def test_check_private_key + cert = issue_cert(@ca, @rsa2048, 1, Time.now, Time.now+3600, [], + nil, nil, OpenSSL::Digest::SHA1.new) + assert_equal(true, cert.check_private_key(@rsa2048)) + end + + private + + def certificate_error_returns_false + yield + rescue OpenSSL::X509::CertificateError + false + end +end + +end diff --git a/test/test_x509crl.rb b/test/test_x509crl.rb new file mode 100644 index 00000000..d5024751 --- /dev/null +++ b/test/test_x509crl.rb @@ -0,0 +1,220 @@ +require_relative "utils" + +if defined?(OpenSSL) + +class OpenSSL::TestX509CRL < Test::Unit::TestCase + def setup + @rsa1024 = OpenSSL::TestUtils::TEST_KEY_RSA1024 + @rsa2048 = OpenSSL::TestUtils::TEST_KEY_RSA2048 + @dsa256 = OpenSSL::TestUtils::TEST_KEY_DSA256 + @dsa512 = OpenSSL::TestUtils::TEST_KEY_DSA512 + @ca = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=CA") + @ee1 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=EE1") + @ee2 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=EE2") + end + + def teardown + end + + def issue_crl(*args) + OpenSSL::TestUtils.issue_crl(*args) + end + + def issue_cert(*args) + OpenSSL::TestUtils.issue_cert(*args) + end + + def test_basic + now = Time.at(Time.now.to_i) + + cert = issue_cert(@ca, @rsa2048, 1, now, now+3600, [], + nil, nil, OpenSSL::Digest::SHA1.new) + crl = issue_crl([], 1, now, now+1600, [], + cert, @rsa2048, OpenSSL::Digest::SHA1.new) + assert_equal(1, crl.version) + assert_equal(cert.issuer.to_der, crl.issuer.to_der) + assert_equal(now, crl.last_update) + assert_equal(now+1600, crl.next_update) + + crl = OpenSSL::X509::CRL.new(crl.to_der) + assert_equal(1, crl.version) + assert_equal(cert.issuer.to_der, crl.issuer.to_der) + assert_equal(now, crl.last_update) + assert_equal(now+1600, crl.next_update) + end + + def test_revoked + + # CRLReason ::= ENUMERATED { + # unspecified (0), + # keyCompromise (1), + # cACompromise (2), + # affiliationChanged (3), + # superseded (4), + # cessationOfOperation (5), + # certificateHold (6), + # removeFromCRL (8), + # privilegeWithdrawn (9), + # aACompromise (10) } + + now = Time.at(Time.now.to_i) + revoke_info = [ + [1, Time.at(0), 1], + [2, Time.at(0x7fffffff), 2], + [3, now, 3], + [4, now, 4], + [5, now, 5], + ] + cert = issue_cert(@ca, @rsa2048, 1, Time.now, Time.now+3600, [], + nil, nil, OpenSSL::Digest::SHA1.new) + crl = issue_crl(revoke_info, 1, Time.now, Time.now+1600, [], + cert, @rsa2048, OpenSSL::Digest::SHA1.new) + revoked = crl.revoked + assert_equal(5, revoked.size) + assert_equal(1, revoked[0].serial) + assert_equal(2, revoked[1].serial) + assert_equal(3, revoked[2].serial) + assert_equal(4, revoked[3].serial) + assert_equal(5, revoked[4].serial) + + assert_equal(Time.at(0), revoked[0].time) + assert_equal(Time.at(0x7fffffff), revoked[1].time) + assert_equal(now, revoked[2].time) + assert_equal(now, revoked[3].time) + assert_equal(now, revoked[4].time) + + assert_equal("CRLReason", revoked[0].extensions[0].oid) + assert_equal("CRLReason", revoked[1].extensions[0].oid) + assert_equal("CRLReason", revoked[2].extensions[0].oid) + assert_equal("CRLReason", revoked[3].extensions[0].oid) + assert_equal("CRLReason", revoked[4].extensions[0].oid) + + assert_equal("Key Compromise", revoked[0].extensions[0].value) + assert_equal("CA Compromise", revoked[1].extensions[0].value) + assert_equal("Affiliation Changed", revoked[2].extensions[0].value) + assert_equal("Superseded", revoked[3].extensions[0].value) + assert_equal("Cessation Of Operation", revoked[4].extensions[0].value) + + assert_equal(false, revoked[0].extensions[0].critical?) + assert_equal(false, revoked[1].extensions[0].critical?) + assert_equal(false, revoked[2].extensions[0].critical?) + assert_equal(false, revoked[3].extensions[0].critical?) + assert_equal(false, revoked[4].extensions[0].critical?) + + assert_equal("Key Compromise", revoked[0].extensions[0].value) + assert_equal("CA Compromise", revoked[1].extensions[0].value) + assert_equal("Affiliation Changed", revoked[2].extensions[0].value) + assert_equal("Superseded", revoked[3].extensions[0].value) + assert_equal("Cessation Of Operation", revoked[4].extensions[0].value) + + revoke_info = (1..1000).collect{|i| [i, now, 0] } + crl = issue_crl(revoke_info, 1, Time.now, Time.now+1600, [], + cert, @rsa2048, OpenSSL::Digest::SHA1.new) + revoked = crl.revoked + assert_equal(1000, revoked.size) + assert_equal(1, revoked[0].serial) + assert_equal(1000, revoked[999].serial) + end + + def test_extension + cert_exts = [ + ["basicConstraints", "CA:TRUE", true], + ["subjectKeyIdentifier", "hash", false], + ["authorityKeyIdentifier", "keyid:always", false], + ["subjectAltName", "email:xyzzy@ruby-lang.org", false], + ["keyUsage", "cRLSign, keyCertSign", true], + ] + crl_exts = [ + ["authorityKeyIdentifier", "keyid:always", false], + ["issuerAltName", "issuer:copy", false], + ] + + cert = issue_cert(@ca, @rsa2048, 1, Time.now, Time.now+3600, cert_exts, + nil, nil, OpenSSL::Digest::SHA1.new) + crl = issue_crl([], 1, Time.now, Time.now+1600, crl_exts, + cert, @rsa2048, OpenSSL::Digest::SHA1.new) + exts = crl.extensions + assert_equal(3, exts.size) + assert_equal("1", exts[0].value) + assert_equal("crlNumber", exts[0].oid) + assert_equal(false, exts[0].critical?) + + assert_equal("authorityKeyIdentifier", exts[1].oid) + keyid = OpenSSL::TestUtils.get_subject_key_id(cert) + assert_match(/^keyid:#{keyid}/, exts[1].value) + assert_equal(false, exts[1].critical?) + + assert_equal("issuerAltName", exts[2].oid) + assert_equal("email:xyzzy@ruby-lang.org", exts[2].value) + assert_equal(false, exts[2].critical?) + + crl = OpenSSL::X509::CRL.new(crl.to_der) + exts = crl.extensions + assert_equal(3, exts.size) + assert_equal("1", exts[0].value) + assert_equal("crlNumber", exts[0].oid) + assert_equal(false, exts[0].critical?) + + assert_equal("authorityKeyIdentifier", exts[1].oid) + keyid = OpenSSL::TestUtils.get_subject_key_id(cert) + assert_match(/^keyid:#{keyid}/, exts[1].value) + assert_equal(false, exts[1].critical?) + + assert_equal("issuerAltName", exts[2].oid) + assert_equal("email:xyzzy@ruby-lang.org", exts[2].value) + assert_equal(false, exts[2].critical?) + end + + def test_crlnumber + cert = issue_cert(@ca, @rsa2048, 1, Time.now, Time.now+3600, [], + nil, nil, OpenSSL::Digest::SHA1.new) + crl = issue_crl([], 1, Time.now, Time.now+1600, [], + cert, @rsa2048, OpenSSL::Digest::SHA1.new) + assert_match(1.to_s, crl.extensions[0].value) + assert_match(/X509v3 CRL Number:\s+#{1}/m, crl.to_text) + + crl = issue_crl([], 2**32, Time.now, Time.now+1600, [], + cert, @rsa2048, OpenSSL::Digest::SHA1.new) + assert_match((2**32).to_s, crl.extensions[0].value) + assert_match(/X509v3 CRL Number:\s+#{2**32}/m, crl.to_text) + + crl = issue_crl([], 2**100, Time.now, Time.now+1600, [], + cert, @rsa2048, OpenSSL::Digest::SHA1.new) + assert_match(/X509v3 CRL Number:\s+#{2**100}/m, crl.to_text) + assert_match((2**100).to_s, crl.extensions[0].value) + end + + def test_sign_and_verify + cert = issue_cert(@ca, @rsa2048, 1, Time.now, Time.now+3600, [], + nil, nil, OpenSSL::Digest::SHA1.new) + crl = issue_crl([], 1, Time.now, Time.now+1600, [], + cert, @rsa2048, OpenSSL::Digest::SHA1.new) + assert_equal(false, crl.verify(@rsa1024)) + assert_equal(true, crl.verify(@rsa2048)) + assert_equal(false, crl_error_returns_false { crl.verify(@dsa256) }) + assert_equal(false, crl_error_returns_false { crl.verify(@dsa512) }) + crl.version = 0 + assert_equal(false, crl.verify(@rsa2048)) + + cert = issue_cert(@ca, @dsa512, 1, Time.now, Time.now+3600, [], + nil, nil, OpenSSL::TestUtils::DSA_SIGNATURE_DIGEST.new) + crl = issue_crl([], 1, Time.now, Time.now+1600, [], + cert, @dsa512, OpenSSL::TestUtils::DSA_SIGNATURE_DIGEST.new) + assert_equal(false, crl_error_returns_false { crl.verify(@rsa1024) }) + assert_equal(false, crl_error_returns_false { crl.verify(@rsa2048) }) + assert_equal(false, crl.verify(@dsa256)) + assert_equal(true, crl.verify(@dsa512)) + crl.version = 0 + assert_equal(false, crl.verify(@dsa512)) + end + + private + + def crl_error_returns_false + yield + rescue OpenSSL::X509::CRLError + false + end +end + +end diff --git a/test/test_x509ext.rb b/test/test_x509ext.rb new file mode 100644 index 00000000..89b45c72 --- /dev/null +++ b/test/test_x509ext.rb @@ -0,0 +1,69 @@ +require_relative 'utils' + +if defined?(OpenSSL) + +class OpenSSL::TestX509Extension < Test::Unit::TestCase + def setup + @basic_constraints_value = OpenSSL::ASN1::Sequence([ + OpenSSL::ASN1::Boolean(true), # CA + OpenSSL::ASN1::Integer(2) # pathlen + ]) + @basic_constraints = OpenSSL::ASN1::Sequence([ + OpenSSL::ASN1::ObjectId("basicConstraints"), + OpenSSL::ASN1::Boolean(true), + OpenSSL::ASN1::OctetString(@basic_constraints_value.to_der), + ]) + end + + def teardown + end + + def test_new + ext = OpenSSL::X509::Extension.new(@basic_constraints.to_der) + assert_equal("basicConstraints", ext.oid) + assert_equal(true, ext.critical?) + assert_equal("CA:TRUE, pathlen:2", ext.value) + + ext = OpenSSL::X509::Extension.new("2.5.29.19", + @basic_constraints_value.to_der, true) + assert_equal(@basic_constraints.to_der, ext.to_der) + end + + def test_create_by_factory + ef = OpenSSL::X509::ExtensionFactory.new + + bc = ef.create_extension("basicConstraints", "critical, CA:TRUE, pathlen:2") + assert_equal(@basic_constraints.to_der, bc.to_der) + + bc = ef.create_extension("basicConstraints", "CA:TRUE, pathlen:2", true) + assert_equal(@basic_constraints.to_der, bc.to_der) + + begin + ef.config = OpenSSL::Config.parse(<<-_end_of_cnf_) + [crlDistPts] + URI.1 = http://www.example.com/crl + URI.2 = ldap://ldap.example.com/cn=ca?certificateRevocationList;binary + _end_of_cnf_ + rescue NotImplementedError + return + end + + cdp = ef.create_extension("crlDistributionPoints", "@crlDistPts") + assert_equal(false, cdp.critical?) + assert_equal("crlDistributionPoints", cdp.oid) + assert_match(%{URI:http://www\.example\.com/crl}, cdp.value) + assert_match( + %r{URI:ldap://ldap\.example\.com/cn=ca\?certificateRevocationList;binary}, + cdp.value) + + cdp = ef.create_extension("crlDistributionPoints", "critical, @crlDistPts") + assert_equal(true, cdp.critical?) + assert_equal("crlDistributionPoints", cdp.oid) + assert_match(%{URI:http://www.example.com/crl}, cdp.value) + assert_match( + %r{URI:ldap://ldap.example.com/cn=ca\?certificateRevocationList;binary}, + cdp.value) + end +end + +end diff --git a/test/test_x509name.rb b/test/test_x509name.rb new file mode 100644 index 00000000..de35fc30 --- /dev/null +++ b/test/test_x509name.rb @@ -0,0 +1,367 @@ +# coding: US-ASCII +require_relative 'utils' + +if defined?(OpenSSL) + +class OpenSSL::TestX509Name < Test::Unit::TestCase + OpenSSL::ASN1::ObjectId.register( + "1.2.840.113549.1.9.1", "emailAddress", "emailAddress") + OpenSSL::ASN1::ObjectId.register( + "2.5.4.5", "serialNumber", "serialNumber") + + def setup + @obj_type_tmpl = Hash.new(OpenSSL::ASN1::PRINTABLESTRING) + @obj_type_tmpl.update(OpenSSL::X509::Name::OBJECT_TYPE_TEMPLATE) + end + + def teardown + end + + def test_s_new + dn = [ ["C", "JP"], ["O", "example"], ["CN", "www.example.jp"] ] + name = OpenSSL::X509::Name.new(dn) + ary = name.to_a + assert_equal("/C=JP/O=example/CN=www.example.jp", name.to_s) + assert_equal("C", ary[0][0]) + assert_equal("O", ary[1][0]) + assert_equal("CN", ary[2][0]) + assert_equal("JP", ary[0][1]) + assert_equal("example", ary[1][1]) + assert_equal("www.example.jp", ary[2][1]) + assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[0][2]) + assert_equal(OpenSSL::ASN1::UTF8STRING, ary[1][2]) + assert_equal(OpenSSL::ASN1::UTF8STRING, ary[2][2]) + + dn = [ + ["countryName", "JP"], + ["organizationName", "example"], + ["commonName", "www.example.jp"] + ] + name = OpenSSL::X509::Name.new(dn) + ary = name.to_a + assert_equal("/C=JP/O=example/CN=www.example.jp", name.to_s) + assert_equal("C", ary[0][0]) + assert_equal("O", ary[1][0]) + assert_equal("CN", ary[2][0]) + assert_equal("JP", ary[0][1]) + assert_equal("example", ary[1][1]) + assert_equal("www.example.jp", ary[2][1]) + assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[0][2]) + assert_equal(OpenSSL::ASN1::UTF8STRING, ary[1][2]) + assert_equal(OpenSSL::ASN1::UTF8STRING, ary[2][2]) + + name = OpenSSL::X509::Name.new(dn, @obj_type_tmpl) + ary = name.to_a + assert_equal("/C=JP/O=example/CN=www.example.jp", name.to_s) + assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[0][2]) + assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[1][2]) + assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[2][2]) + + dn = [ + ["countryName", "JP", OpenSSL::ASN1::PRINTABLESTRING], + ["organizationName", "example", OpenSSL::ASN1::PRINTABLESTRING], + ["commonName", "www.example.jp", OpenSSL::ASN1::PRINTABLESTRING] + ] + name = OpenSSL::X509::Name.new(dn) + ary = name.to_a + assert_equal("/C=JP/O=example/CN=www.example.jp", name.to_s) + assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[0][2]) + assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[1][2]) + assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[2][2]) + + dn = [ + ["DC", "org"], + ["DC", "ruby-lang"], + ["CN", "GOTOU Yuuzou"], + ["emailAddress", "gotoyuzo@ruby-lang.org"], + ["serialNumber", "123"], + ] + name = OpenSSL::X509::Name.new(dn) + ary = name.to_a + assert_equal("/DC=org/DC=ruby-lang/CN=GOTOU Yuuzou/emailAddress=gotoyuzo@ruby-lang.org/serialNumber=123", name.to_s) + assert_equal("DC", ary[0][0]) + assert_equal("DC", ary[1][0]) + assert_equal("CN", ary[2][0]) + assert_equal("emailAddress", ary[3][0]) + assert_equal("serialNumber", ary[4][0]) + assert_equal("org", ary[0][1]) + assert_equal("ruby-lang", ary[1][1]) + assert_equal("GOTOU Yuuzou", ary[2][1]) + assert_equal("gotoyuzo@ruby-lang.org", ary[3][1]) + assert_equal("123", ary[4][1]) + assert_equal(OpenSSL::ASN1::IA5STRING, ary[0][2]) + assert_equal(OpenSSL::ASN1::IA5STRING, ary[1][2]) + assert_equal(OpenSSL::ASN1::UTF8STRING, ary[2][2]) + assert_equal(OpenSSL::ASN1::IA5STRING, ary[3][2]) + assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[4][2]) + + name_from_der = OpenSSL::X509::Name.new(name.to_der) + assert_equal(name_from_der.to_s, name.to_s) + assert_equal(name_from_der.to_a, name.to_a) + assert_equal(name_from_der.to_der, name.to_der) + end + + def test_unrecognized_oid + dn = [ ["1.2.3.4.5.6.7.8.9.7.5.3.1", "Unknown OID 1"], + ["1.1.2.3.5.8.13.21.34", "Unknown OID 2"], + ["C", "US"], + ["postalCode", "60602"], + ["ST", "Illinois"], + ["L", "Chicago"], + #["street", "123 Fake St"], + ["O", "Some Company LLC"], + ["CN", "mydomain.com"] ] + + name = OpenSSL::X509::Name.new(dn) + ary = name.to_a + #assert_equal("/1.2.3.4.5.6.7.8.9.7.5.3.1=Unknown OID 1/1.1.2.3.5.8.13.21.34=Unknown OID 2/C=US/postalCode=60602/ST=Illinois/L=Chicago/street=123 Fake St/O=Some Company LLC/CN=mydomain.com", name.to_s) + assert_equal("/1.2.3.4.5.6.7.8.9.7.5.3.1=Unknown OID 1/1.1.2.3.5.8.13.21.34=Unknown OID 2/C=US/postalCode=60602/ST=Illinois/L=Chicago/O=Some Company LLC/CN=mydomain.com", name.to_s) + assert_equal("1.2.3.4.5.6.7.8.9.7.5.3.1", ary[0][0]) + assert_equal("1.1.2.3.5.8.13.21.34", ary[1][0]) + assert_equal("C", ary[2][0]) + assert_equal("postalCode", ary[3][0]) + assert_equal("ST", ary[4][0]) + assert_equal("L", ary[5][0]) + #assert_equal("street", ary[6][0]) + assert_equal("O", ary[6][0]) + assert_equal("CN", ary[7][0]) + assert_equal("Unknown OID 1", ary[0][1]) + assert_equal("Unknown OID 2", ary[1][1]) + assert_equal("US", ary[2][1]) + assert_equal("60602", ary[3][1]) + assert_equal("Illinois", ary[4][1]) + assert_equal("Chicago", ary[5][1]) + #assert_equal("123 Fake St", ary[6][1]) + assert_equal("Some Company LLC", ary[6][1]) + assert_equal("mydomain.com", ary[7][1]) + end + + def test_unrecognized_oid_parse_encode_equality + dn = [ ["1.2.3.4.5.6.7.8.9.7.5.3.2", "Unknown OID1"], + ["1.1.2.3.5.8.13.21.35", "Unknown OID2"], + ["C", "US"], + ["postalCode", "60602"], + ["ST", "Illinois"], + ["L", "Chicago"], + #["street", "123 Fake St"], + ["O", "Some Company LLC"], + ["CN", "mydomain.com"] ] + + name1 = OpenSSL::X509::Name.new(dn) + name2 = OpenSSL::X509::Name.parse(name1.to_s) + assert_equal(name1.to_s, name2.to_s) + assert_equal(name1.to_a, name2.to_a) + end + + def test_s_parse + dn = "/DC=org/DC=ruby-lang/CN=www.ruby-lang.org" + name = OpenSSL::X509::Name.parse(dn) + assert_equal(dn, name.to_s) + ary = name.to_a + assert_equal("DC", ary[0][0]) + assert_equal("DC", ary[1][0]) + assert_equal("CN", ary[2][0]) + assert_equal("org", ary[0][1]) + assert_equal("ruby-lang", ary[1][1]) + assert_equal("www.ruby-lang.org", ary[2][1]) + assert_equal(OpenSSL::ASN1::IA5STRING, ary[0][2]) + assert_equal(OpenSSL::ASN1::IA5STRING, ary[1][2]) + assert_equal(OpenSSL::ASN1::UTF8STRING, ary[2][2]) + + dn2 = "DC=org, DC=ruby-lang, CN=www.ruby-lang.org" + name = OpenSSL::X509::Name.parse(dn2) + ary = name.to_a + assert_equal(dn, name.to_s) + assert_equal("org", ary[0][1]) + assert_equal("ruby-lang", ary[1][1]) + assert_equal("www.ruby-lang.org", ary[2][1]) + + name = OpenSSL::X509::Name.parse(dn2, @obj_type_tmpl) + ary = name.to_a + assert_equal(OpenSSL::ASN1::IA5STRING, ary[0][2]) + assert_equal(OpenSSL::ASN1::IA5STRING, ary[1][2]) + assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[2][2]) + end + + def test_s_parse_rfc2253 + scanner = OpenSSL::X509::Name::RFC2253DN.method(:scan) + + assert_equal([["C", "JP"]], scanner.call("C=JP")) + assert_equal([ + ["DC", "org"], + ["DC", "ruby-lang"], + ["CN", "GOTOU Yuuzou"], + ["emailAddress", "gotoyuzo@ruby-lang.org"], + ], + scanner.call( + "emailAddress=gotoyuzo@ruby-lang.org,CN=GOTOU Yuuzou,"+ + "DC=ruby-lang,DC=org") + ) + + u8 = OpenSSL::ASN1::UTF8STRING + assert_equal([ + ["DC", "org"], + ["DC", "ruby-lang"], + ["O", ",=+<>#;"], + ["O", ",=+<>#;"], + ["OU", ""], + ["OU", ""], + ["L", "aaa=\"bbb, ccc\""], + ["L", "aaa=\"bbb, ccc\""], + ["CN", "\345\276\214\350\227\244\350\243\225\350\224\265"], + ["CN", "\345\276\214\350\227\244\350\243\225\350\224\265"], + ["CN", "\345\276\214\350\227\244\350\243\225\350\224\265"], + ["CN", "\345\276\214\350\227\244\350\243\225\350\224\265", u8], + ["2.5.4.3", "GOTOU, Yuuzou"], + ["2.5.4.3", "GOTOU, Yuuzou"], + ["2.5.4.3", "GOTOU, Yuuzou"], + ["2.5.4.3", "GOTOU, Yuuzou"], + ["CN", "GOTOU \"gotoyuzo\" Yuuzou"], + ["CN", "GOTOU \"gotoyuzo\" Yuuzou"], + ["1.2.840.113549.1.9.1", "gotoyuzo@ruby-lang.org"], + ["emailAddress", "gotoyuzo@ruby-lang.org"], + ], + scanner.call( + "emailAddress=gotoyuzo@ruby-lang.org," + + "1.2.840.113549.1.9.1=gotoyuzo@ruby-lang.org," + + 'CN=GOTOU \"gotoyuzo\" Yuuzou,' + + 'CN="GOTOU \"gotoyuzo\" Yuuzou",' + + '2.5.4.3=GOTOU\,\20Yuuzou,' + + '2.5.4.3=GOTOU\, Yuuzou,' + + '2.5.4.3="GOTOU, Yuuzou",' + + '2.5.4.3="GOTOU\, Yuuzou",' + + "CN=#0C0CE5BE8CE897A4E8A395E894B5," + + 'CN=\E5\BE\8C\E8\97\A4\E8\A3\95\E8\94\B5,' + + "CN=\"\xE5\xBE\x8C\xE8\x97\xA4\xE8\xA3\x95\xE8\x94\xB5\"," + + "CN=\xE5\xBE\x8C\xE8\x97\xA4\xE8\xA3\x95\xE8\x94\xB5," + + 'L=aaa\=\"bbb\, ccc\",' + + 'L="aaa=\"bbb, ccc\"",' + + 'OU=,' + + 'OU="",' + + 'O=\,\=\+\<\>\#\;,' + + 'O=",=+<>#;",' + + "DC=ruby-lang," + + "DC=org") + ) + + [ + "DC=org+DC=jp", + "DC=org,DC=ruby-lang+DC=rubyist,DC=www" + ].each{|dn| + ex = scanner.call(dn) rescue $! + dn_r = Regexp.escape(dn) + assert_match(/^multi-valued RDN is not supported: #{dn_r}/, ex.message) + } + + [ + ["DC=org,DC=exapmle,CN", "CN"], + ["DC=org,DC=example,", ""], + ["DC=org,DC=exapmle,CN=www.example.org;", "CN=www.example.org;"], + ["DC=org,DC=exapmle,CN=#www.example.org", "CN=#www.example.org"], + ["DC=org,DC=exapmle,CN=#777777.example.org", "CN=#777777.example.org"], + ["DC=org,DC=exapmle,CN=\"www.example\".org", "CN=\"www.example\".org"], + ["DC=org,DC=exapmle,CN=www.\"example.org\"", "CN=www.\"example.org\""], + ["DC=org,DC=exapmle,CN=www.\"example\".org", "CN=www.\"example\".org"], + ].each{|dn, msg| + ex = scanner.call(dn) rescue $! + assert_match(/^malformed RDN: .*=>#{Regexp.escape(msg)}/, ex.message) + } + + dn = "CN=www.ruby-lang.org,DC=ruby-lang,DC=org" + name = OpenSSL::X509::Name.parse_rfc2253(dn) + assert_equal(dn, name.to_s(OpenSSL::X509::Name::RFC2253)) + ary = name.to_a + assert_equal("DC", ary[0][0]) + assert_equal("DC", ary[1][0]) + assert_equal("CN", ary[2][0]) + assert_equal("org", ary[0][1]) + assert_equal("ruby-lang", ary[1][1]) + assert_equal("www.ruby-lang.org", ary[2][1]) + assert_equal(OpenSSL::ASN1::IA5STRING, ary[0][2]) + assert_equal(OpenSSL::ASN1::IA5STRING, ary[1][2]) + assert_equal(OpenSSL::ASN1::UTF8STRING, ary[2][2]) + end + + def test_add_entry + dn = [ + ["DC", "org"], + ["DC", "ruby-lang"], + ["CN", "GOTOU Yuuzou"], + ["emailAddress", "gotoyuzo@ruby-lang.org"], + ["serialNumber", "123"], + ] + name = OpenSSL::X509::Name.new + dn.each{|attr| name.add_entry(*attr) } + ary = name.to_a + assert_equal("/DC=org/DC=ruby-lang/CN=GOTOU Yuuzou/emailAddress=gotoyuzo@ruby-lang.org/serialNumber=123", name.to_s) + assert_equal("DC", ary[0][0]) + assert_equal("DC", ary[1][0]) + assert_equal("CN", ary[2][0]) + assert_equal("emailAddress", ary[3][0]) + assert_equal("serialNumber", ary[4][0]) + assert_equal("org", ary[0][1]) + assert_equal("ruby-lang", ary[1][1]) + assert_equal("GOTOU Yuuzou", ary[2][1]) + assert_equal("gotoyuzo@ruby-lang.org", ary[3][1]) + assert_equal("123", ary[4][1]) + assert_equal(OpenSSL::ASN1::IA5STRING, ary[0][2]) + assert_equal(OpenSSL::ASN1::IA5STRING, ary[1][2]) + assert_equal(OpenSSL::ASN1::UTF8STRING, ary[2][2]) + assert_equal(OpenSSL::ASN1::IA5STRING, ary[3][2]) + assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[4][2]) + end + + def test_add_entry_street + return if OpenSSL::OPENSSL_VERSION_NUMBER < 0x009080df # 0.9.8m + # openssl/crypto/objects/obj_mac.h 1.83 + dn = [ + ["DC", "org"], + ["DC", "ruby-lang"], + ["CN", "GOTOU Yuuzou"], + ["emailAddress", "gotoyuzo@ruby-lang.org"], + ["serialNumber", "123"], + ["street", "Namiki"], + ] + name = OpenSSL::X509::Name.new + dn.each{|attr| name.add_entry(*attr) } + ary = name.to_a + assert_equal("/DC=org/DC=ruby-lang/CN=GOTOU Yuuzou/emailAddress=gotoyuzo@ruby-lang.org/serialNumber=123/street=Namiki", name.to_s) + assert_equal("Namiki", ary[5][1]) + end + + def test_equals2 + n1 = OpenSSL::X509::Name.parse 'CN=a' + n2 = OpenSSL::X509::Name.parse 'CN=a' + + assert_equal n1, n2 + end + + def test_spaceship + n1 = OpenSSL::X509::Name.parse 'CN=a' + n2 = OpenSSL::X509::Name.parse 'CN=b' + + assert_equal(-1, n1 <=> n2) + end + + def name_hash(name) + # OpenSSL 1.0.0 uses SHA1 for canonical encoding (not just a der) of + # X509Name for X509_NAME_hash. + name.respond_to?(:hash_old) ? name.hash_old : name.hash + end + + def test_hash + dn = "/DC=org/DC=ruby-lang/CN=www.ruby-lang.org" + name = OpenSSL::X509::Name.parse(dn) + d = Digest::MD5.digest(name.to_der) + expected = (d[0].ord & 0xff) | (d[1].ord & 0xff) << 8 | (d[2].ord & 0xff) << 16 | (d[3].ord & 0xff) << 24 + assert_equal(expected, name_hash(name)) + # + dn = "/DC=org/DC=ruby-lang/CN=baz.ruby-lang.org" + name = OpenSSL::X509::Name.parse(dn) + d = Digest::MD5.digest(name.to_der) + expected = (d[0].ord & 0xff) | (d[1].ord & 0xff) << 8 | (d[2].ord & 0xff) << 16 | (d[3].ord & 0xff) << 24 + assert_equal(expected, name_hash(name)) + end +end + +end diff --git a/test/test_x509req.rb b/test/test_x509req.rb new file mode 100644 index 00000000..d0b6a572 --- /dev/null +++ b/test/test_x509req.rb @@ -0,0 +1,158 @@ +require_relative "utils" + +if defined?(OpenSSL) + +class OpenSSL::TestX509Request < Test::Unit::TestCase + def setup + @rsa1024 = OpenSSL::TestUtils::TEST_KEY_RSA1024 + @rsa2048 = OpenSSL::TestUtils::TEST_KEY_RSA2048 + @dsa256 = OpenSSL::TestUtils::TEST_KEY_DSA256 + @dsa512 = OpenSSL::TestUtils::TEST_KEY_DSA512 + @dn = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=GOTOU Yuuzou") + end + + def issue_csr(ver, dn, key, digest) + req = OpenSSL::X509::Request.new + req.version = ver + req.subject = dn + req.public_key = key.public_key + req.sign(key, digest) + req + end + + def test_public_key + req = issue_csr(0, @dn, @rsa1024, OpenSSL::Digest::SHA1.new) + assert_equal(@rsa1024.public_key.to_der, req.public_key.to_der) + req = OpenSSL::X509::Request.new(req.to_der) + assert_equal(@rsa1024.public_key.to_der, req.public_key.to_der) + + req = issue_csr(0, @dn, @dsa512, OpenSSL::TestUtils::DSA_SIGNATURE_DIGEST.new) + assert_equal(@dsa512.public_key.to_der, req.public_key.to_der) + req = OpenSSL::X509::Request.new(req.to_der) + assert_equal(@dsa512.public_key.to_der, req.public_key.to_der) + end + + def test_version + req = issue_csr(0, @dn, @rsa1024, OpenSSL::Digest::SHA1.new) + assert_equal(0, req.version) + req = OpenSSL::X509::Request.new(req.to_der) + assert_equal(0, req.version) + + req = issue_csr(1, @dn, @rsa1024, OpenSSL::Digest::SHA1.new) + assert_equal(1, req.version) + req = OpenSSL::X509::Request.new(req.to_der) + assert_equal(1, req.version) + end + + def test_subject + req = issue_csr(0, @dn, @rsa1024, OpenSSL::Digest::SHA1.new) + assert_equal(@dn.to_der, req.subject.to_der) + req = OpenSSL::X509::Request.new(req.to_der) + assert_equal(@dn.to_der, req.subject.to_der) + end + + def create_ext_req(exts) + ef = OpenSSL::X509::ExtensionFactory.new + exts = exts.collect{|e| ef.create_extension(*e) } + return OpenSSL::ASN1::Set([OpenSSL::ASN1::Sequence(exts)]) + end + + def get_ext_req(ext_req_value) + set = OpenSSL::ASN1.decode(ext_req_value) + seq = set.value[0] + seq.value.collect{|asn1ext| + OpenSSL::X509::Extension.new(asn1ext).to_a + } + end + + def test_attr + exts = [ + ["keyUsage", "Digital Signature, Key Encipherment", true], + ["subjectAltName", "email:gotoyuzo@ruby-lang.org", false], + ] + attrval = create_ext_req(exts) + attrs = [ + OpenSSL::X509::Attribute.new("extReq", attrval), + OpenSSL::X509::Attribute.new("msExtReq", attrval), + ] + + req0 = issue_csr(0, @dn, @rsa1024, OpenSSL::Digest::SHA1.new) + attrs.each{|attr| req0.add_attribute(attr) } + req1 = issue_csr(0, @dn, @rsa1024, OpenSSL::Digest::SHA1.new) + req1.attributes = attrs + assert_equal(req0.to_der, req1.to_der) + + attrs = req0.attributes + assert_equal(2, attrs.size) + assert_equal("extReq", attrs[0].oid) + assert_equal("msExtReq", attrs[1].oid) + assert_equal(exts, get_ext_req(attrs[0].value)) + assert_equal(exts, get_ext_req(attrs[1].value)) + + req = OpenSSL::X509::Request.new(req0.to_der) + attrs = req.attributes + assert_equal(2, attrs.size) + assert_equal("extReq", attrs[0].oid) + assert_equal("msExtReq", attrs[1].oid) + assert_equal(exts, get_ext_req(attrs[0].value)) + assert_equal(exts, get_ext_req(attrs[1].value)) + end + + def test_sign_and_verify_rsa_sha1 + req = issue_csr(0, @dn, @rsa1024, OpenSSL::Digest::SHA1.new) + assert_equal(true, req.verify(@rsa1024)) + assert_equal(false, req.verify(@rsa2048)) + assert_equal(false, request_error_returns_false { req.verify(@dsa256) }) + assert_equal(false, request_error_returns_false { req.verify(@dsa512) }) + req.version = 1 + assert_equal(false, req.verify(@rsa1024)) + end + + def test_sign_and_verify_rsa_md5 + req = issue_csr(0, @dn, @rsa2048, OpenSSL::Digest::MD5.new) + assert_equal(false, req.verify(@rsa1024)) + assert_equal(true, req.verify(@rsa2048)) + assert_equal(false, request_error_returns_false { req.verify(@dsa256) }) + assert_equal(false, request_error_returns_false { req.verify(@dsa512) }) + req.subject = OpenSSL::X509::Name.parse("/C=JP/CN=FooBar") + assert_equal(false, req.verify(@rsa2048)) + rescue OpenSSL::X509::RequestError # RHEL7 disables MD5 + end + + def test_sign_and_verify_dsa + req = issue_csr(0, @dn, @dsa512, OpenSSL::TestUtils::DSA_SIGNATURE_DIGEST.new) + assert_equal(false, request_error_returns_false { req.verify(@rsa1024) }) + assert_equal(false, request_error_returns_false { req.verify(@rsa2048) }) + assert_equal(false, req.verify(@dsa256)) + assert_equal(true, req.verify(@dsa512)) + req.public_key = @rsa1024.public_key + assert_equal(false, req.verify(@dsa512)) + end + + def test_sign_and_verify_rsa_dss1 + req = issue_csr(0, @dn, @rsa1024, OpenSSL::Digest::DSS1.new) + assert_equal(true, req.verify(@rsa1024)) + assert_equal(false, req.verify(@rsa2048)) + assert_equal(false, request_error_returns_false { req.verify(@dsa256) }) + assert_equal(false, request_error_returns_false { req.verify(@dsa512) }) + req.version = 1 + assert_equal(false, req.verify(@rsa1024)) + rescue OpenSSL::X509::RequestError + skip + end + + def test_sign_and_verify_dsa_md5 + assert_raise(OpenSSL::X509::RequestError){ + issue_csr(0, @dn, @dsa512, OpenSSL::Digest::MD5.new) } + end + + private + + def request_error_returns_false + yield + rescue OpenSSL::X509::RequestError + false + end +end + +end diff --git a/test/test_x509store.rb b/test/test_x509store.rb new file mode 100644 index 00000000..082e4053 --- /dev/null +++ b/test/test_x509store.rb @@ -0,0 +1,232 @@ +require_relative "../ruby/envutil" +require_relative "utils" + +if defined?(OpenSSL) + +class OpenSSL::TestX509Store < Test::Unit::TestCase + def setup + @rsa1024 = OpenSSL::TestUtils::TEST_KEY_RSA1024 + @rsa2048 = OpenSSL::TestUtils::TEST_KEY_RSA2048 + @dsa256 = OpenSSL::TestUtils::TEST_KEY_DSA256 + @dsa512 = OpenSSL::TestUtils::TEST_KEY_DSA512 + @ca1 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=CA1") + @ca2 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=CA2") + @ee1 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=EE1") + @ee2 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=EE2") + end + + def teardown + end + + def test_nosegv_on_cleanup + cert = OpenSSL::X509::Certificate.new + store = OpenSSL::X509::Store.new + ctx = OpenSSL::X509::StoreContext.new(store, cert, []) + EnvUtil.suppress_warning do + ctx.cleanup + end + ctx.verify + end + + def issue_cert(*args) + OpenSSL::TestUtils.issue_cert(*args) + end + + def issue_crl(*args) + OpenSSL::TestUtils.issue_crl(*args) + end + + def test_verify + now = Time.at(Time.now.to_i) + ca_exts = [ + ["basicConstraints","CA:TRUE",true], + ["keyUsage","cRLSign,keyCertSign",true], + ] + ee_exts = [ + ["keyUsage","keyEncipherment,digitalSignature",true], + ] + ca1_cert = issue_cert(@ca1, @rsa2048, 1, now, now+3600, ca_exts, + nil, nil, OpenSSL::Digest::SHA1.new) + ca2_cert = issue_cert(@ca2, @rsa1024, 2, now, now+1800, ca_exts, + ca1_cert, @rsa2048, OpenSSL::Digest::SHA1.new) + ee1_cert = issue_cert(@ee1, @dsa256, 10, now, now+1800, ee_exts, + ca2_cert, @rsa1024, OpenSSL::Digest::SHA1.new) + ee2_cert = issue_cert(@ee2, @dsa512, 20, now, now+1800, ee_exts, + ca2_cert, @rsa1024, OpenSSL::Digest::SHA1.new) + ee3_cert = issue_cert(@ee2, @dsa512, 30, now-100, now-1, ee_exts, + ca2_cert, @rsa1024, OpenSSL::Digest::SHA1.new) + ee4_cert = issue_cert(@ee2, @dsa512, 40, now+1000, now+2000, ee_exts, + ca2_cert, @rsa1024, OpenSSL::Digest::SHA1.new) + + revoke_info = [] + crl1 = issue_crl(revoke_info, 1, now, now+1800, [], + ca1_cert, @rsa2048, OpenSSL::Digest::SHA1.new) + revoke_info = [ [2, now, 1], ] + crl1_2 = issue_crl(revoke_info, 2, now, now+1800, [], + ca1_cert, @rsa2048, OpenSSL::Digest::SHA1.new) + revoke_info = [ [20, now, 1], ] + crl2 = issue_crl(revoke_info, 1, now, now+1800, [], + ca2_cert, @rsa1024, OpenSSL::Digest::SHA1.new) + revoke_info = [] + crl2_2 = issue_crl(revoke_info, 2, now-100, now-1, [], + ca2_cert, @rsa1024, OpenSSL::Digest::SHA1.new) + + assert_equal(true, ca1_cert.verify(ca1_cert.public_key)) # self signed + assert_equal(true, ca2_cert.verify(ca1_cert.public_key)) # issued by ca1 + assert_equal(true, ee1_cert.verify(ca2_cert.public_key)) # issued by ca2 + assert_equal(true, ee2_cert.verify(ca2_cert.public_key)) # issued by ca2 + assert_equal(true, ee3_cert.verify(ca2_cert.public_key)) # issued by ca2 + assert_equal(true, crl1.verify(ca1_cert.public_key)) # issued by ca1 + assert_equal(true, crl1_2.verify(ca1_cert.public_key)) # issued by ca1 + assert_equal(true, crl2.verify(ca2_cert.public_key)) # issued by ca2 + assert_equal(true, crl2_2.verify(ca2_cert.public_key)) # issued by ca2 + + store = OpenSSL::X509::Store.new + assert_equal(false, store.verify(ca1_cert)) + assert_not_equal(OpenSSL::X509::V_OK, store.error) + + assert_equal(false, store.verify(ca2_cert)) + assert_not_equal(OpenSSL::X509::V_OK, store.error) + + store.add_cert(ca1_cert) + assert_equal(true, store.verify(ca2_cert)) + assert_equal(OpenSSL::X509::V_OK, store.error) + assert_equal("ok", store.error_string) + chain = store.chain + assert_equal(2, chain.size) + assert_equal(@ca2.to_der, chain[0].subject.to_der) + assert_equal(@ca1.to_der, chain[1].subject.to_der) + + store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT + assert_equal(false, store.verify(ca2_cert)) + assert_not_equal(OpenSSL::X509::V_OK, store.error) + + store.purpose = OpenSSL::X509::PURPOSE_CRL_SIGN + assert_equal(true, store.verify(ca2_cert)) + assert_equal(OpenSSL::X509::V_OK, store.error) + + store.add_cert(ca2_cert) + store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT + assert_equal(true, store.verify(ee1_cert)) + assert_equal(true, store.verify(ee2_cert)) + assert_equal(OpenSSL::X509::V_OK, store.error) + assert_equal("ok", store.error_string) + chain = store.chain + assert_equal(3, chain.size) + assert_equal(@ee2.to_der, chain[0].subject.to_der) + assert_equal(@ca2.to_der, chain[1].subject.to_der) + assert_equal(@ca1.to_der, chain[2].subject.to_der) + assert_equal(false, store.verify(ee3_cert)) + assert_equal(OpenSSL::X509::V_ERR_CERT_HAS_EXPIRED, store.error) + assert_match(/expire/i, store.error_string) + assert_equal(false, store.verify(ee4_cert)) + assert_equal(OpenSSL::X509::V_ERR_CERT_NOT_YET_VALID, store.error) + assert_match(/not yet valid/i, store.error_string) + + store = OpenSSL::X509::Store.new + store.add_cert(ca1_cert) + store.add_cert(ca2_cert) + store.time = now + 1500 + assert_equal(true, store.verify(ca1_cert)) + assert_equal(true, store.verify(ca2_cert)) + assert_equal(true, store.verify(ee4_cert)) + store.time = now + 1900 + assert_equal(true, store.verify(ca1_cert)) + assert_equal(false, store.verify(ca2_cert)) + assert_equal(OpenSSL::X509::V_ERR_CERT_HAS_EXPIRED, store.error) + assert_equal(false, store.verify(ee4_cert)) + assert_equal(OpenSSL::X509::V_ERR_CERT_HAS_EXPIRED, store.error) + store.time = now + 4000 + assert_equal(false, store.verify(ee1_cert)) + assert_equal(OpenSSL::X509::V_ERR_CERT_HAS_EXPIRED, store.error) + assert_equal(false, store.verify(ee4_cert)) + assert_equal(OpenSSL::X509::V_ERR_CERT_HAS_EXPIRED, store.error) + + # the underlying X509 struct caches the result of the last + # verification for signature and not-before. so the following code + # rebuilds new objects to avoid site effect. + store.time = Time.now - 4000 + assert_equal(false, store.verify(OpenSSL::X509::Certificate.new(ca2_cert))) + assert_equal(OpenSSL::X509::V_ERR_CERT_NOT_YET_VALID, store.error) + assert_equal(false, store.verify(OpenSSL::X509::Certificate.new(ee1_cert))) + assert_equal(OpenSSL::X509::V_ERR_CERT_NOT_YET_VALID, store.error) + + return unless defined?(OpenSSL::X509::V_FLAG_CRL_CHECK) + + store = OpenSSL::X509::Store.new + store.purpose = OpenSSL::X509::PURPOSE_ANY + store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK + store.add_cert(ca1_cert) + store.add_crl(crl1) # revoke no cert + store.add_crl(crl2) # revoke ee2_cert + assert_equal(true, store.verify(ca1_cert)) + assert_equal(true, store.verify(ca2_cert)) + assert_equal(true, store.verify(ee1_cert, [ca2_cert])) + assert_equal(false, store.verify(ee2_cert, [ca2_cert])) + + store = OpenSSL::X509::Store.new + store.purpose = OpenSSL::X509::PURPOSE_ANY + store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK + store.add_cert(ca1_cert) + store.add_crl(crl1_2) # revoke ca2_cert + store.add_crl(crl2) # revoke ee2_cert + assert_equal(true, store.verify(ca1_cert)) + assert_equal(false, store.verify(ca2_cert)) + assert_equal(true, store.verify(ee1_cert, [ca2_cert]), + "This test is expected to be success with OpenSSL 0.9.7c or later.") + assert_equal(false, store.verify(ee2_cert, [ca2_cert])) + + store.flags = + OpenSSL::X509::V_FLAG_CRL_CHECK|OpenSSL::X509::V_FLAG_CRL_CHECK_ALL + assert_equal(true, store.verify(ca1_cert)) + assert_equal(false, store.verify(ca2_cert)) + assert_equal(false, store.verify(ee1_cert, [ca2_cert])) + assert_equal(false, store.verify(ee2_cert, [ca2_cert])) + + store = OpenSSL::X509::Store.new + store.purpose = OpenSSL::X509::PURPOSE_ANY + store.flags = + OpenSSL::X509::V_FLAG_CRL_CHECK|OpenSSL::X509::V_FLAG_CRL_CHECK_ALL + store.add_cert(ca1_cert) + store.add_cert(ca2_cert) + store.add_crl(crl1) + store.add_crl(crl2_2) # issued by ca2 but expired. + assert_equal(true, store.verify(ca1_cert)) + assert_equal(true, store.verify(ca2_cert)) + assert_equal(false, store.verify(ee1_cert)) + assert_equal(OpenSSL::X509::V_ERR_CRL_HAS_EXPIRED, store.error) + assert_equal(false, store.verify(ee2_cert)) + end + + def test_set_errors + now = Time.now + ca1_cert = issue_cert(@ca1, @rsa2048, 1, now, now+3600, [], + nil, nil, OpenSSL::Digest::SHA1.new) + store = OpenSSL::X509::Store.new + store.add_cert(ca1_cert) + assert_raise(OpenSSL::X509::StoreError){ + store.add_cert(ca1_cert) # add same certificate twice + } + + revoke_info = [] + crl1 = issue_crl(revoke_info, 1, now, now+1800, [], + ca1_cert, @rsa2048, OpenSSL::Digest::SHA1.new) + revoke_info = [ [2, now, 1], ] + crl2 = issue_crl(revoke_info, 2, now+1800, now+3600, [], + ca1_cert, @rsa2048, OpenSSL::Digest::SHA1.new) + store.add_crl(crl1) + if /0\.9\.8.*-rhel/ =~ OpenSSL::OPENSSL_VERSION + # RedHat is distributing a patched version of OpenSSL that allows + # multiple CRL for a key (multi-crl.patch) + assert_nothing_raised do + store.add_crl(crl2) # add CRL issued by same CA twice. + end + else + assert_raise(OpenSSL::X509::StoreError){ + store.add_crl(crl2) # add CRL issued by same CA twice. + } + end + end +end + +end diff --git a/test/utils.rb b/test/utils.rb new file mode 100644 index 00000000..4df14056 --- /dev/null +++ b/test/utils.rb @@ -0,0 +1,336 @@ +begin + require "openssl" + + # Disable FIPS mode for tests for installations + # where FIPS mode would be enabled by default. + # Has no effect on all other installations. + OpenSSL.fips_mode=false +rescue LoadError +end +require "test/unit" +require "digest/md5" +require 'tempfile' +require "rbconfig" +require "socket" +require_relative '../ruby/envutil' + +module OpenSSL::TestUtils + TEST_KEY_RSA1024 = OpenSSL::PKey::RSA.new <<-_end_of_pem_ +-----BEGIN RSA PRIVATE KEY----- +MIICXgIBAAKBgQDLwsSw1ECnPtT+PkOgHhcGA71nwC2/nL85VBGnRqDxOqjVh7Cx +aKPERYHsk4BPCkE3brtThPWc9kjHEQQ7uf9Y1rbCz0layNqHyywQEVLFmp1cpIt/ +Q3geLv8ZD9pihowKJDyMDiN6ArYUmZczvW4976MU3+l54E6lF/JfFEU5hwIDAQAB +AoGBAKSl/MQarye1yOysqX6P8fDFQt68VvtXkNmlSiKOGuzyho0M+UVSFcs6k1L0 +maDE25AMZUiGzuWHyaU55d7RXDgeskDMakD1v6ZejYtxJkSXbETOTLDwUWTn618T +gnb17tU1jktUtU67xK/08i/XodlgnQhs6VoHTuCh3Hu77O6RAkEA7+gxqBuZR572 +74/akiW/SuXm0SXPEviyO1MuSRwtI87B02D0qgV8D1UHRm4AhMnJ8MCs1809kMQE +JiQUCrp9mQJBANlt2ngBO14us6NnhuAseFDTBzCHXwUUu1YKHpMMmxpnGqaldGgX +sOZB3lgJsT9VlGf3YGYdkLTNVbogQKlKpB8CQQDiSwkb4vyQfDe8/NpU5Not0fII +8jsDUCb+opWUTMmfbxWRR3FBNu8wnym/m19N4fFj8LqYzHX4KY0oVPu6qvJxAkEA +wa5snNekFcqONLIE4G5cosrIrb74sqL8GbGb+KuTAprzj5z1K8Bm0UW9lTjVDjDi +qRYgZfZSL+x1P/54+xTFSwJAY1FxA/N3QPCXCjPh5YqFxAMQs2VVYTfg+t0MEcJD +dPMQD5JX6g5HKnHFg2mZtoXQrWmJSn7p8GJK8yNTopEErA== +-----END RSA PRIVATE KEY----- + _end_of_pem_ + + TEST_KEY_RSA2048 = OpenSSL::PKey::RSA.new <<-_end_of_pem_ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAuV9ht9J7k4NBs38jOXvvTKY9gW8nLICSno5EETR1cuF7i4pN +s9I1QJGAFAX0BEO4KbzXmuOvfCpD3CU+Slp1enenfzq/t/e/1IRW0wkJUJUFQign +4CtrkJL+P07yx18UjyPlBXb81ApEmAB5mrJVSrWmqbjs07JbuS4QQGGXLc+Su96D +kYKmSNVjBiLxVVSpyZfAY3hD37d60uG+X8xdW5v68JkRFIhdGlb6JL8fllf/A/bl +NwdJOhVr9mESHhwGjwfSeTDPfd8ZLE027E5lyAVX9KZYcU00mOX+fdxOSnGqS/8J +DRh0EPHDL15RcJjV2J6vZjPb0rOYGDoMcH+94wIDAQABAoIBAAzsamqfYQAqwXTb +I0CJtGg6msUgU7HVkOM+9d3hM2L791oGHV6xBAdpXW2H8LgvZHJ8eOeSghR8+dgq +PIqAffo4x1Oma+FOg3A0fb0evyiACyrOk+EcBdbBeLo/LcvahBtqnDfiUMQTpy6V +seSoFCwuN91TSCeGIsDpRjbG1vxZgtx+uI+oH5+ytqJOmfCksRDCkMglGkzyfcl0 +Xc5CUhIJ0my53xijEUQl19rtWdMnNnnkdbG8PT3LZlOta5Do86BElzUYka0C6dUc +VsBDQ0Nup0P6rEQgy7tephHoRlUGTYamsajGJaAo1F3IQVIrRSuagi7+YpSpCqsW +wORqorkCgYEA7RdX6MDVrbw7LePnhyuaqTiMK+055/R1TqhB1JvvxJ1CXk2rDL6G +0TLHQ7oGofd5LYiemg4ZVtWdJe43BPZlVgT6lvL/iGo8JnrncB9Da6L7nrq/+Rvj +XGjf1qODCK+LmreZWEsaLPURIoR/Ewwxb9J2zd0CaMjeTwafJo1CZvcCgYEAyCgb +aqoWvUecX8VvARfuA593Lsi50t4MEArnOXXcd1RnXoZWhbx5rgO8/ATKfXr0BK/n +h2GF9PfKzHFm/4V6e82OL7gu/kLy2u9bXN74vOvWFL5NOrOKPM7Kg+9I131kNYOw +Ivnr/VtHE5s0dY7JChYWE1F3vArrOw3T00a4CXUCgYEA0SqY+dS2LvIzW4cHCe9k +IQqsT0yYm5TFsUEr4sA3xcPfe4cV8sZb9k/QEGYb1+SWWZ+AHPV3UW5fl8kTbSNb +v4ng8i8rVVQ0ANbJO9e5CUrepein2MPL0AkOATR8M7t7dGGpvYV0cFk8ZrFx0oId +U0PgYDotF/iueBWlbsOM430CgYEAqYI95dFyPI5/AiSkY5queeb8+mQH62sdcCCr +vd/w/CZA/K5sbAo4SoTj8dLk4evU6HtIa0DOP63y071eaxvRpTNqLUOgmLh+D6gS +Cc7TfLuFrD+WDBatBd5jZ+SoHccVrLR/4L8jeodo5FPW05A+9gnKXEXsTxY4LOUC +9bS4e1kCgYAqVXZh63JsMwoaxCYmQ66eJojKa47VNrOeIZDZvd2BPVf30glBOT41 +gBoDG3WMPZoQj9pb7uMcrnvs4APj2FIhMU8U15LcPAj59cD6S6rWnAxO8NFK7HQG +4Jxg3JNNf8ErQoCHb1B3oVdXJkmbJkARoDpBKmTCgKtP8ADYLmVPQw== +-----END RSA PRIVATE KEY----- + _end_of_pem_ + + TEST_KEY_DSA256 = OpenSSL::PKey::DSA.new <<-_end_of_pem_ +-----BEGIN DSA PRIVATE KEY----- +MIH3AgEAAkEAhk2libbY2a8y2Pt21+YPYGZeW6wzaW2yfj5oiClXro9XMR7XWLkE +9B7XxLNFCS2gmCCdMsMW1HulaHtLFQmB2wIVAM43JZrcgpu6ajZ01VkLc93gu/Ed +AkAOhujZrrKV5CzBKutKLb0GVyVWmdC7InoNSMZEeGU72rT96IjM59YzoqmD0pGM +3I1o4cGqg1D1DfM1rQlnN1eSAkBq6xXfEDwJ1mLNxF6q8Zm/ugFYWR5xcX/3wFiT +b4+EjHP/DbNh9Vm5wcfnDBJ1zKvrMEf2xqngYdrV/3CiGJeKAhRvL57QvJZcQGvn +ISNX5cMzFHRW3Q== +-----END DSA PRIVATE KEY----- + _end_of_pem_ + + TEST_KEY_DSA512 = OpenSSL::PKey::DSA.new <<-_end_of_pem_ +-----BEGIN DSA PRIVATE KEY----- +MIH4AgEAAkEA5lB4GvEwjrsMlGDqGsxrbqeFRh6o9OWt6FgTYiEEHaOYhkIxv0Ok +RZPDNwOG997mDjBnvDJ1i56OmS3MbTnovwIVAJgub/aDrSDB4DZGH7UyarcaGy6D +AkB9HdFw/3td8K4l1FZHv7TCZeJ3ZLb7dF3TWoGUP003RCqoji3/lHdKoVdTQNuR +S/m6DlCwhjRjiQ/lBRgCLCcaAkEAjN891JBjzpMj4bWgsACmMggFf57DS0Ti+5++ +Q1VB8qkJN7rA7/2HrCR3gTsWNb1YhAsnFsoeRscC+LxXoXi9OAIUBG98h4tilg6S +55jreJD3Se3slps= +-----END DSA PRIVATE KEY----- + _end_of_pem_ + +if defined?(OpenSSL::PKey::EC) + + TEST_KEY_EC_P256V1 = OpenSSL::PKey::EC.new <<-_end_of_pem_ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIID49FDqcf1O1eO8saTgG70UbXQw9Fqwseliit2aWhH1oAoGCCqGSM49 +AwEHoUQDQgAEFglk2c+oVUIKQ64eZG9bhLNPWB7lSZ/ArK41eGy5wAzU/0G51Xtt +CeBUl+MahZtn9fO1JKdF4qJmS39dXnpENg== +-----END EC PRIVATE KEY----- + _end_of_pem_ + +end + + TEST_KEY_DH512_PUB = OpenSSL::PKey::DH.new <<-_end_of_pem_ +-----BEGIN DH PARAMETERS----- +MEYCQQDmWXGPqk76sKw/edIOdhAQD4XzjJ+AR/PTk2qzaGs+u4oND2yU5D2NN4wr +aPgwHyJBiK1/ebK3tYcrSKrOoRyrAgEC +-----END DH PARAMETERS----- + _end_of_pem_ + + TEST_KEY_DH1024 = OpenSSL::PKey::DH.new <<-_end_of_pem_ +-----BEGIN DH PARAMETERS----- +MIGHAoGBAKnKQ8MNK6nYZzLrrcuTsLxuiJGXoOO5gT+tljOTbHBuiktdMTITzIY0 +pFxIvjG05D7HoBZQfrR0c92NGWPkAiCkhQKB8JCbPVzwNLDy6DZ0pmofDKrEsYHG +AQjjxMXhwULlmuR/K+WwlaZPiLIBYalLAZQ7ZbOPeVkJ8ePao0eLAgEC +-----END DH PARAMETERS----- + _end_of_pem_ + + TEST_KEY_DH1024.priv_key = OpenSSL::BN.new("48561834C67E65FFD2A9B47F41E5E78FDC95C387428FDB1E4B0188B64D1643C3A8D3455B945B7E8C4D166010C7C2CE23BFB9BEF43D0348FE7FA5284B0225E7FE1537546D114E3D8A4411B9B9351AB451E1A358F50ED61B1F00DA29336EEBBD649980AC86D76AF8BBB065298C2052672EEF3EF13AB47A15275FC2836F3AC74CEA", 16) + + DSA_SIGNATURE_DIGEST = OpenSSL::OPENSSL_VERSION_NUMBER > 0x10000000 ? + OpenSSL::Digest::SHA1 : + OpenSSL::Digest::DSS1 + + module_function + + def issue_cert(dn, key, serial, not_before, not_after, extensions, + issuer, issuer_key, digest) + cert = OpenSSL::X509::Certificate.new + issuer = cert unless issuer + issuer_key = key unless issuer_key + cert.version = 2 + cert.serial = serial + cert.subject = dn + cert.issuer = issuer.subject + cert.public_key = key.public_key + cert.not_before = not_before + cert.not_after = not_after + ef = OpenSSL::X509::ExtensionFactory.new + ef.subject_certificate = cert + ef.issuer_certificate = issuer + extensions.each{|oid, value, critical| + cert.add_extension(ef.create_extension(oid, value, critical)) + } + cert.sign(issuer_key, digest) + cert + end + + def issue_crl(revoke_info, serial, lastup, nextup, extensions, + issuer, issuer_key, digest) + crl = OpenSSL::X509::CRL.new + crl.issuer = issuer.subject + crl.version = 1 + crl.last_update = lastup + crl.next_update = nextup + revoke_info.each{|rserial, time, reason_code| + revoked = OpenSSL::X509::Revoked.new + revoked.serial = rserial + revoked.time = time + enum = OpenSSL::ASN1::Enumerated(reason_code) + ext = OpenSSL::X509::Extension.new("CRLReason", enum) + revoked.add_extension(ext) + crl.add_revoked(revoked) + } + ef = OpenSSL::X509::ExtensionFactory.new + ef.issuer_certificate = issuer + ef.crl = crl + crlnum = OpenSSL::ASN1::Integer(serial) + crl.add_extension(OpenSSL::X509::Extension.new("crlNumber", crlnum)) + extensions.each{|oid, value, critical| + crl.add_extension(ef.create_extension(oid, value, critical)) + } + crl.sign(issuer_key, digest) + crl + end + + def get_subject_key_id(cert) + asn1_cert = OpenSSL::ASN1.decode(cert) + tbscert = asn1_cert.value[0] + pkinfo = tbscert.value[6] + publickey = pkinfo.value[1] + pkvalue = publickey.value + OpenSSL::Digest::SHA1.hexdigest(pkvalue).scan(/../).join(":").upcase + end + + def silent + begin + back, $VERBOSE = $VERBOSE, nil + yield + ensure + $VERBOSE = back + end + end + + class OpenSSL::SSLTestCase < Test::Unit::TestCase + RUBY = EnvUtil.rubybin + SSL_SERVER = File.join(File.dirname(__FILE__), "ssl_server.rb") + PORT = 20443 + ITERATIONS = ($0 == __FILE__) ? 100 : 10 + + def setup + @ca_key = OpenSSL::TestUtils::TEST_KEY_RSA2048 + @svr_key = OpenSSL::TestUtils::TEST_KEY_RSA1024 + @cli_key = OpenSSL::TestUtils::TEST_KEY_DSA256 + @ca = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=CA") + @svr = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=localhost") + @cli = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=localhost") + now = Time.at(Time.now.to_i) + ca_exts = [ + ["basicConstraints","CA:TRUE",true], + ["keyUsage","cRLSign,keyCertSign",true], + ] + ee_exts = [ + ["keyUsage","keyEncipherment,digitalSignature",true], + ] + @ca_cert = issue_cert(@ca, @ca_key, 1, now, now+3600, ca_exts, nil, nil, OpenSSL::Digest::SHA1.new) + @svr_cert = issue_cert(@svr, @svr_key, 2, now, now+1800, ee_exts, @ca_cert, @ca_key, OpenSSL::Digest::SHA1.new) + @cli_cert = issue_cert(@cli, @cli_key, 3, now, now+1800, ee_exts, @ca_cert, @ca_key, OpenSSL::Digest::SHA1.new) + @server = nil + end + + def teardown + end + + def issue_cert(*arg) + OpenSSL::TestUtils.issue_cert(*arg) + end + + def issue_crl(*arg) + OpenSSL::TestUtils.issue_crl(*arg) + end + + def readwrite_loop(ctx, ssl) + while line = ssl.gets + if line =~ /^STARTTLS$/ + ssl.accept + next + end + ssl.write(line) + end + rescue OpenSSL::SSL::SSLError + rescue IOError + ensure + ssl.close rescue nil + end + + def server_loop(ctx, ssls, server_proc, threads) + loop do + ssl = nil + begin + ssl = ssls.accept + rescue OpenSSL::SSL::SSLError + retry + end + + th = Thread.start do + Thread.current.abort_on_exception = true + server_proc.call(ctx, ssl) + end + threads << th + end + rescue Errno::EBADF, IOError, Errno::EINVAL, Errno::ECONNABORTED, Errno::ENOTSOCK, Errno::ECONNRESET + end + + def start_server(port0, verify_mode, start_immediately, args = {}, &block) + ctx_proc = args[:ctx_proc] + server_proc = args[:server_proc] + server_proc ||= method(:readwrite_loop) + threads = [] + + store = OpenSSL::X509::Store.new + store.add_cert(@ca_cert) + store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT + ctx = OpenSSL::SSL::SSLContext.new + ctx.cert_store = store + #ctx.extra_chain_cert = [ ca_cert ] + ctx.cert = @svr_cert + ctx.key = @svr_key + ctx.tmp_dh_callback = proc { OpenSSL::TestUtils::TEST_KEY_DH1024 } + ctx.verify_mode = verify_mode + ctx_proc.call(ctx) if ctx_proc + + Socket.do_not_reverse_lookup = true + tcps = nil + port = port0 + begin + tcps = TCPServer.new("127.0.0.1", port) + rescue Errno::EADDRINUSE + port += 1 + retry + end + + ssls = OpenSSL::SSL::SSLServer.new(tcps, ctx) + ssls.start_immediately = start_immediately + + begin + server = Thread.new do + Thread.current.abort_on_exception = true + server_loop(ctx, ssls, server_proc, threads) + end + + $stderr.printf("%s started: pid=%d port=%d\n", SSL_SERVER, $$, port) if $DEBUG + + block.call(server, port.to_i) + ensure + begin + begin + tcps.shutdown + rescue Errno::ENOTCONN + # when `Errno::ENOTCONN: Socket is not connected' on some platforms, + # call #close instead of #shutdown. + tcps.close + tcps = nil + end if (tcps) + if (server) + server.join(5) + if server.alive? + server.join + flunk("TCPServer was closed and SSLServer is still alive") unless $! + end + end + ensure + tcps.close if (tcps) + end + end + ensure + threads.each {|th| + th.join + } + end + + def starttls(ssl) + ssl.puts("STARTTLS") + sleep 1 # When this line is eliminated, process on Cygwin blocks + # forever at ssl.connect. But I don't know why it does. + ssl.connect + end + end + +end if defined?(OpenSSL) |