From fcbf63e62c627deae76c1b8cb8c0876c536ed811 Mon Sep 17 00:00:00 2001 From: Jari Vetoniemi Date: Mon, 16 Mar 2020 18:49:26 +0900 Subject: Fresh start --- jni/ruby/sample/openssl/c_rehash.rb | 174 +++++++++++++++++++++++++++++++++ jni/ruby/sample/openssl/cert2text.rb | 23 +++++ jni/ruby/sample/openssl/certstore.rb | 161 ++++++++++++++++++++++++++++++ jni/ruby/sample/openssl/cipher.rb | 54 ++++++++++ jni/ruby/sample/openssl/crlstore.rb | 122 +++++++++++++++++++++++ jni/ruby/sample/openssl/echo_cli.rb | 44 +++++++++ jni/ruby/sample/openssl/echo_svr.rb | 65 ++++++++++++ jni/ruby/sample/openssl/gen_csr.rb | 51 ++++++++++ jni/ruby/sample/openssl/smime_read.rb | 23 +++++ jni/ruby/sample/openssl/smime_write.rb | 23 +++++ jni/ruby/sample/openssl/wget.rb | 34 +++++++ 11 files changed, 774 insertions(+) create mode 100644 jni/ruby/sample/openssl/c_rehash.rb create mode 100644 jni/ruby/sample/openssl/cert2text.rb create mode 100644 jni/ruby/sample/openssl/certstore.rb create mode 100644 jni/ruby/sample/openssl/cipher.rb create mode 100644 jni/ruby/sample/openssl/crlstore.rb create mode 100644 jni/ruby/sample/openssl/echo_cli.rb create mode 100644 jni/ruby/sample/openssl/echo_svr.rb create mode 100644 jni/ruby/sample/openssl/gen_csr.rb create mode 100644 jni/ruby/sample/openssl/smime_read.rb create mode 100644 jni/ruby/sample/openssl/smime_write.rb create mode 100644 jni/ruby/sample/openssl/wget.rb (limited to 'jni/ruby/sample/openssl') diff --git a/jni/ruby/sample/openssl/c_rehash.rb b/jni/ruby/sample/openssl/c_rehash.rb new file mode 100644 index 0000000..cd6c9d5 --- /dev/null +++ b/jni/ruby/sample/openssl/c_rehash.rb @@ -0,0 +1,174 @@ +#!/usr/bin/env ruby + +require 'openssl' +require 'digest/md5' + +class CHashDir + include Enumerable + + def initialize(dirpath) + @dirpath = dirpath + @fingerprint_cache = @cert_cache = @crl_cache = nil + end + + def hash_dir(silent = false) + # ToDo: Should lock the directory... + @silent = silent + @fingerprint_cache = Hash.new + @cert_cache = Hash.new + @crl_cache = Hash.new + do_hash_dir + end + + def get_certs(name = nil) + if name + @cert_cache[hash_name(name)] + else + @cert_cache.values.flatten + end + end + + def get_crls(name = nil) + if name + @crl_cache[hash_name(name)] + else + @crl_cache.values.flatten + end + end + + def delete_crl(crl) + File.unlink(crl_filename(crl)) + hash_dir(true) + end + + def add_crl(crl) + File.open(crl_filename(crl), "w") do |f| + f << crl.to_pem + end + hash_dir(true) + end + + def load_pem_file(filepath) + str = File.read(filepath) + begin + OpenSSL::X509::Certificate.new(str) + rescue + begin + OpenSSL::X509::CRL.new(str) + rescue + begin + OpenSSL::X509::Request.new(str) + rescue + nil + end + end + end + end + +private + + def crl_filename(crl) + path(hash_name(crl.issuer)) + '.pem' + end + + def do_hash_dir + Dir.chdir(@dirpath) do + delete_symlink + Dir.glob('*.pem') do |pemfile| + cert = load_pem_file(pemfile) + case cert + when OpenSSL::X509::Certificate + link_hash_cert(pemfile, cert) + when OpenSSL::X509::CRL + link_hash_crl(pemfile, cert) + else + STDERR.puts("WARNING: #{pemfile} does not contain a certificate or CRL: skipping") unless @silent + end + end + end + end + + def delete_symlink + Dir.entries(".").each do |entry| + next unless /^[\da-f]+\.r{0,1}\d+$/ =~ entry + File.unlink(entry) if FileTest.symlink?(entry) + end + end + + def link_hash_cert(org_filename, cert) + name_hash = hash_name(cert.subject) + fingerprint = fingerprint(cert.to_der) + filepath = link_hash(org_filename, name_hash, fingerprint) { |idx| + "#{name_hash}.#{idx}" + } + unless filepath + unless @silent + STDERR.puts("WARNING: Skipping duplicate certificate #{org_filename}") + end + else + (@cert_cache[name_hash] ||= []) << path(filepath) + end + end + + def link_hash_crl(org_filename, crl) + name_hash = hash_name(crl.issuer) + fingerprint = fingerprint(crl.to_der) + filepath = link_hash(org_filename, name_hash, fingerprint) { |idx| + "#{name_hash}.r#{idx}" + } + unless filepath + unless @silent + STDERR.puts("WARNING: Skipping duplicate CRL #{org_filename}") + end + else + (@crl_cache[name_hash] ||= []) << path(filepath) + end + end + + def link_hash(org_filename, name, fingerprint) + idx = 0 + filepath = nil + while true + filepath = yield(idx) + break unless FileTest.symlink?(filepath) or FileTest.exist?(filepath) + if @fingerprint_cache[filepath] == fingerprint + return false + end + idx += 1 + end + STDOUT.puts("#{org_filename} => #{filepath}") unless @silent + symlink(org_filename, filepath) + @fingerprint_cache[filepath] = fingerprint + filepath + end + + def symlink(from, to) + begin + File.symlink(from, to) + rescue + File.open(to, "w") do |f| + f << File.read(from) + end + end + end + + def path(filename) + File.join(@dirpath, filename) + end + + def hash_name(name) + sprintf("%x", name.hash) + end + + def fingerprint(der) + Digest::MD5.hexdigest(der).upcase + end +end + +if $0 == __FILE__ + dirlist = ARGV + dirlist << '/usr/ssl/certs' if dirlist.empty? + dirlist.each do |dir| + CHashDir.new(dir).hash_dir + end +end diff --git a/jni/ruby/sample/openssl/cert2text.rb b/jni/ruby/sample/openssl/cert2text.rb new file mode 100644 index 0000000..50da224 --- /dev/null +++ b/jni/ruby/sample/openssl/cert2text.rb @@ -0,0 +1,23 @@ +#!/usr/bin/env ruby + +require 'openssl' +include OpenSSL::X509 + +def cert2text(cert_str) + [Certificate, CRL, Request].each do |klass| + begin + puts klass.new(cert_str).to_text + return + rescue + end + end + raise ArgumentError.new('Unknown format.') +end + +if ARGV.empty? + cert2text(STDIN.read) +else + ARGV.each do |file| + cert2text(File.read(file)) + end +end diff --git a/jni/ruby/sample/openssl/certstore.rb b/jni/ruby/sample/openssl/certstore.rb new file mode 100644 index 0000000..c6e8f81 --- /dev/null +++ b/jni/ruby/sample/openssl/certstore.rb @@ -0,0 +1,161 @@ +require 'c_rehash' +require 'crlstore' + + +class CertStore + include OpenSSL + include X509 + + attr_reader :self_signed_ca + attr_reader :other_ca + attr_reader :ee + attr_reader :crl + attr_reader :request + + def initialize(certs_dir) + @certs_dir = certs_dir + @c_store = CHashDir.new(@certs_dir) + @c_store.hash_dir(true) + @crl_store = CrlStore.new(@c_store) + @x509store = Store.new + @self_signed_ca = @other_ca = @ee = @crl = nil + + # Uncomment this line to let OpenSSL to check CRL for each certs. + # @x509store.flags = V_FLAG_CRL_CHECK | V_FLAG_CRL_CHECK_ALL + + add_path + scan_certs + end + + def generate_cert(filename) + @c_store.load_pem_file(filename) + end + + def verify(cert) + error, crl_map = do_verify(cert) + if error + [[false, cert, crl_map[cert.subject], error]] + else + @x509store.chain.collect { |c| [true, c, crl_map[c.subject], nil] } + end + end + + def match_cert(cert1, cert2) + (cert1.issuer.cmp(cert2.issuer) == 0) and cert1.serial == cert2.serial + end + + def is_ca?(cert) + case guess_cert_type(cert) + when CERT_TYPE_SELF_SIGNED + true + when CERT_TYPE_OTHER + true + else + false + end + end + + def scan_certs + @self_signed_ca = [] + @other_ca = [] + @ee = [] + @crl = [] + @request = [] + load_certs + end + +private + + def add_path + @x509store.add_path(@certs_dir) + end + + def do_verify(cert) + error_map = {} + crl_map = {} + result = @x509store.verify(cert) do |ok, ctx| + cert = ctx.current_cert + if ctx.current_crl + crl_map[cert.subject] = true + end + if ok + if !ctx.current_crl + if crl = @crl_store.find_crl(cert) + crl_map[cert.subject] = true + if crl.revoked.find { |revoked| revoked.serial == cert.serial } + ok = false + error_string = 'certification revoked' + end + end + end + end + error_map[cert.subject] = error_string if error_string + ok + end + error = if result + nil + else + error_map[cert.subject] || @x509store.error_string + end + return error, crl_map + end + + def load_certs + @c_store.get_certs.each do |certfile| + cert = generate_cert(certfile) + case guess_cert_type(cert) + when CERT_TYPE_SELF_SIGNED + @self_signed_ca << cert + when CERT_TYPE_OTHER + @other_ca << cert + when CERT_TYPE_EE + @ee << cert + else + raise "Unknown cert type." + end + end + @c_store.get_crls.each do |crlfile| + @crl << generate_cert(crlfile) + end + end + + CERT_TYPE_SELF_SIGNED = 0 + CERT_TYPE_OTHER = 1 + CERT_TYPE_EE = 2 + def guess_cert_type(cert) + ca = self_signed = is_cert_self_signed(cert) + cert.extensions.each do |ext| + # Ignores criticality of extensions. It's 'guess'ing. + case ext.oid + when 'basicConstraints' + /CA:(TRUE|FALSE), pathlen:(\d+)/ =~ ext.value + ca = ($1 == 'TRUE') unless ca + when 'keyUsage' + usage = ext.value.split(/\s*,\s*/) + ca = usage.include?('Certificate Sign') unless ca + when 'nsCertType' + usage = ext.value.split(/\s*,\s*/) + ca = usage.include?('SSL CA') unless ca + end + end + if ca + if self_signed + CERT_TYPE_SELF_SIGNED + else + CERT_TYPE_OTHER + end + else + CERT_TYPE_EE + end + end + + def is_cert_self_signed(cert) + # cert.subject.cmp(cert.issuer) == 0 + cert.subject.to_s == cert.issuer.to_s + end +end + + +if $0 == __FILE__ + c = CertStore.new("trust_certs") +end diff --git a/jni/ruby/sample/openssl/cipher.rb b/jni/ruby/sample/openssl/cipher.rb new file mode 100644 index 0000000..58b10d6 --- /dev/null +++ b/jni/ruby/sample/openssl/cipher.rb @@ -0,0 +1,54 @@ +#!/usr/bin/env ruby +require 'openssl' + +def crypt_by_password(alg, pass, salt, text) + puts "--Setup--" + puts %(cipher alg: "#{alg}") + puts %(plain text: "#{text}") + puts %(password: "#{pass}") + puts %(salt: "#{salt}") + puts + + puts "--Encrypting--" + enc = OpenSSL::Cipher::Cipher.new(alg) + enc.encrypt + enc.pkcs5_keyivgen(pass, salt) + cipher = enc.update(text) + cipher << enc.final + puts %(encrypted text: #{cipher.inspect}) + puts + + puts "--Decrypting--" + dec = OpenSSL::Cipher::Cipher.new(alg) + dec.decrypt + dec.pkcs5_keyivgen(pass, salt) + plain = dec.update(cipher) + plain << dec.final + puts %(decrypted text: "#{plain}") + puts +end + +def ciphers + ciphers = OpenSSL::Cipher.ciphers.sort + ciphers.each{|i| + if i.upcase != i && ciphers.include?(i.upcase) + ciphers.delete(i) + end + } + return ciphers +end + +puts "Supported ciphers in #{OpenSSL::OPENSSL_VERSION}:" +ciphers.each_with_index{|name, i| + printf("%-15s", name) + puts if (i + 1) % 5 == 0 +} +puts +puts + +alg = ARGV.shift || ciphers.first +pass = "secret password" +salt = "8 octets" # or nil +text = "abcdefghijklmnopqrstuvwxyz" + +crypt_by_password(alg, pass, salt, text) diff --git a/jni/ruby/sample/openssl/crlstore.rb b/jni/ruby/sample/openssl/crlstore.rb new file mode 100644 index 0000000..e3a5925 --- /dev/null +++ b/jni/ruby/sample/openssl/crlstore.rb @@ -0,0 +1,122 @@ +begin + require 'http-access2' +rescue LoadError + STDERR.puts("Cannot load http-access2. CRL might not be fetched.") +end +require 'c_rehash' + + +class CrlStore + def initialize(c_store) + @c_store = c_store + @c_store.hash_dir(true) + end + + def find_crl(cert) + do_find_crl(cert) + end + +private + + def do_find_crl(cert) + unless ca = find_ca(cert) + return nil + end + unless crlfiles = @c_store.get_crls(ca.subject) + if crl = renew_crl(cert, ca) + @c_store.add_crl(crl) + return crl + end + return nil + end + crlfiles.each do |crlfile| + next unless crl = load_crl(crlfile) + if crl.next_update < Time.now + if new_crl = renew_crl(cert, ca) + @c_store.delete_crl(crl) + @c_store.add_crl(new_crl) + crl = new_crl + end + end + if check_valid(crl, ca) + return crl + end + end + nil + end + + def find_ca(cert) + @c_store.get_certs(cert.issuer).each do |cafile| + ca = load_cert(cafile) + if cert.verify(ca.public_key) + return ca + end + end + nil + end + + def fetch(location) + if /\AURI:(.*)\z/ =~ location + begin + c = HTTPAccess2::Client.new(ENV['http_proxy'] || ENV['HTTP_PROXY']) + c.get_content($1) + rescue NameError, StandardError + nil + end + else + nil + end + end + + def load_cert(certfile) + load_cert_str(File.read(certfile)) + end + + def load_crl(crlfile) + load_crl_str(File.read(crlfile)) + end + + def load_cert_str(cert_str) + OpenSSL::X509::Certificate.new(cert_str) + end + + def load_crl_str(crl_str) + OpenSSL::X509::CRL.new(crl_str) + end + + def check_valid(crl, ca) + unless crl.verify(ca.public_key) + return false + end + crl.last_update <= Time.now + end + + RE_CDP = /\AcrlDistributionPoints\z/ + def get_cdp(cert) + if cdp_ext = cert.extensions.find { |ext| RE_CDP =~ ext.oid } + cdp_ext.value.chomp + else + false + end + end + + def renew_crl(cert, ca) + if cdp = get_cdp(cert) + if new_crl_str = fetch(cdp) + new_crl = load_crl_str(new_crl_str) + if check_valid(new_crl, ca) + return new_crl + end + end + end + false + end +end + +if $0 == __FILE__ + dir = "trust_certs" + c_store = CHashDir.new(dir) + s = CrlStore.new(c_store) + c = OpenSSL::X509::Certificate.new(File.read("cert_store/google_codesign.pem")) + p s.find_crl(c) +end diff --git a/jni/ruby/sample/openssl/echo_cli.rb b/jni/ruby/sample/openssl/echo_cli.rb new file mode 100644 index 0000000..069a21e --- /dev/null +++ b/jni/ruby/sample/openssl/echo_cli.rb @@ -0,0 +1,44 @@ +#!/usr/bin/env ruby + +require 'socket' +require 'openssl' +require 'optparse' + +options = ARGV.getopts("p:c:k:C:") + +host = ARGV[0] || "localhost" +port = options["p"] || "2000" +cert_file = options["c"] +key_file = options["k"] +ca_path = options["C"] + +ctx = OpenSSL::SSL::SSLContext.new() +if cert_file && key_file + ctx.cert = OpenSSL::X509::Certificate.new(File::read(cert_file)) + ctx.key = OpenSSL::PKey::RSA.new(File::read(key_file)) +end +if ca_path + ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER + ctx.ca_path = ca_path +else + $stderr.puts "!!! WARNING: PEER CERTIFICATE WON'T BE VERIFIED !!!" +end + +s = TCPSocket.new(host, port) +ssl = OpenSSL::SSL::SSLSocket.new(s, ctx) +ssl.connect # start SSL session +p ssl.peer_cert +errors = Hash.new +OpenSSL::X509.constants.grep(/^V_(ERR_|OK)/).each do |name| + errors[OpenSSL::X509.const_get(name)] = name +end +p errors[ssl.verify_result] + +ssl.sync_close = true # if true the underlying socket will be + # closed in SSLSocket#close. (default: false) +while line = $stdin.gets + ssl.write line + puts ssl.gets.inspect +end + +ssl.close diff --git a/jni/ruby/sample/openssl/echo_svr.rb b/jni/ruby/sample/openssl/echo_svr.rb new file mode 100644 index 0000000..719de6b --- /dev/null +++ b/jni/ruby/sample/openssl/echo_svr.rb @@ -0,0 +1,65 @@ +#!/usr/bin/env ruby + +require 'socket' +require 'openssl' +require 'optparse' + +options = ARGV.getopts("p:c:k:C:") + +port = options["p"] || "2000" +cert_file = options["c"] +key_file = options["k"] +ca_path = options["C"] + +if cert_file && key_file + cert = OpenSSL::X509::Certificate.new(File::read(cert_file)) + key = OpenSSL::PKey::RSA.new(File::read(key_file)) +else + key = OpenSSL::PKey::RSA.new(512){ print "." } + puts + cert = OpenSSL::X509::Certificate.new + cert.version = 2 + cert.serial = 0 + name = OpenSSL::X509::Name.new([["C","JP"],["O","TEST"],["CN","localhost"]]) + cert.subject = name + cert.issuer = name + cert.not_before = Time.now + cert.not_after = Time.now + 3600 + cert.public_key = key.public_key + ef = OpenSSL::X509::ExtensionFactory.new(nil,cert) + cert.extensions = [ + ef.create_extension("basicConstraints","CA:FALSE"), + ef.create_extension("subjectKeyIdentifier","hash"), + ef.create_extension("extendedKeyUsage","serverAuth"), + ef.create_extension("keyUsage", + "keyEncipherment,dataEncipherment,digitalSignature") + ] + ef.issuer_certificate = cert + cert.add_extension ef.create_extension("authorityKeyIdentifier", + "keyid:always,issuer:always") + cert.sign(key, OpenSSL::Digest::SHA1.new) +end + +ctx = OpenSSL::SSL::SSLContext.new() +ctx.key = key +ctx.cert = cert +if ca_path + ctx.verify_mode = + OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT + ctx.ca_path = ca_path +else + $stderr.puts "!!! WARNING: PEER CERTIFICATE WON'T BE VERIFIED !!!" +end + +tcps = TCPServer.new(port) +ssls = OpenSSL::SSL::SSLServer.new(tcps, ctx) +loop do + ns = ssls.accept + puts "connected from #{ns.peeraddr}" + while line = ns.gets + puts line.inspect + ns.write line + end + puts "connection closed" + ns.close +end diff --git a/jni/ruby/sample/openssl/gen_csr.rb b/jni/ruby/sample/openssl/gen_csr.rb new file mode 100644 index 0000000..4228707 --- /dev/null +++ b/jni/ruby/sample/openssl/gen_csr.rb @@ -0,0 +1,51 @@ +#!/usr/bin/env ruby + +require 'optparse' +require 'openssl' + +include OpenSSL + +def usage + myname = File::basename($0) + $stderr.puts <