├── .gitignore ├── mruby_version.lock ├── .travis.yml ├── mrblib ├── acme │ ├── client │ │ ├── resources.rb │ │ ├── resources │ │ │ ├── challenges.rb │ │ │ ├── challenges │ │ │ │ ├── dns01.rb │ │ │ │ ├── tls_sni01.rb │ │ │ │ ├── http01.rb │ │ │ │ └── base.rb │ │ │ ├── registration.rb │ │ │ └── authorization.rb │ │ ├── version.rb │ │ ├── certificate.rb │ │ ├── error.rb │ │ ├── crypto.rb │ │ ├── self_sign_certificate.rb │ │ └── certificate_request.rb │ └── client.rb ├── base64.rb ├── openssl.rb └── custom_http_request.rb ├── src ├── ossl_config.h ├── ossl_asn1.h ├── ossl_bio.h ├── ossl_digest.h ├── ossl_x509.h ├── ossl_x509.c ├── acme_client.c ├── ossl_bio.c ├── ossl_x509attr.h ├── ossl_config.c ├── ossl_bn.h ├── ossl_x509crl.c ├── ossl.c ├── ossl_x509name.h ├── ossl_pkey.h ├── ossl_x509cert.c ├── ossl_x509name.c ├── ossl_digest.c ├── ossl_bn.c ├── ossl_pkey.c ├── ossl_x509attr.c ├── ossl.h ├── ossl_pkey_rsa.h ├── ossl_x509req.c ├── ossl_x509ext.c ├── ossl_pkey_rsa.c └── ossl_asn1.c ├── mruby-acme-client.gem ├── test └── openssl_digest.rb ├── example ├── register_client.rb ├── digest.rb └── auth_domain.rb ├── mrbgem.rake ├── Rakefile └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | mruby/ 2 | -------------------------------------------------------------------------------- /mruby_version.lock: -------------------------------------------------------------------------------- 1 | 7c91efc1ffda769a5f1a872c646c82b00698f1b8 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | cache: bundler 3 | rvm: 4 | - 2.6.4 5 | -------------------------------------------------------------------------------- /mrblib/acme/client/resources.rb: -------------------------------------------------------------------------------- 1 | module Acme::Client::Resources; end 2 | -------------------------------------------------------------------------------- /mrblib/acme/client/resources/challenges.rb: -------------------------------------------------------------------------------- 1 | module Acme::Client::Resources::Challenges; end 2 | -------------------------------------------------------------------------------- /mrblib/acme/client/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Acme 4 | class Client 5 | VERSION = '0.5.5'.freeze 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /mrblib/base64.rb: -------------------------------------------------------------------------------- 1 | module Base64 2 | def self.urlsafe_base64(data) 3 | encode(data).to_s.gsub(/\+/, "-").gsub(/\//, "_").sub(/[\s=]*\z/, '') 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /src/ossl_config.h: -------------------------------------------------------------------------------- 1 | 2 | //LICENSE: https://github.com/ruby/openssl/blob/master/LICENSE.txt 3 | CONF *GetConfigPtr(mrb_state *mrb, VALUE obj); 4 | void Init_ossl_config(mrb_state *mrb); 5 | -------------------------------------------------------------------------------- /src/ossl_asn1.h: -------------------------------------------------------------------------------- 1 | // LICENCE: https://github.com/ruby/openssl/blob/master/LICENSE.txt 2 | ASN1_TYPE *ossl_asn1_get_asn1type(mrb_state *mrb, VALUE obj); 3 | extern struct RClass *eASN1Error; 4 | extern struct RClass *cASN1Data; 5 | -------------------------------------------------------------------------------- /mruby-acme-client.gem: -------------------------------------------------------------------------------- 1 | name: mruby-acme-client 2 | description: acme-client 3 | author: pyama86 4 | website: https://github.com/pyama86/mruby-acme-client 5 | protocol: git 6 | repository: https://github.com/pyama86/mruby-acme-client.git 7 | -------------------------------------------------------------------------------- /src/ossl_bio.h: -------------------------------------------------------------------------------- 1 | 2 | //LICENSE: https://github.com/ruby/openssl/blob/master/LICENSE.txt 3 | #if !defined(_OSSL_BIO_H_) 4 | #define _OSSL_BIO_H_ 5 | 6 | BIO *ossl_obj2bio(mrb_state *mrb, mrb_value self); 7 | 8 | VALUE ossl_membio2str(mrb_state *mrb, BIO *bio); 9 | #endif 10 | -------------------------------------------------------------------------------- /src/ossl_digest.h: -------------------------------------------------------------------------------- 1 | 2 | //LICENSE: https://github.com/ruby/openssl/blob/master/LICENSE.txt 3 | #if !defined(_OSSL_DIGEST_H_) 4 | #define _OSSL_DIGEST_H_ 5 | 6 | void Init_ossl_digest(mrb_state *mrb); 7 | const EVP_MD *GetDigestPtr(mrb_state *mrb, mrb_value); 8 | #endif /* _OSSL_DIGEST_H_ */ 9 | -------------------------------------------------------------------------------- /test/openssl_digest.rb: -------------------------------------------------------------------------------- 1 | assert 'OpenSSL::Digest::SHA256#digest' do 2 | # assert_equal OpenSSL::Digest::SHA256.new.digest('exmple'), "uzSDAQ9TFYzliiJ8yN/v1IT8j8sKsTmICxGA4Hc/BVI=" 3 | #assert_equal Base64.encode(OpenSSL::Digest::SHA256.new.digest()), "uzSDAQ9TFYzliiJ8yN/v1IT8j8sKsTmICxGA4Hc/BVI=" 4 | end 5 | -------------------------------------------------------------------------------- /src/ossl_x509.h: -------------------------------------------------------------------------------- 1 | 2 | //LICENSE: https://github.com/ruby/openssl/blob/master/LICENSE.txt 3 | X509 *GetX509CertPtr(mrb_state *mrb, mrb_value VALUE); 4 | void Init_ossl_x509ext(mrb_state *mrb); 5 | void Init_ossl_x509cert(mrb_state *mrb); 6 | void Init_ossl_x509crl(mrb_state *mrb); 7 | void Init_ossl_x509req(mrb_state *mrb); 8 | 9 | X509_REQ *GetX509ReqPtr(mrb_state *mrb, VALUE obj); 10 | X509_CRL *GetX509CRLPtr(mrb_state *mrb, VALUE obj); 11 | -------------------------------------------------------------------------------- /src/ossl_x509.c: -------------------------------------------------------------------------------- 1 | 2 | //LICENSE: https://github.com/ruby/openssl/blob/master/LICENSE.txt 3 | #include "ossl.h" 4 | 5 | struct RClass *mX509; 6 | 7 | void mrb_init_ossl_x509(mrb_state *mrb) 8 | { 9 | mX509 = mrb_define_module_under(mrb, mOSSL, "X509"); 10 | Init_ossl_x509attr(mrb); 11 | Init_ossl_x509name(mrb); 12 | Init_ossl_x509cert(mrb); 13 | Init_ossl_x509ext(mrb); 14 | Init_ossl_x509crl(mrb); 15 | Init_ossl_x509req(mrb); 16 | } 17 | -------------------------------------------------------------------------------- /example/register_client.rb: -------------------------------------------------------------------------------- 1 | private_key = OpenSSL::PKey::RSA.new(4096) 2 | 3 | endpoint = 'http://127.0.0.1:4000/' 4 | client = Acme::Client.new( 5 | private_key, 6 | endpoint, 7 | endpoint+"directory", 8 | { request: { open_timeout: 5, timeout: 5 } } 9 | ) 10 | 11 | registration = client.register('mailto:contact@example.com') 12 | 13 | # You may need to agree to the terms of service (that's up the to the server to require it or not but boulder does by default) 14 | registration.agree_terms 15 | -------------------------------------------------------------------------------- /mrblib/acme/client/resources/challenges/dns01.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Acme::Client::Resources::Challenges::DNS01 < Acme::Client::Resources::Challenges::Base 4 | CHALLENGE_TYPE = 'dns-01'.freeze 5 | RECORD_NAME = '_acme-challenge'.freeze 6 | RECORD_TYPE = 'TXT'.freeze 7 | 8 | def record_name 9 | RECORD_NAME 10 | end 11 | 12 | def record_type 13 | RECORD_TYPE 14 | end 15 | 16 | def record_content 17 | crypto.urlsafe_base64(crypto.digest.digest(authorization_key)) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /src/acme_client.c: -------------------------------------------------------------------------------- 1 | // LICENCE: https://github.com/ruby/openssl/blob/master/LICENSE.txt 2 | #include "ossl.h" 3 | void mrb_init_ossl(mrb_state *mrb); 4 | void mrb_init_ossl_x509(mrb_state *mrb); 5 | void mrb_init_ossl_asn1(mrb_state *mrb); 6 | #define DONE mrb_gc_arena_restore(mrb, 0) 7 | 8 | void mrb_mruby_acme_client_gem_init(mrb_state *mrb) 9 | { 10 | mrb_init_ossl(mrb); 11 | DONE; 12 | mrb_init_ossl_x509(mrb); 13 | DONE; 14 | mrb_init_ossl_asn1(mrb); 15 | DONE; 16 | } 17 | void mrb_mruby_acme_client_gem_final(mrb_state *mrb) 18 | { 19 | } 20 | -------------------------------------------------------------------------------- /mrbgem.rake: -------------------------------------------------------------------------------- 1 | require 'open3' 2 | require 'fileutils' 3 | MRuby::Gem::Specification.new('mruby-acme-client') do |spec| 4 | spec.license = 'MIT' 5 | spec.authors = 'pyama86' 6 | spec.add_dependency 'mruby-polarssl', github: 'pyama86/mruby-polarssl', branch: 'disable-base64' 7 | spec.add_dependency 'mruby-io' 8 | spec.add_dependency 'mruby-json' 9 | spec.add_dependency 'mruby-process' 10 | spec.add_dependency 'mruby-onig-regexp' 11 | spec.add_dependency 'mruby-forwardable' 12 | spec.add_dependency 'mruby-tempfile' 13 | spec.add_dependency 'mruby-pack' 14 | spec.add_dependency 'mruby-digest' 15 | spec.add_dependency 'mruby-httprequest' 16 | spec.add_dependency 'mruby-base64' 17 | end 18 | 19 | -------------------------------------------------------------------------------- /mrblib/acme/client/resources/challenges/tls_sni01.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Acme::Client::Resources::Challenges::TLSSNI01 < Acme::Client::Resources::Challenges::Base 4 | CHALLENGE_TYPE = 'tls-sni-01'.freeze 5 | 6 | def hostname 7 | digest = crypto.digest.hexdigest(authorization_key) 8 | "#{digest[0..31]}.#{digest[32..64]}.acme.invalid" 9 | end 10 | 11 | def certificate 12 | self_sign_certificate.certificate 13 | end 14 | 15 | def private_key 16 | self_sign_certificate.private_key 17 | end 18 | 19 | private 20 | 21 | def self_sign_certificate 22 | @self_sign_certificate ||= Acme::Client::SelfSignCertificate.new(subject_alt_names: [hostname]) 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /mrblib/acme/client/certificate.rb: -------------------------------------------------------------------------------- 1 | class Acme::Client::Certificate 2 | extend Forwardable 3 | 4 | attr_reader :x509, :x509_chain, :request, :private_key, :url 5 | 6 | def_delegators :@x509, :to_pem, :to_der 7 | 8 | def initialize(certificate, url, chain, request) 9 | @x509 = certificate 10 | @url = url 11 | @x509_chain = chain 12 | @request = request 13 | end 14 | 15 | def chain_to_pem 16 | x509_chain.map(&:to_pem).join 17 | end 18 | 19 | def x509_fullchain 20 | [x509, *x509_chain] 21 | end 22 | 23 | def fullchain_to_pem 24 | x509_fullchain.map(&:to_pem).join 25 | end 26 | 27 | def common_name 28 | x509.subject.to_a.find { |name, _, _| name == 'CN' }[1] 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /mrblib/acme/client/resources/challenges/http01.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Acme::Client::Resources::Challenges::HTTP01 < Acme::Client::Resources::Challenges::Base 4 | CHALLENGE_TYPE = 'http-01'.freeze 5 | CONTENT_TYPE = 'text/plain'.freeze 6 | 7 | def content_type 8 | CONTENT_TYPE 9 | end 10 | 11 | def file_content 12 | authorization_key 13 | end 14 | 15 | def filedir 16 | ".well-known/acme-challenge" 17 | end 18 | 19 | def filename 20 | "#{filedir}/#{token}" 21 | end 22 | 23 | def put_content(document_root) 24 | `mkdir -p #{File.join(document_root, filedir)}` 25 | 26 | File.open(File.join(document_root, filename), 'w') do |f| 27 | f.puts file_content 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /mrblib/acme/client/error.rb: -------------------------------------------------------------------------------- 1 | class Acme::Client::Error < StandardError 2 | class NotFound < Acme::Client::Error; end 3 | class BadCSR < Acme::Client::Error; end 4 | class BadNonce < Acme::Client::Error; end 5 | class Connection < Acme::Client::Error; end 6 | class Dnssec < Acme::Client::Error; end 7 | class Malformed < Acme::Client::Error; end 8 | class ServerInternal < Acme::Client::Error; end 9 | class Acme::Tls < Acme::Client::Error; end 10 | class Unauthorized < Acme::Client::Error; end 11 | class UnknownHost < Acme::Client::Error; end 12 | class Timeout < Acme::Client::Error; end 13 | class RateLimited < Acme::Client::Error; end 14 | class RejectedIdentifier < Acme::Client::Error; end 15 | class UnsupportedIdentifier < Acme::Client::Error; end 16 | end 17 | -------------------------------------------------------------------------------- /example/digest.rb: -------------------------------------------------------------------------------- 1 | json = '{"e":"AQAB","kty":"RSA","n":"1i1WQ2XU3wP7xuXZQQ6uiH7qiAeoGMLKlIW2zNudL2VgMdX1TrtO6rsOJO0IhGA_BTamqwAun4xe2Sg3PzLGx4-Kty88ea34MPrabeyI9U8u00Z43HNPOR7Qy16t-F-MuNgnrk-lHd-PHCnYKU6ReNN3dniPCaS9w1GybgK-N0IxG4zjeEEu4bVRJjuws-yr5xEhvOtNIxETw_4Q-lUeBjaEhjR3j-41z4qjreDDIiH4x-yteL2JLDLL8y5WkWVQ0AI-KT8pQgs2d0ZbZwLpfpUTSmHFhGi5R0xv7sw6fC02ElJpEYngw5V_IWDOkSFRjc5KO6gStEQkCbRzYvfgCggmJG8fJn9wjIOlqCpDZXbXKWObyTA1oterqP-ZCNJkAMdXwu6z7C4W17yem9nvSIFkJRGeOqeodRIsdKhdvA-iKLMgo8khAq9nGzyTyJI5jEgT-6oxWgY2m5PC8cCJkvW6d99IXYtTaxCBainJF8kG7vXt3KKNmnipiY_hqCBM6MBxfdIq_aZ45BO3S4h9mtKDoEkuFDmdoTkn6CDVIjt3_f7Wg3Z_g2yR-OJJJjX2GnySEQH94yef6LMFmOfk1XlpY0pl_HGqXD4WXd4LCJZcLZyPkzqxWwlPrgqittY77p6SrBSTH9QDl9yIv1iyXt2w40gx6Dw7GJrDy0RAXhs"}' 2 | 3 | puts Base64.urlsafe_base64 OpenSSL::Digest::SHA256.new.digest("hoge") 4 | puts o = OpenSSL::PKey::RSA.new(4096) 5 | puts Base64.urlsafe_base64 o.public_key.e.to_s(2) 6 | -------------------------------------------------------------------------------- /src/ossl_bio.c: -------------------------------------------------------------------------------- 1 | //LICENSE: https://github.com/ruby/openssl/blob/master/LICENSE.txt 2 | 3 | #include "ossl.h" 4 | BIO *ossl_obj2bio(mrb_state *mrb, mrb_value obj) 5 | { 6 | BIO *bio; 7 | 8 | bio = BIO_new_mem_buf(RSTRING_PTR(obj), RSTRING_LEN(obj)); 9 | if (!bio) 10 | mrb_raise(mrb, eOSSLError, NULL); 11 | 12 | return bio; 13 | } 14 | 15 | VALUE 16 | ossl_membio2str0(mrb_state *mrb, BIO *bio) 17 | { 18 | VALUE ret; 19 | BUF_MEM *buf; 20 | 21 | BIO_get_mem_ptr(bio, &buf); 22 | ret = mrb_str_new(mrb, buf->data, buf->length); 23 | 24 | return ret; 25 | } 26 | VALUE 27 | ossl_protect_membio2str(mrb_state *mrb, BIO *bio) 28 | { 29 | return ossl_membio2str0(mrb, bio); 30 | } 31 | 32 | VALUE 33 | ossl_membio2str(mrb_state *mrb, BIO *bio) 34 | { 35 | VALUE ret; 36 | int status = 0; 37 | 38 | ret = ossl_protect_membio2str(mrb, bio); 39 | BIO_free(bio); 40 | 41 | return ret; 42 | } 43 | -------------------------------------------------------------------------------- /src/ossl_x509attr.h: -------------------------------------------------------------------------------- 1 | 2 | //LICENSE: https://github.com/ruby/openssl/blob/master/LICENSE.txt 3 | #define SetX509Attr(obj, attr) do { \ 4 | if (!(attr)) { \ 5 | mrb_raise((mrb), E_RUNTIME_ERROR, "ATTR wasn't initialized!"); \ 6 | } \ 7 | DATA_PTR(obj) = attr; \ 8 | DATA_TYPE(obj) = &ossl_x509attr_type; \ 9 | } while (0) 10 | #define GetX509Attr(obj, attr) do { \ 11 | attr = DATA_PTR(obj); \ 12 | if (!(attr)) { \ 13 | mrb_raise((mrb), E_RUNTIME_ERROR, "ATTR wasn't initialized!"); \ 14 | } \ 15 | } while (0) 16 | #define SafeGetX509Attr(obj, attr) do { \ 17 | OSSL_Check_Kind((mrb), (obj), cX509Attr); \ 18 | GetX509Attr((mrb), (obj), (attr)); \ 19 | } while (0) 20 | void Init_ossl_x509attr(mrb_state *mrb); 21 | X509_ATTRIBUTE *DupX509AttrPtr(mrb_state *mrb, VALUE obj); 22 | -------------------------------------------------------------------------------- /mrblib/acme/client/resources/registration.rb: -------------------------------------------------------------------------------- 1 | class Acme::Client::Resources::Registration 2 | attr_reader :id, :key, :contact, :uri, :next_uri, :recover_uri, :term_of_service_uri 3 | 4 | def initialize(client, response) 5 | @client = client 6 | @uri = response.headers['location'] 7 | assign_links(response.headers['link']) 8 | assign_attributes(response.body) 9 | end 10 | 11 | def get_terms 12 | return unless @term_of_service_uri 13 | 14 | @client.connection.get(@term_of_service_uri).body 15 | end 16 | 17 | def agree_terms 18 | return true unless @term_of_service_uri 19 | response = @client.connection.post(@uri, resource: 'reg', agreement: @term_of_service_uri) 20 | response.success? 21 | end 22 | 23 | private 24 | 25 | def assign_links(links) 26 | @next_uri = links['next'] 27 | @recover_uri = links['recover'] 28 | @term_of_service_uri = links['terms-of-service'] 29 | end 30 | 31 | def assign_attributes(body) 32 | @id = body['id'] 33 | @key = body['key'] 34 | @contact = body['contact'] 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /mrblib/acme/client/resources/challenges/base.rb: -------------------------------------------------------------------------------- 1 | class Acme::Client::Resources::Challenges::Base 2 | attr_reader :authorization, :status, :uri, :token, :error 3 | 4 | def initialize(authorization) 5 | @authorization = authorization 6 | end 7 | 8 | def client 9 | authorization.client 10 | end 11 | 12 | def verify_status 13 | authorization.verify_status 14 | 15 | status 16 | end 17 | 18 | def request_verification 19 | response = client.connection.post(@uri, { resource: 'challenge', type: challenge_type, keyAuthorization: authorization_key }) 20 | response.success? 21 | end 22 | 23 | def assign_attributes(attributes) 24 | @status = attributes.fetch('status', 'pending') 25 | @uri = attributes.fetch('uri') 26 | @token = attributes.fetch('token') 27 | @error = attributes['error'] 28 | end 29 | 30 | private 31 | 32 | def challenge_type 33 | self.class::CHALLENGE_TYPE 34 | end 35 | 36 | def authorization_key 37 | "#{token}.#{crypto.thumbprint}" 38 | end 39 | 40 | def crypto 41 | @crypto ||= Acme::Client::Crypto.new(client.private_key) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /example/auth_domain.rb: -------------------------------------------------------------------------------- 1 | private_key = OpenSSL::PKey::RSA.new(4096) 2 | endpoint = "http://127.0.0.1:4000/" 3 | client = Acme::Client.new( 4 | private_key, 5 | endpoint, 6 | { request: { open_timeout: 5, timeout: 5 } } 7 | ) 8 | 9 | registration = client.register('mailto:contact@example.org') 10 | registration.agree_terms 11 | 12 | domains = %w(eample.org) 13 | 14 | domains.each do |n| 15 | authorization = client.authorize(n) 16 | 17 | challenge = authorization.http01 18 | challenge.put_content '/var/www/html' 19 | 20 | challenge = client.fetch_authorization(authorization.uri).http01 21 | challenge.request_verification # => true 22 | puts challenge.authorization.verify_status # => 'pending' 23 | 24 | sleep(1) 25 | puts challenge.authorization.verify_status # => 'valid' 26 | end 27 | csr = Acme::Client::CertificateRequest.new(domains) 28 | certificate = client.new_certificate(csr) 29 | { 30 | 'privkey.pem' => certificate.request.private_key.to_pem, 31 | "cert.pem" => certificate.to_pem, 32 | "chain.pem" => certificate.chain_to_pem, 33 | "fullchain.pem" => certificate.fullchain_to_pem 34 | }.each do |k,v| 35 | File.open(k, 'w'){|fp| 36 | fp.puts v 37 | } 38 | end 39 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | MRUBY_CONFIG=File.expand_path(ENV["MRUBY_CONFIG"] || "build_config.rb") 2 | 3 | require 'fileutils' 4 | 5 | if ENV["MRUBY_VERSION"] && !ENV["MRUBY_VERSION"].empty? 6 | MRUBY_VERSION = ENV["MRUBY_VERSION"] 7 | else 8 | MRUBY_VERSION = File.read(File.expand_path "../mruby_version.lock", __FILE__).chomp 9 | end 10 | 11 | file :mruby do 12 | cmd = "git clone --depth=1 https://github.com/mruby/mruby.git" 13 | case MRUBY_VERSION 14 | when /\A[a-fA-F0-9]+\z/ 15 | cmd << " && cd mruby" 16 | cmd << " && git fetch --depth=5000 && git checkout #{MRUBY_VERSION}" 17 | when /\A\d\.\d\.\d\z/ 18 | cmd << " && cd mruby" 19 | cmd << " && git fetch --tags && git checkout $(git rev-parse #{MRUBY_VERSION})" 20 | when "master" 21 | # skip 22 | else 23 | fail "Invalid MRUBY_VERSION spec: #{MRUBY_VERSION}" 24 | end 25 | sh cmd 26 | end 27 | 28 | desc "compile binary" 29 | task :compile => :mruby do 30 | sh "cd mruby && rake all MRUBY_CONFIG=\"#{MRUBY_CONFIG}\"" 31 | end 32 | 33 | desc "test" 34 | task :test => :mruby do 35 | sh "cd mruby && rake all test MRUBY_CONFIG=\"#{MRUBY_CONFIG}\"" 36 | end 37 | 38 | desc "cleanup" 39 | task :clean do 40 | sh "cd mruby && rake deep_clean" 41 | end 42 | 43 | task :default => :compile 44 | -------------------------------------------------------------------------------- /src/ossl_config.c: -------------------------------------------------------------------------------- 1 | // LICENSE: https://github.com/ruby/openssl/blob/master/LICENSE.txt 2 | #include "ossl.h" 3 | struct RClass *cConfig; 4 | struct RClass *eConfigError; 5 | 6 | CONF *GetConfigPtr(mrb_state *mrb, VALUE obj) 7 | { 8 | CONF *conf; 9 | VALUE str; 10 | BIO *bio; 11 | long eline = -1; 12 | 13 | OSSL_Check_Kind(mrb, obj, cConfig); 14 | str = mrb_funcall(mrb, obj, "to_s", 0); 15 | bio = ossl_obj2bio(mrb, str); 16 | conf = NCONF_new(NULL); 17 | if (!conf) { 18 | BIO_free(bio); 19 | mrb_raise(mrb, eConfigError, NULL); 20 | } 21 | if (!NCONF_load_bio(conf, bio, &eline)) { 22 | BIO_free(bio); 23 | NCONF_free(conf); 24 | if (eline <= 0) 25 | mrb_raise(mrb, eConfigError, "wrong config format"); 26 | else 27 | mrb_raisef(mrb, eConfigError, "error in line %d", eline); 28 | mrb_raise(mrb, eConfigError, NULL); 29 | } 30 | BIO_free(bio); 31 | 32 | return conf; 33 | } 34 | 35 | void Init_ossl_config(mrb_state *mrb) 36 | { 37 | char *default_config_file; 38 | eConfigError = mrb_define_class_under(mrb, mOSSL, "ConfigError", eOSSLError); 39 | cConfig = mrb_define_class_under(mrb, mOSSL, "Config", mrb->object_class); 40 | 41 | default_config_file = CONF_get1_default_config_file(); 42 | mrb_define_const(mrb, cConfig, "DEFAULT_CONFIG_FILE", mrb_str_new_cstr(mrb, default_config_file)); 43 | OPENSSL_free(default_config_file); 44 | /* methods are defined by openssl/config.rb */ 45 | } 46 | -------------------------------------------------------------------------------- /mrblib/acme/client/resources/authorization.rb: -------------------------------------------------------------------------------- 1 | class Acme::Client::Resources::Authorization 2 | HTTP01 = Acme::Client::Resources::Challenges::HTTP01 3 | DNS01 = Acme::Client::Resources::Challenges::DNS01 4 | TLSSNI01 = Acme::Client::Resources::Challenges::TLSSNI01 5 | 6 | attr_reader :client, :uri, :domain, :status, :expires, :http01, :dns01, :tls_sni01 7 | 8 | def initialize(client, uri, response) 9 | @client = client 10 | @uri = uri 11 | assign_attributes(JSON.parse(response.body)) 12 | end 13 | 14 | def verify_status 15 | response = @client.connection.get(@uri) 16 | assign_attributes(JSON.parse(response.body)) 17 | status 18 | end 19 | 20 | private 21 | 22 | def assign_attributes(body) 23 | # @expires = Time.iso8601(body['expires']) if body.key? 'expires' 24 | @domain = body['identifier']['value'] 25 | @status = body['status'] 26 | assign_challenges(body['challenges']) 27 | end 28 | 29 | def assign_challenges(challenges) 30 | challenges.each do |attributes| 31 | challenge = case attributes.fetch('type') 32 | when 'http-01' 33 | @http01 ||= HTTP01.new(self) 34 | when 'dns-01' 35 | @dns01 ||= DNS01.new(self) 36 | when 'tls-sni-01' 37 | @tls_sni01 ||= TLSSNI01.new(self) 38 | end 39 | 40 | challenge.assign_attributes(attributes) if challenge 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /src/ossl_bn.h: -------------------------------------------------------------------------------- 1 | 2 | //LICENSE: https://github.com/ruby/openssl/blob/master/LICENSE.txt 3 | #if !defined(_OSSL_BN_H_) 4 | #define _OSSL_BN_H_ 5 | #define NewBN(klass) mrb_obj_value(Data_Wrap_Struct(mrb, klass, &ossl_bn_type, 0)) 6 | 7 | #define SetBN(obj, bn) \ 8 | do { \ 9 | if (!(bn)) { \ 10 | mrb_raise((mrb), E_RUNTIME_ERROR, "BN wasn't initialized!"); \ 11 | } \ 12 | DATA_PTR(obj) = bn; \ 13 | DATA_TYPE(obj) = &ossl_bn_type; \ 14 | } while (0) 15 | 16 | #define GetBN(obj, bn) \ 17 | do { \ 18 | bn = DATA_PTR(obj); \ 19 | } while (0) 20 | 21 | void Init_ossl_bn(mrb_state *mrb); 22 | mrb_value ossl_bn_new(mrb_state *mrb, const BIGNUM *); 23 | BIGNUM *GetBNPtr(mrb_state *mrb, VALUE); 24 | 25 | #endif /* _OSS_BN_H_ */ 26 | -------------------------------------------------------------------------------- /src/ossl_x509crl.c: -------------------------------------------------------------------------------- 1 | 2 | //LICENSE: https://github.com/ruby/openssl/blob/master/LICENSE.txt 3 | #include "ossl.h" 4 | extern struct RClass *mX509; 5 | #define GetX509CRL(obj, crl) \ 6 | do { \ 7 | crl = DATA_PTR(obj); \ 8 | } while (0) 9 | 10 | #define SafeGetX509CRL(obj, crl) \ 11 | do { \ 12 | OSSL_Check_Kind((mrb), (obj), cX509CRL); \ 13 | GetX509CRL((obj), (crl)); \ 14 | } while (0) 15 | struct RClass *cX509CRL; 16 | struct RClass *eX509CRLError; 17 | 18 | static void ossl_x509crl_free(mrb_state *mrb, void *ptr) 19 | { 20 | X509_CRL_free(ptr); 21 | } 22 | 23 | static const mrb_data_type ossl_x509crl_type = {"OpenSSL/X509/CRL", ossl_x509crl_free}; 24 | X509_CRL *GetX509CRLPtr(mrb_state *mrb, VALUE obj) 25 | { 26 | X509_CRL *crl; 27 | 28 | SafeGetX509CRL(obj, crl); 29 | 30 | return crl; 31 | } 32 | 33 | void Init_ossl_x509crl(mrb_state *mrb) 34 | { 35 | eX509CRLError = mrb_define_class_under(mrb, mX509, "CRLError", eOSSLError); 36 | cX509CRL = mrb_define_class_under(mrb, mX509, "CRL", mrb->object_class); 37 | }; 38 | -------------------------------------------------------------------------------- /mrblib/acme/client/crypto.rb: -------------------------------------------------------------------------------- 1 | class Acme::Client::Crypto 2 | attr_reader :private_key 3 | 4 | def initialize(private_key) 5 | @private_key = private_key 6 | end 7 | 8 | def generate_signed_jws(nonce, payload) 9 | header = { typ: 'JWT', alg: jws_alg, jwk: jwk } 10 | encoded_header = Base64.urlsafe_base64(header.merge(nonce: nonce).to_json) 11 | encoded_payload = Base64.urlsafe_base64(payload.to_json) 12 | signature_data = "#{encoded_header}.#{encoded_payload}" 13 | signature = private_key.sign digest, signature_data 14 | encoded_signature = Base64.urlsafe_base64(signature) 15 | { 16 | protected: encoded_header, 17 | payload: encoded_payload, 18 | signature: encoded_signature 19 | }.to_json 20 | end 21 | 22 | def thumbprint 23 | Base64.urlsafe_base64 digest.digest(jwk.to_json) 24 | end 25 | 26 | def digest 27 | OpenSSL::Digest::SHA256.new 28 | end 29 | 30 | private 31 | 32 | def jws_alg 33 | { 'RSA' => 'RS256', 'EC' => 'ES256' }.fetch(jwk[:kty]) 34 | end 35 | 36 | def jwk 37 | @jwk ||= case private_key 38 | when OpenSSL::PKey::RSA 39 | rsa_jwk 40 | else 41 | raise ArgumentError, "Can't handle #{private_key} as private key, only OpenSSL::PKey::RSA" 42 | end 43 | end 44 | 45 | def rsa_jwk 46 | { 47 | e: Base64.urlsafe_base64(public_key.e.to_s(2)), 48 | kty: 'RSA', 49 | n: Base64.urlsafe_base64(public_key.n.to_s(2)) 50 | } 51 | end 52 | 53 | def public_key 54 | @public_key ||= private_key.public_key 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mruby-acme-client [![Build Status](https://travis-ci.org/pyama86/mruby-acme-client.svg?branch=master)](https://travis-ci.org/pyama86/mruby-acme-client) 2 | It is a clone of https://github.com/unixcharles/acme-client 3 | For openssl we refer to Ruby's implementation. [ruby/openssl](https://github.com/ruby/openssl) 4 | ## install by mrbgems 5 | - add conf.gem line to `build_config.rb` 6 | 7 | ```ruby 8 | MRuby::Build.new do |conf| 9 | 10 | # ... (snip) ... 11 | 12 | conf.gem :github => 'pyama86/mruby-acme-client' 13 | end 14 | ``` 15 | ## example 16 | ```ruby 17 | private_key = OpenSSL::PKey::RSA.new(4096) 18 | endpoint = "http://127.0.0.1:4000/" 19 | client = Acme::Client.new( 20 | private_key, 21 | endpoint, 22 | { request: { open_timeout: 5, timeout: 5 } } 23 | ) 24 | 25 | registration = client.register('mailto:contact@example.org') 26 | registration.agree_terms 27 | 28 | domains = %w(eample.org www.example.org) 29 | 30 | domains.each do |n| 31 | authorization = client.authorize(n) 32 | 33 | challenge = authorization.http01 34 | challenge.put_content '/var/www/html' 35 | 36 | challenge = client.fetch_authorization(authorization.uri).http01 37 | challenge.request_verification # => true 38 | puts challenge.authorization.verify_status # => 'pending' 39 | 40 | sleep(1) 41 | puts challenge.authorization.verify_status # => 'valid' 42 | end 43 | 44 | csr = Acme::Client::CertificateRequest.new(domains) 45 | certificate = client.new_certificate(csr) 46 | 47 | { 48 | 'privkey.pem' => certificate.request.private_key.to_pem, 49 | "cert.pem" => certificate.to_pem, 50 | "chain.pem" => certificate.chain_to_pem, 51 | "fullchain.pem" => certificate.fullchain_to_pem 52 | }.each do |k,v| 53 | File.open(k, 'w'){|fp| 54 | fp.puts v 55 | } 56 | end 57 | ``` 58 | 59 | ## License 60 | under the MIT License: 61 | - see LICENSE file 62 | -------------------------------------------------------------------------------- /mrblib/acme/client/self_sign_certificate.rb: -------------------------------------------------------------------------------- 1 | class Acme::Client::SelfSignCertificate 2 | attr_reader :private_key, :subject_alt_names, :not_before, :not_after 3 | 4 | extend Forwardable 5 | def_delegators :certificate, :to_pem, :to_der 6 | 7 | def initialize(subject_alt_names, not_before=default_not_before, not_after=default_not_after, private_key=generate_private_key) 8 | @private_key = private_key 9 | @subject_alt_names = subject_alt_names 10 | @not_before = not_before 11 | @not_after = not_after 12 | end 13 | 14 | def certificate 15 | @certificate ||= begin 16 | certificate = generate_certificate 17 | 18 | extension_factory = generate_extension_factory(certificate) 19 | subject_alt_name_entry = subject_alt_names.map { |d| "DNS: #{d}" }.join(',') 20 | subject_alt_name_extension = extension_factory.create_extension('subjectAltName', subject_alt_name_entry) 21 | certificate.add_extension(subject_alt_name_extension) 22 | 23 | certificate.sign(private_key, digest) 24 | end 25 | end 26 | 27 | private 28 | 29 | def generate_private_key 30 | OpenSSL::PKey::RSA.new(2048) 31 | end 32 | 33 | def default_not_before 34 | Time.now - 3600 35 | end 36 | 37 | def default_not_after 38 | Time.now + 30 * 24 * 3600 39 | end 40 | 41 | def digest 42 | OpenSSL::Digest::SHA256.new 43 | end 44 | 45 | def generate_certificate 46 | certificate = OpenSSL::X509::Certificate.new 47 | certificate.not_before = not_before 48 | certificate.not_after = not_after 49 | certificate.public_key = private_key.public_key 50 | certificate.version = 2 51 | certificate.serial = 1 52 | certificate 53 | end 54 | 55 | def generate_extension_factory(certificate) 56 | extension_factory = OpenSSL::X509::ExtensionFactory.new 57 | extension_factory.subject_certificate = certificate 58 | extension_factory.issuer_certificate = certificate 59 | extension_factory 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /src/ossl.c: -------------------------------------------------------------------------------- 1 | #include "ossl.h" 2 | // LICENCE: https://github.com/ruby/openssl/blob/master/LICENSE.txt 3 | 4 | struct RClass *eOSSLError; 5 | struct RClass *mOSSL; 6 | 7 | mrb_value ossl_to_der(mrb_state *mrb, mrb_value obj) 8 | { 9 | mrb_value tmp; 10 | 11 | tmp = mrb_funcall(mrb, obj, "to_der", 0); 12 | return mrb_str_to_str(mrb, tmp); 13 | } 14 | 15 | mrb_value ossl_to_der_if_possible(mrb_state *mrb, mrb_value obj) 16 | { 17 | if (mrb_respond_to(mrb, obj, mrb_intern_lit(mrb, "to_der"))) 18 | return ossl_to_der(mrb, obj); 19 | return obj; 20 | } 21 | 22 | static mrb_value ossl_str_new(mrb_state *mrb, int size) 23 | { 24 | return mrb_str_new(mrb, 0, size); 25 | } 26 | 27 | mrb_value ossl_fetch_error(mrb_state *mrb) 28 | { 29 | const char *msg; 30 | const char *warn; 31 | long e; 32 | 33 | #ifdef HAVE_ERR_PEEK_LAST_ERROR 34 | e = ERR_peek_last_error(); 35 | #else 36 | e = ERR_peek_error(); 37 | #endif 38 | if (e) { 39 | msg = ERR_reason_error_string(e); 40 | } 41 | 42 | 43 | while ((e = ERR_get_error()) != 0) { 44 | warn = ERR_error_string(e, NULL); 45 | if (warn) 46 | mrb_warn(mrb, "error on stack:lib: %S", mrb_str_new_cstr(mrb, warn)); 47 | } 48 | 49 | ERR_clear_error(); 50 | if (msg) 51 | return mrb_str_new_cstr(mrb, msg); 52 | else 53 | mrb_nil_value(); 54 | } 55 | 56 | mrb_value ossl_buf2str(mrb_state *mrb, char *buf, int len) 57 | { 58 | mrb_value str; 59 | 60 | str = ossl_str_new(mrb, len); 61 | if (!NIL_P(str)) 62 | memcpy(RSTRING_PTR(str), buf, len); 63 | return str; 64 | } 65 | 66 | 67 | int ossl_pem_passwd_cb(char *buf, int max_len, int flag, void *mrb) 68 | { 69 | return 0; 70 | } 71 | 72 | void mrb_init_ossl(mrb_state *mrb) 73 | { 74 | mOSSL = mrb_define_module(mrb, "OpenSSL"); 75 | eOSSLError = mrb_define_class_under(mrb, mOSSL, "OpenSSLError", mrb->eStandardError_class); 76 | Init_ossl_digest(mrb); 77 | Init_ossl_bn(mrb); 78 | Init_ossl_pkey(mrb); 79 | Init_ossl_config(mrb); 80 | } 81 | -------------------------------------------------------------------------------- /mrblib/openssl.rb: -------------------------------------------------------------------------------- 1 | module OpenSSL 2 | class ASN1Data 3 | attr_accessor :value, :tag, :tag_class, :infinite_length 4 | end 5 | 6 | class Primitive < ASN1Data 7 | attr_accessor :tagging 8 | end 9 | 10 | class Constructive < ASN1Data 11 | attr_accessor :tagging 12 | end 13 | 14 | class Config 15 | include Enumerable 16 | def to_s 17 | ary = [] 18 | @data.keys.sort.each do |section| 19 | ary << "[ #{section} ]\n" 20 | @data[section].keys.each do |key| 21 | ary << "#{key}=#{@data[section][key]}\n" 22 | end 23 | ary << "\n" 24 | end 25 | ary.join 26 | end 27 | end 28 | 29 | module X509 30 | class ExtensionFactory 31 | def create_extension(*arg) 32 | if arg.size > 1 33 | create_ext(*arg) 34 | else 35 | send("create_ext_from_"+arg[0].class.name.downcase, arg[0]) 36 | end 37 | end 38 | 39 | def create_ext_from_array(ary) 40 | raise ExtensionError, "unexpected array form" if ary.size > 3 41 | create_ext(ary[0], ary[1], ary[2]) 42 | end 43 | 44 | def create_ext_from_string(str) # "oid = critical, value" 45 | oid, value = str.split(/=/, 2) 46 | oid.strip! 47 | value.strip! 48 | create_ext(oid, value) 49 | end 50 | 51 | def create_ext_from_hash(hash) 52 | create_ext(hash["oid"], hash["value"], hash["critical"]) 53 | end 54 | end 55 | 56 | class Extension 57 | def to_s # "oid = critical, value" 58 | str = self.oid 59 | str << " = " 60 | str << "critical, " if self.critical? 61 | str << self.value.gsub(/\n/, ", ") 62 | end 63 | 64 | def to_h # {"oid"=>sn|ln, "value"=>value, "critical"=>true|false} 65 | {"oid"=>self.oid,"value"=>self.value,"critical"=>self.critical?} 66 | end 67 | 68 | def to_a 69 | [ self.oid, self.value, self.critical? ] 70 | end 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /src/ossl_x509name.h: -------------------------------------------------------------------------------- 1 | 2 | //LICENSE: https://github.com/ruby/openssl/blob/master/LICENSE.txt 3 | 4 | extern struct RClass *cX509Name; 5 | extern struct RClass *eX509NameError; 6 | #define GetX509Name(obj, name) \ 7 | do { \ 8 | name = DATA_PTR(obj); \ 9 | } while (0) 10 | 11 | #define SetX509Name(obj, name) \ 12 | do { \ 13 | if (!(name)) { \ 14 | mrb_raise((mrb), E_RUNTIME_ERROR, "Name wasn't initialized!"); \ 15 | } \ 16 | DATA_PTR(obj) = name; \ 17 | DATA_TYPE(obj) = &ossl_x509name_type; \ 18 | } while (0) 19 | #define SafeGetX509Name(obj, name) \ 20 | do { \ 21 | OSSL_Check_Kind((mrb), (obj), cX509Name); \ 22 | GetX509Name((obj), (name)); \ 23 | } while (0) 24 | #define OBJECT_TYPE_TEMPLATE(mrb, klass) \ 25 | mrb_const_get(mrb, klass, mrb_intern_lit(mrb, "OBJECT_TYPE_TEMPLATE")) 26 | #define DEFAULT_OBJECT_TYPE(mrb, klass) \ 27 | mrb_const_get(mrb, klass, mrb_intern_lit(mrb, "DEFAULT_OBJECT_TYPE")) 28 | 29 | #define mrb_aref(mrb, obj, key) mrb_funcall((mrb), (obj), "[]", 1, (key)) 30 | void Init_ossl_x509name(mrb_state *mrb); 31 | -------------------------------------------------------------------------------- /src/ossl_pkey.h: -------------------------------------------------------------------------------- 1 | 2 | //LICENSE: https://github.com/ruby/openssl/blob/master/LICENSE.txt 3 | #if !defined(_OSSL_PKEY_H_) 4 | #define _OSSL_PKEY_H_ 5 | extern struct RClass *cPKey; 6 | extern struct RClass *mPKey; 7 | extern struct RClass *ePKeyError; 8 | void Init_ossl_pkey(mrb_state *mrb); 9 | mrb_value ossl_pkey_new(mrb_state *mrb, EVP_PKEY *pkey); 10 | mrb_value ossl_pkey_alloc(mrb_state *mrb, mrb_value klass); 11 | EVP_PKEY *GetPKeyPtr(mrb_state *mrb, mrb_value obj); 12 | EVP_PKEY *GetPrivPKeyPtr(mrb_state *mrb, VALUE obj); 13 | 14 | #define NewPKey(klass) mrb_obj_value(Data_Wrap_Struct(mrb, klass, &ossl_evp_pkey_type, 0)) 15 | 16 | #define OSSL_PKEY_SET_PUBLIC(obj) \ 17 | mrb_iv_set(mrb, (obj), mrb_intern_lit(mrb, "private"), mrb_false_value()) 18 | 19 | #define SetPKey(obj, pkey) \ 20 | do { \ 21 | if (!(pkey)) { \ 22 | mrb_raise((mrb), E_RUNTIME_ERROR, "PKEY wasn't initialized!"); \ 23 | } \ 24 | DATA_PTR(obj) = pkey; \ 25 | DATA_TYPE(obj) = &ossl_evp_pkey_type; \ 26 | OSSL_PKEY_SET_PUBLIC(obj); \ 27 | } while (0) 28 | 29 | #define GetPKey(obj, pkey) \ 30 | do { \ 31 | pkey = DATA_PTR(obj); \ 32 | } while (0) 33 | 34 | #define SafeGetPKey(obj, pkey) \ 35 | do { \ 36 | OSSL_Check_Kind(mrb, (obj), cPKey); \ 37 | GetPKey((obj), (pkey)); \ 38 | } while (0) 39 | #endif /* _OSSL_PKEY_H_ */ 40 | -------------------------------------------------------------------------------- /mrblib/custom_http_request.rb: -------------------------------------------------------------------------------- 1 | class CustomHttpRequest < HttpRequest 2 | def initialize(endpoint, crypto) 3 | @endpoint = endpoint 4 | @directory_uri = full_path("/directory") 5 | @crypto = crypto 6 | @nonces = [] 7 | end 8 | 9 | def post(uri, payload) 10 | nonce = pop_nonce 11 | body = @crypto.generate_signed_jws(nonce, payload) 12 | response = super(full_path(uri), body) 13 | store_nonce(response.headers) 14 | on_complete response 15 | end 16 | 17 | def get(uri, payload={}) 18 | on_complete super(full_path(uri), payload) 19 | end 20 | 21 | def head(uri) 22 | on_complete super(full_path(uri)) 23 | end 24 | 25 | def path_strip(uri) 26 | uri.gsub(/^\/+|\/+$/, '') 27 | end 28 | 29 | def full_path(uri) 30 | (uri =~ /^http/ ? uri : "#{path_strip(@endpoint)}/#{path_strip(uri)}").gsub(%r{http://127.0.0.1/}, 'http://127.0.0.1:4000/') 31 | end 32 | 33 | def directory 34 | get(@directory_uri) 35 | end 36 | 37 | private 38 | def store_nonce(response_headers) 39 | @nonces << response_headers['replay-nonce'] 40 | end 41 | 42 | def pop_nonce 43 | if @nonces.empty? 44 | get_nonce 45 | else 46 | @nonces.pop 47 | end 48 | end 49 | 50 | def get_nonce 51 | head(@directory_uri)['replay-nonce'] 52 | end 53 | 54 | def on_complete(response) 55 | error = Error.from_response(response) 56 | raise error if error 57 | response.headers['link'] = decode_link_headers(response.headers) if response.headers.key?('link') 58 | response 59 | end 60 | 61 | LINK_MATCH = /<(.*?)>;rel="([\w-]+)"/ 62 | def decode_link_headers(headers) 63 | link_header = headers['link'].is_a?(Array) ? headers['link'] : [headers['link'] ] 64 | links = link_header.map { |entry| 65 | _, link, name = *entry.match(LINK_MATCH) 66 | [name, link] 67 | } 68 | 69 | Hash[*links.flatten] 70 | end 71 | 72 | class Error < ::StandardError 73 | attr_reader :response 74 | def self.from_response(response) 75 | status = response.status.to_i 76 | klass = case status 77 | when 400 then BadRequest 78 | when 400..499 then ClientError 79 | when 500 then InternalServerError 80 | when 500..599 then ServerError 81 | end 82 | 83 | raise klass.new(response) if klass 84 | end 85 | 86 | def initialize(response) 87 | @response = response 88 | super(build_error_message) 89 | end 90 | 91 | private 92 | 93 | def build_error_message 94 | return nil if @response.nil? 95 | message = "#{@response.body} #{@response.status}" 96 | message << " - #{response_error}>" if response_error 97 | message 98 | end 99 | 100 | def response_error 101 | data = @response.body 102 | data.error if data.respond_to?(:error) && data.error 103 | end 104 | end 105 | 106 | class ClientError < Error;end 107 | class BadRequest < ClientError;end 108 | class ServerError < Error;end 109 | class InternalServerError < ServerError;end 110 | end 111 | 112 | class SimpleHttp::SimpleHttpResponse 113 | def success? 114 | status =~ /^2/ 115 | end 116 | end 117 | -------------------------------------------------------------------------------- /src/ossl_x509cert.c: -------------------------------------------------------------------------------- 1 | 2 | //LICENSE: https://github.com/ruby/openssl/blob/master/LICENSE.txt 3 | #include "ossl.h" 4 | struct RClass *cX509Cert; 5 | struct RClass *eX509CertError; 6 | extern struct RClass *mX509; 7 | 8 | #define GetX509(obj, x509) \ 9 | do { \ 10 | x509 = DATA_PTR(obj); \ 11 | } while (0) 12 | #define SetX509(obj, x509) \ 13 | do { \ 14 | if (!(x509)) { \ 15 | mrb_raise((mrb), E_RUNTIME_ERROR, " wasn't initialized!"); \ 16 | } \ 17 | DATA_PTR(obj) = x509; \ 18 | DATA_TYPE(obj) = &ossl_x509_type; \ 19 | } while (0) 20 | #define SafeGetX509(obj, x509) \ 21 | do { \ 22 | OSSL_Check_Kind((mrb), (obj), cX509Cert); \ 23 | GetX509((obj), (x509)); \ 24 | } while (0) 25 | 26 | static void ossl_x509_free(mrb_state *mrb, void *ptr) 27 | { 28 | X509_free(ptr); 29 | } 30 | 31 | static const mrb_data_type ossl_x509_type = {"OpenSSL/X509", ossl_x509_free}; 32 | 33 | X509 *GetX509CertPtr(mrb_state *mrb, VALUE obj) 34 | { 35 | X509 *x509; 36 | 37 | SafeGetX509(obj, x509); 38 | 39 | return x509; 40 | } 41 | 42 | static VALUE ossl_x509_initialize(mrb_state *mrb, VALUE self) 43 | { 44 | BIO *in; 45 | X509 *x509, *x; 46 | VALUE arg; 47 | VALUE obj; 48 | 49 | x = X509_new(); 50 | if (!x) 51 | mrb_raise(mrb, eX509CertError, NULL); 52 | 53 | if (mrb_get_args(mrb, "o", &arg) > 0) { 54 | 55 | arg = ossl_to_der_if_possible(mrb, arg); 56 | in = ossl_obj2bio(mrb, arg); 57 | x509 = PEM_read_bio_X509(in, &x, NULL, NULL); 58 | if (!x509) { 59 | OSSL_BIO_reset(in); 60 | x509 = d2i_X509_bio(in, &x); 61 | } 62 | BIO_free(in); 63 | if (!x509) 64 | mrb_raise(mrb, eX509CertError, NULL); 65 | } 66 | SetX509(self, x); 67 | 68 | return self; 69 | } 70 | 71 | static VALUE ossl_x509_to_pem(mrb_state *mrb, VALUE self) 72 | { 73 | X509 *x509; 74 | BIO *out; 75 | VALUE str; 76 | 77 | GetX509(self, x509); 78 | out = BIO_new(BIO_s_mem()); 79 | if (!out) 80 | mrb_raise(mrb, eX509CertError, NULL); 81 | 82 | if (!PEM_write_bio_X509(out, x509)) { 83 | BIO_free(out); 84 | mrb_raise(mrb, eX509CertError, NULL); 85 | } 86 | str = ossl_membio2str(mrb, out); 87 | 88 | return str; 89 | } 90 | 91 | void Init_ossl_x509cert(mrb_state *mrb) 92 | { 93 | eX509CertError = mrb_define_class_under(mrb, mX509, "CertificateError", eOSSLError); 94 | cX509Cert = mrb_define_class_under(mrb, mX509, "Certificate", mrb->object_class); 95 | MRB_SET_INSTANCE_TT(cX509Cert, MRB_TT_DATA); 96 | mrb_define_method(mrb, cX509Cert, "initialize", ossl_x509_initialize, MRB_ARGS_OPT(1)); 97 | mrb_define_method(mrb, cX509Cert, "to_pem", ossl_x509_to_pem, 0); 98 | } 99 | -------------------------------------------------------------------------------- /mrblib/acme/client/certificate_request.rb: -------------------------------------------------------------------------------- 1 | class Acme::Client::CertificateRequest 2 | extend Forwardable 3 | 4 | DEFAULT_KEY_LENGTH = 2048 5 | DEFAULT_DIGEST = OpenSSL::Digest::SHA256 6 | SUBJECT_KEYS = { 7 | common_name: 'CN', 8 | country_name: 'C', 9 | organization_name: 'O', 10 | organizational_unit: 'OU', 11 | state_or_province: 'ST', 12 | locality_name: 'L' 13 | }.freeze 14 | 15 | SUBJECT_TYPES = { 16 | 'CN' => OpenSSL::ASN1::UTF8STRING, 17 | 'C' => OpenSSL::ASN1::UTF8STRING, 18 | 'O' => OpenSSL::ASN1::UTF8STRING, 19 | 'OU' => OpenSSL::ASN1::UTF8STRING, 20 | 'ST' => OpenSSL::ASN1::UTF8STRING, 21 | 'L' => OpenSSL::ASN1::UTF8STRING 22 | }.freeze 23 | 24 | attr_reader :private_key, :common_name, :names, :subject, :csr 25 | 26 | def_delegators :@csr, :to_pem, :to_der 27 | 28 | def initialize(names=[], private_key=generate_private_key, subject={}, digest=DEFAULT_DIGEST.new) 29 | @digest = digest 30 | @private_key = private_key 31 | @subject = normalize_subject(subject) 32 | @common_name = common_name || @subject[SUBJECT_KEYS[:common_name]] || @subject[:common_name] 33 | @names = names.dup 34 | normalize_names 35 | @subject[SUBJECT_KEYS[:common_name]] ||= @common_name 36 | validate_subject 37 | @csr ||= generate 38 | end 39 | 40 | private 41 | 42 | def generate_private_key 43 | OpenSSL::PKey::RSA.new(DEFAULT_KEY_LENGTH) 44 | end 45 | 46 | def normalize_subject(subject) 47 | @subject = subject.each_with_object({}) do |(key, value), hash| 48 | hash[SUBJECT_KEYS.fetch(key, key)] = value.to_s 49 | end 50 | end 51 | 52 | def normalize_names 53 | if @common_name 54 | @names.unshift(@common_name) unless @names.include?(@common_name) 55 | else 56 | raise ArgumentError, 'No common name and no list of names given' if @names.empty? 57 | @common_name = @names.first 58 | end 59 | end 60 | 61 | def validate_subject 62 | validate_subject_attributes 63 | validate_subject_common_name 64 | end 65 | 66 | def validate_subject_attributes 67 | extra_keys = @subject.keys - SUBJECT_KEYS.keys - SUBJECT_KEYS.values 68 | return if extra_keys.empty? 69 | raise ArgumentError, "Unexpected subject attributes given: #{extra_keys.inspect}" 70 | end 71 | 72 | def validate_subject_common_name 73 | return if @common_name == @subject[SUBJECT_KEYS[:common_name]] 74 | raise ArgumentError, 'Conflicting common name given in arguments and subject' 75 | end 76 | 77 | def generate 78 | OpenSSL::X509::Request.new.tap do |csr| 79 | csr.public_key = @private_key 80 | csr.subject = generate_subject 81 | csr.version = 2 82 | add_extension(csr) 83 | csr.sign @private_key, @digest 84 | end 85 | end 86 | 87 | def generate_subject 88 | OpenSSL::X509::Name.new( 89 | @subject.map {|name, value| 90 | [name, value, SUBJECT_TYPES[name]] 91 | } 92 | ) 93 | end 94 | 95 | def add_extension(csr) 96 | return if @names.size <= 1 97 | 98 | extension = OpenSSL::X509::ExtensionFactory.new.create_extension( 99 | 'subjectAltName', 100 | @names.map { |name| "DNS:#{name}" }.join(', '), 101 | false 102 | ) 103 | 104 | csr.add_attribute( 105 | OpenSSL::X509::Attribute.new( 106 | 'extReq', 107 | OpenSSL::ASN1::Set.new([OpenSSL::ASN1::Sequence.new([extension])]) 108 | ) 109 | ) 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /src/ossl_x509name.c: -------------------------------------------------------------------------------- 1 | 2 | //LICENSE: https://github.com/ruby/openssl/blob/master/LICENSE.txt 3 | #include "ossl.h" 4 | 5 | extern struct RClass *mX509; 6 | struct RClass *cX509Name; 7 | struct RClass *eX509NameError; 8 | 9 | static void ossl_x509name_free(mrb_state *mrb, void *ptr) 10 | { 11 | X509_NAME_free(ptr); 12 | } 13 | 14 | static const mrb_data_type ossl_x509name_type = {"OpenSSL/X509/NAME", ossl_x509name_free}; 15 | 16 | static mrb_value ossl_x509name_add_entry(mrb_state *mrb, mrb_value self) 17 | { 18 | X509_NAME *name; 19 | const char *oid_name; 20 | mrb_value oid, value, type; 21 | 22 | mrb_get_args(mrb, "SS|o", &oid, &value, &type); 23 | 24 | oid_name = mrb_str_to_cstr(mrb, oid); 25 | if (mrb_nil_p(type)) 26 | type = mrb_aref(mrb, OBJECT_TYPE_TEMPLATE(mrb, self), oid); 27 | GetX509Name(self, name); 28 | if (!X509_NAME_add_entry_by_txt(name, oid_name, mrb_fixnum(type), 29 | (const unsigned char *)RSTRING_PTR(value), RSTRING_LEN(value), -1, 30 | 0)) { 31 | mrb_raise(mrb, eX509NameError, NULL); 32 | } 33 | return self; 34 | } 35 | 36 | static mrb_value ossl_x509name_init_i(mrb_state *mrb, mrb_value args, mrb_value cur) 37 | { 38 | mrb_value self = mrb_ary_entry(args, 0); 39 | mrb_value template = mrb_ary_entry(args, 1); 40 | mrb_value entry[3]; 41 | 42 | entry[0] = mrb_ary_entry(cur, 0); 43 | entry[1] = mrb_ary_entry(cur, 1); 44 | entry[2] = mrb_ary_entry(cur, 2); 45 | if (mrb_nil_p(entry[2])) 46 | entry[2] = mrb_aref(mrb, template, entry[0]); 47 | if (mrb_nil_p(entry[2])) 48 | entry[2] = DEFAULT_OBJECT_TYPE(mrb, self); 49 | 50 | mrb_funcall(mrb, self, "add_entry", 3, entry[0], entry[1], entry[2]); 51 | return mrb_false_value(); 52 | } 53 | 54 | static mrb_value ossl_x509name_initialize(mrb_state *mrb, mrb_value self) 55 | { 56 | X509_NAME *name; 57 | mrb_value arg, template; 58 | int argc; 59 | 60 | if (!(name = X509_NAME_new())) { 61 | mrb_raise(mrb, eX509NameError, NULL); 62 | } 63 | SetX509Name(self, name); 64 | 65 | argc = mrb_get_args(mrb, "|oo", &arg, &template); 66 | if (argc == 0) { 67 | return self; 68 | } else { 69 | mrb_value tmp = mrb_check_array_type(mrb, arg); 70 | if (!mrb_nil_p(tmp)) { 71 | mrb_value args; 72 | if (mrb_nil_p(template)) 73 | template = OBJECT_TYPE_TEMPLATE(mrb, self); 74 | args = mrb_ary_new(mrb); 75 | mrb_ary_push(mrb, args, self); 76 | mrb_ary_push(mrb, args, template); 77 | 78 | int len = RARRAY_LEN(tmp); 79 | for (int i = 0; i < len; ++i) { 80 | ossl_x509name_init_i(mrb, args, mrb_ary_entry(tmp, i)); 81 | } 82 | } else { 83 | const unsigned char *p; 84 | mrb_value str = ossl_to_der_if_possible(mrb, arg); 85 | X509_NAME *x; 86 | p = (unsigned char *)RSTRING_PTR(str); 87 | x = d2i_X509_NAME(&name, &p, RSTRING_LEN(str)); 88 | DATA_PTR(self) = name; 89 | if (!x) { 90 | mrb_raise(mrb, eX509NameError, NULL); 91 | } 92 | } 93 | } 94 | 95 | return self; 96 | } 97 | 98 | void Init_ossl_x509name(mrb_state *mrb) 99 | { 100 | eX509NameError = mrb_define_class_under(mrb, mX509, "NameError", eOSSLError); 101 | cX509Name = mrb_define_class_under(mrb, mX509, "Name", mrb->object_class); 102 | MRB_SET_INSTANCE_TT(cX509Name, MRB_TT_DATA); 103 | mrb_define_method(mrb, cX509Name, "initialize", ossl_x509name_initialize, MRB_ARGS_OPT(2)); 104 | mrb_define_method(mrb, cX509Name, "add_entry", ossl_x509name_add_entry, MRB_ARGS_ARG(2, 1)); 105 | } 106 | -------------------------------------------------------------------------------- /mrblib/acme/client.rb: -------------------------------------------------------------------------------- 1 | module Acme; end 2 | class Acme::Client; end 3 | module Acme::Client::Resources; end 4 | module Acme::Client::Resources::Challenges; end 5 | class Acme::Client::Resources::Challenges::Base; end 6 | class Acme::Client::Resources::Challenges::DNS01 < Acme::Client::Resources::Challenges::Base; end 7 | class Acme::Client::Resources::Challenges::HTTP01 < Acme::Client::Resources::Challenges::Base; end 8 | class Acme::Client::Resources::Challenges::TLSSNI01 < Acme::Client::Resources::Challenges::Base; end 9 | class OpenSSL::PKey::EC; end 10 | 11 | class Acme::Client 12 | DEFAULT_ENDPOINT = 'http://127.0.0.1:4000/'.freeze 13 | def initialize(private_key, endpoint=DEFAULT_ENDPOINT, connection_options={}) 14 | @endpoint, @private_key, @connection_options = endpoint, private_key, connection_options 15 | @nonces ||= [] 16 | load_directory! 17 | end 18 | 19 | attr_reader :private_key, :nonces, :endpoint, :operation_endpoints 20 | 21 | def crypto 22 | @_crypto ||= Acme::Client::Crypto.new(private_key) 23 | end 24 | 25 | def register(contact) 26 | payload = { 27 | resource: 'new-reg', 28 | contact: Array(contact), 29 | } 30 | 31 | response = connection.post(@operation_endpoints.fetch('new-reg'), payload) 32 | ::Acme::Client::Resources::Registration.new(self, response) 33 | end 34 | 35 | def authorize(domain) 36 | payload = { 37 | resource: 'new-authz', 38 | identifier: { 39 | type: 'dns', 40 | value: domain 41 | } 42 | } 43 | 44 | response = connection.post(@operation_endpoints.fetch('new-authz'), payload) 45 | ::Acme::Client::Resources::Authorization.new(self, response.headers['location'], response) 46 | end 47 | 48 | def fetch_authorization(uri) 49 | response = connection.get(uri) 50 | ::Acme::Client::Resources::Authorization.new(self, uri, response) 51 | end 52 | 53 | def new_certificate(csr) 54 | payload = { 55 | resource: 'new-cert', 56 | csr: Base64.urlsafe_base64(csr.to_der) 57 | } 58 | response = connection.post(@operation_endpoints.fetch('new-cert'), payload) 59 | ::Acme::Client::Certificate.new(OpenSSL::X509::Certificate.new(response.body), response.headers['location'], fetch_chain(response), csr) 60 | end 61 | 62 | def revoke_certificate(certificate) 63 | payload = { resource: 'revoke-cert', certificate: Base64.urlsafe_encode64(certificate.to_der) } 64 | endpoint = @operation_endpoints.fetch('revoke-cert') 65 | response = connection.post(endpoint, payload) 66 | response.success? 67 | end 68 | 69 | def self.revoke_certificate(certificate, *arguments) 70 | client = new(*arguments) 71 | client.revoke_certificate(certificate) 72 | end 73 | 74 | def connection 75 | @connection ||= CustomHttpRequest.new(@endpoint, self.crypto) 76 | end 77 | 78 | private 79 | 80 | def fetch_chain(response, limit = 10) 81 | links = response.headers['link'] 82 | if limit.zero? || links.nil? || links['up'].nil? 83 | [] 84 | else 85 | issuer = connection.get(links['up']) 86 | [OpenSSL::X509::Certificate.new(issuer.body), *fetch_chain(issuer, limit - 1)] 87 | end 88 | end 89 | 90 | def load_directory! 91 | response = connection.directory 92 | body = JSON.parse(response.body) 93 | @operation_endpoints = { 94 | 'new-reg' => body['new-reg'], 95 | 'new-authz' => body['new-authz'], 96 | 'new-cert' => body['new-cert'], 97 | 'revoke-cert' =>body['revoke-cert'] 98 | } 99 | rescue 100 | @operation_endpoints = { 101 | 'new-authz' => "/acme/new-authz", 102 | 'new-cert' => "/acme/new-cert", 103 | 'new-reg' => "/acme/new-reg", 104 | 'revoke-cert' => "/acme/revoke-cert" 105 | } 106 | end 107 | end 108 | -------------------------------------------------------------------------------- /src/ossl_digest.c: -------------------------------------------------------------------------------- 1 | 2 | //LICENSE: https://github.com/ruby/openssl/blob/master/LICENSE.txt 3 | #include "ossl.h" 4 | 5 | struct RClass *mDigest; 6 | struct RClass *eDigestError; 7 | 8 | #define SafeGetDigest(obj, ctx) \ 9 | do { \ 10 | OSSL_Check_Kind((mrb), (obj), cDigest); \ 11 | GetDigest((mrb), (obj), (ctx)); \ 12 | } while (0) 13 | 14 | #define SetDigest(obj, digest) \ 15 | do { \ 16 | if (!(digest)) { \ 17 | mrb_raise(mrb, E_RUNTIME_ERROR, "Digest wasn't initialized!"); \ 18 | } \ 19 | DATA_PTR(obj) = digest; \ 20 | DATA_TYPE(obj) = &ossl_digest_type; \ 21 | } while (0) 22 | const EVP_MD *GetDigestPtr(mrb_state *mrb, VALUE obj) 23 | { 24 | const EVP_MD *md; 25 | ASN1_OBJECT *oid = NULL; 26 | 27 | if (mrb_type(obj) == MRB_TT_STRING) { 28 | const char *name = mrb_str_to_cstr(mrb, obj); 29 | 30 | md = EVP_get_digestbyname(name); 31 | if (!md) { 32 | oid = OBJ_txt2obj(name, 0); 33 | md = EVP_get_digestbyobj(oid); 34 | ASN1_OBJECT_free(oid); 35 | } 36 | if (!md) 37 | mrb_raisef(mrb, E_RUNTIME_ERROR, "Unsupported digest algorithm (%s).", name); 38 | } else { 39 | EVP_MD_CTX *ctx; 40 | 41 | GetDigest(obj, ctx); 42 | 43 | md = EVP_MD_CTX_md(ctx); 44 | } 45 | 46 | return md; 47 | } 48 | 49 | static void ossl_digest_free(mrb_state *mrb, void *ctx) 50 | { 51 | EVP_MD_CTX_destroy(ctx); 52 | } 53 | static const mrb_data_type ossl_digest_type = {"OpenSSL/Digest", ossl_digest_free}; 54 | 55 | EVP_MD_CTX *ctx_new(mrb_state *mrb) 56 | { 57 | const EVP_MD *md; 58 | EVP_MD_CTX *ctx; 59 | md = EVP_get_digestbyname("SHA256"); 60 | ctx = EVP_MD_CTX_create(); 61 | 62 | if (!EVP_DigestInit_ex(ctx, md, NULL)) 63 | mrb_raise(mrb, eDigestError, "Digest initialization failed"); 64 | return ctx; 65 | } 66 | 67 | mrb_value mrb_ossl_digest_sha256_digest(mrb_state *mrb, mrb_value self) 68 | { 69 | const EVP_MD *md; 70 | EVP_MD_CTX *ctx; 71 | char *src; 72 | unsigned char buffer[SHA256_DIGEST_LENGTH]; 73 | mrb_get_args(mrb, "z", &src); 74 | 75 | ctx = DATA_PTR(self); 76 | 77 | if (!ctx) { 78 | ctx = ctx_new(mrb); 79 | } 80 | if (!EVP_DigestUpdate(ctx, src, strlen(src))) 81 | mrb_raise(mrb, eDigestError, "EVP_DigestUpdate"); 82 | 83 | if (!EVP_DigestFinal_ex(ctx, buffer, NULL)) 84 | mrb_raise(mrb, eDigestError, "EVP_DigestFinal_ex"); 85 | 86 | return mrb_str_new(mrb, (char *)buffer, EVP_MD_CTX_size(ctx)); 87 | } 88 | 89 | mrb_value mrb_ossl_digest_sha256_init(mrb_state *mrb, mrb_value self) 90 | { 91 | EVP_MD_CTX *ctx; 92 | char *src; 93 | unsigned char buffer[SHA256_DIGEST_LENGTH]; 94 | 95 | ctx = ctx_new(mrb); 96 | SetDigest(self, ctx); 97 | } 98 | 99 | void Init_ossl_digest(mrb_state *mrb) 100 | { 101 | struct RClass *ossl_digest_sha256; 102 | 103 | mDigest = mrb_define_module_under(mrb, mOSSL, "Digest"); 104 | eDigestError = mrb_define_class_under(mrb, mDigest, "DigestError", eOSSLError); 105 | 106 | ossl_digest_sha256 = mrb_define_class_under(mrb, mDigest, "SHA256", mrb->object_class); 107 | MRB_SET_INSTANCE_TT(ossl_digest_sha256, MRB_TT_DATA); 108 | 109 | mrb_define_method(mrb, ossl_digest_sha256, "initialize", mrb_ossl_digest_sha256_init, 110 | MRB_ARGS_NONE()); 111 | mrb_define_method(mrb, ossl_digest_sha256, "digest", mrb_ossl_digest_sha256_digest, 112 | MRB_ARGS_REQ(1)); 113 | } 114 | -------------------------------------------------------------------------------- /src/ossl_bn.c: -------------------------------------------------------------------------------- 1 | 2 | // LICENSE: https://github.com/ruby/openssl/blob/master/LICENSE.txt 3 | #include "ossl.h" 4 | 5 | struct RClass *cBN; 6 | struct RClass *eBNError; 7 | static void ossl_bn_free(mrb_state *mrb, void *ptr) 8 | { 9 | BN_clear_free(ptr); 10 | } 11 | static const mrb_data_type ossl_bn_type = {"OpenSSL/BN", ossl_bn_free}; 12 | 13 | BIGNUM *GetBNPtr(mrb_state *mrb, VALUE obj) 14 | { 15 | BIGNUM *bn = NULL; 16 | VALUE newobj; 17 | 18 | if (mrb_obj_is_kind_of(mrb, obj, cBN)) { 19 | GetBN(obj, bn); 20 | } else 21 | switch (mrb_type(obj)) { 22 | case MRB_TT_FIXNUM: 23 | newobj = NewBN(cBN); /* GC bug */ 24 | if (!BN_dec2bn(&bn, RSTRING_PTR(obj))) { 25 | mrb_raise(mrb, eBNError, NULL); 26 | } 27 | SetBN(newobj, bn); /* Handle potencial mem leaks */ 28 | break; 29 | default: 30 | mrb_raise(mrb, E_TYPE_ERROR, "Cannot convert into OpenSSL::BN"); 31 | } 32 | return bn; 33 | } 34 | 35 | mrb_value ossl_bn_new(mrb_state *mrb, const BIGNUM *bn) 36 | { 37 | BIGNUM *newbn; 38 | VALUE obj; 39 | 40 | obj = NewBN(cBN); 41 | newbn = bn ? BN_dup(bn) : BN_new(); 42 | if (!newbn) { 43 | mrb_raise(mrb, eBNError, NULL); 44 | } 45 | SetBN(obj, newbn); 46 | 47 | return obj; 48 | } 49 | 50 | static mrb_value mrb_ossl_bn_to_s(mrb_state *mrb, mrb_value self) 51 | { 52 | mrb_int base = 2; 53 | int len; 54 | BIGNUM *bn; 55 | mrb_value str, value_bn; 56 | 57 | mrb_get_args(mrb, "i", &base); 58 | 59 | bn = DATA_PTR(self); 60 | 61 | switch (base) { 62 | case 2: 63 | len = BN_num_bytes(bn); 64 | str = mrb_str_new(mrb, 0, len); 65 | if (BN_bn2bin(bn, (unsigned char *)RSTRING_PTR(str)) != len) 66 | mrb_raise(mrb, E_RUNTIME_ERROR, "BN bn2bin Error!"); 67 | break; 68 | default: 69 | mrb_raisef(mrb, E_RUNTIME_ERROR, "invalid radix %d", base); 70 | } 71 | 72 | return str; 73 | } 74 | static VALUE ossl_bn_initialize(mrb_state *mrb, VALUE self) 75 | { 76 | BIGNUM *bn; 77 | VALUE str, bs; 78 | int base = 10; 79 | 80 | if (!(bn = BN_new())) { 81 | mrb_raise(mrb, eBNError, NULL); 82 | } 83 | SetBN(self, bn); 84 | 85 | if (mrb_get_args(mrb, "S|i", &str, &bs) == 2) { 86 | base = NUM2INT(bs); 87 | } 88 | 89 | if (mrb_type(str) == MRB_TT_FIXNUM) { 90 | long i; 91 | unsigned char bin[sizeof(long)]; 92 | long n = FIX2LONG(str); 93 | unsigned long un = labs(n); 94 | 95 | for (i = sizeof(long) - 1; 0 <= i; i--) { 96 | bin[i] = un & 0xff; 97 | un >>= 8; 98 | } 99 | 100 | if (!BN_bin2bn(bin, sizeof(bin), bn)) { 101 | mrb_raise(mrb, eBNError, NULL); 102 | } 103 | if (n < 0) 104 | BN_set_negative(bn, 1); 105 | return self; 106 | } else { 107 | mrb_raise(mrb, eBNError, "undefined method"); 108 | } 109 | if (mrb_obj_is_kind_of(mrb, str, cBN)) { 110 | BIGNUM *other; 111 | 112 | GetBN(self, bn); 113 | GetBN(str, other); /* Safe - we checked kind_of? above */ 114 | if (!BN_copy(bn, other)) { 115 | mrb_raise(mrb, eBNError, NULL); 116 | } 117 | return self; 118 | } 119 | 120 | GetBN(self, bn); 121 | switch (base) { 122 | case 0: 123 | if (!BN_mpi2bn((unsigned char *)RSTRING_PTR(str), RSTRING_LEN(str), bn)) { 124 | mrb_raise(mrb, eBNError, NULL); 125 | } 126 | break; 127 | case 2: 128 | if (!BN_bin2bn((unsigned char *)RSTRING_PTR(str), RSTRING_LEN(str), bn)) { 129 | mrb_raise(mrb, eBNError, NULL); 130 | } 131 | break; 132 | case 10: 133 | if (!BN_dec2bn(&bn, RSTRING_PTR(str))) { 134 | mrb_raise(mrb, eBNError, NULL); 135 | } 136 | break; 137 | case 16: 138 | if (!BN_hex2bn(&bn, RSTRING_PTR(str))) { 139 | mrb_raise(mrb, eBNError, NULL); 140 | } 141 | break; 142 | default: 143 | mrb_raisef(mrb, E_ARGUMENT_ERROR, "invalid radix %d", base); 144 | } 145 | return self; 146 | } 147 | 148 | void Init_ossl_bn(mrb_state *mrb) 149 | { 150 | eBNError = mrb_define_class_under(mrb, mOSSL, "BNError", eOSSLError); 151 | 152 | cBN = mrb_define_class_under(mrb, mOSSL, "BN", mrb->object_class); 153 | MRB_SET_INSTANCE_TT(cBN, MRB_TT_DATA); 154 | mrb_define_method(mrb, cBN, "initialize", ossl_bn_initialize, MRB_ARGS_ARG(1, 1)); 155 | 156 | mrb_define_method(mrb, cBN, "to_s", mrb_ossl_bn_to_s, MRB_ARGS_REQ(1)); 157 | } 158 | -------------------------------------------------------------------------------- /src/ossl_pkey.c: -------------------------------------------------------------------------------- 1 | 2 | // LICENSE: https://github.com/ruby/openssl/blob/master/LICENSE.txt 3 | #include "ossl.h" 4 | 5 | struct RClass *ePKeyError; 6 | struct RClass *cPKey; 7 | struct RClass *mPKey; 8 | 9 | static void ossl_evp_pkey_free(mrb_state *mrb, void *ptr) 10 | { 11 | EVP_PKEY_free(ptr); 12 | } 13 | 14 | const mrb_data_type ossl_evp_pkey_type = {"OpenSSL/EVP_PKEY", ossl_evp_pkey_free}; 15 | 16 | mrb_value ossl_pkey_new(mrb_state *mrb, EVP_PKEY *pkey) 17 | { 18 | int type; 19 | #if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined LIBRESSL_VERSION_NUMBER) 20 | if (!pkey || (type = EVP_PKEY_base_id(pkey)) == EVP_PKEY_NONE) { 21 | #else 22 | if (!pkey) { 23 | #endif 24 | mrb_raise(mrb, ePKeyError, "Cannot make new key from NULL."); 25 | } 26 | 27 | #if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined LIBRESSL_VERSION_NUMBER) 28 | switch (type) { 29 | #else 30 | switch (EVP_PKEY_type(pkey->type)) { 31 | #endif 32 | case EVP_PKEY_RSA: 33 | return ossl_rsa_new(mrb, pkey); 34 | default: 35 | mrb_raise(mrb, ePKeyError, "unsupported key type"); 36 | } 37 | 38 | UNREACHABLE; 39 | } 40 | 41 | EVP_PKEY *GetPKeyPtr(mrb_state *mrb, mrb_value obj) 42 | { 43 | EVP_PKEY *pkey; 44 | 45 | GetPKey(obj, pkey); 46 | 47 | return pkey; 48 | } 49 | 50 | const EVP_MD *ossl_evp_get_digestbyname(mrb_value obj) 51 | { 52 | const EVP_MD *md; 53 | ASN1_OBJECT *oid = NULL; 54 | 55 | EVP_MD_CTX *ctx; 56 | 57 | GetDigest(obj, ctx); 58 | 59 | md = EVP_MD_CTX_md(ctx); 60 | 61 | return md; 62 | } 63 | 64 | static mrb_value mrb_ossl_pkey_sign(mrb_state *mrb, mrb_value self) 65 | { 66 | EVP_PKEY *pkey; 67 | #if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined LIBRESSL_VERSION_NUMBER) 68 | EVP_MD_CTX *ctx; 69 | #else 70 | EVP_MD_CTX ctx, *ictx; 71 | #endif 72 | 73 | unsigned int buf_len; 74 | mrb_value str, digest_instance, data; 75 | int result; 76 | 77 | if (!mrb_bool(mrb_funcall(mrb, self, "private?", 0, NULL))) { 78 | mrb_raise(mrb, ePKeyError, "Private key is needed."); 79 | } 80 | 81 | mrb_get_args(mrb, "oS", &digest_instance, &data); 82 | 83 | GetPKey(self, pkey); 84 | 85 | #if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined LIBRESSL_VERSION_NUMBER) 86 | const EVP_MD *md; 87 | ctx = EVP_MD_CTX_new(); 88 | md = ossl_evp_get_digestbyname(digest_instance); 89 | 90 | if (!EVP_SignInit_ex(ctx, md, NULL)) { 91 | EVP_MD_CTX_free(ctx); 92 | mrb_raise(mrb, ePKeyError, "EVP_SignInit_ex."); 93 | } 94 | 95 | if (!EVP_SignUpdate(ctx, RSTRING_PTR(data), RSTRING_LEN(data))) { 96 | EVP_MD_CTX_free(ctx); 97 | mrb_raise(mrb, ePKeyError, "EVP_SignUpdate."); 98 | } 99 | str = mrb_str_new(mrb, 0, EVP_PKEY_size(pkey)); 100 | result = EVP_SignFinal(ctx, (unsigned char *)RSTRING_PTR(str), &buf_len, pkey); 101 | #else 102 | ictx = DATA_PTR(digest_instance); 103 | EVP_SignInit(&ctx, ictx->digest); 104 | EVP_SignUpdate(&ctx, RSTRING_PTR(data), RSTRING_LEN(data)); 105 | str = mrb_str_new(mrb, 0, EVP_PKEY_size(pkey) + 16); 106 | result = EVP_SignFinal(&ctx, (unsigned char *)RSTRING_PTR(str), &buf_len, pkey); 107 | #endif 108 | 109 | #if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined LIBRESSL_VERSION_NUMBER) 110 | EVP_MD_CTX_free(ctx); 111 | #else 112 | EVP_MD_CTX_cleanup(&ctx); 113 | #endif 114 | 115 | if (!result) 116 | mrb_raise(mrb, ePKeyError, NULL); 117 | assert((long)buf_len <= RSTRING_LEN(str)); 118 | 119 | mrb_str_resize(mrb, str, mrb_fixnum(mrb_fixnum_value(buf_len))); 120 | return str; 121 | } 122 | 123 | mrb_value ossl_pkey_init(mrb_state *mrb, mrb_value klass) 124 | { 125 | return ossl_pkey_alloc(mrb, klass); 126 | } 127 | 128 | mrb_value ossl_pkey_alloc(mrb_state *mrb, mrb_value klass) 129 | { 130 | EVP_PKEY *pkey; 131 | 132 | if (!(pkey = EVP_PKEY_new())) { 133 | mrb_raise(mrb, ePKeyError, NULL); 134 | } 135 | SetPKey(klass, pkey); 136 | 137 | return klass; 138 | } 139 | 140 | EVP_PKEY *GetPrivPKeyPtr(mrb_state *mrb, VALUE obj) 141 | { 142 | EVP_PKEY *pkey; 143 | if (!mrb_bool(mrb_funcall(mrb, obj, "private?", 0, NULL))) { 144 | mrb_raise(mrb, E_ARGUMENT_ERROR, "Private key is needed."); 145 | } 146 | GetPKey(obj, pkey); 147 | 148 | return pkey; 149 | } 150 | void Init_ossl_pkey(mrb_state *mrb) 151 | { 152 | mPKey = mrb_define_module_under(mrb, mOSSL, "PKey"); 153 | ePKeyError = mrb_define_class_under(mrb, mPKey, "PKeyError", eOSSLError); 154 | cPKey = mrb_define_class_under(mrb, mPKey, "PKey", mrb->object_class); 155 | mrb_define_method(mrb, cPKey, "initialize", ossl_pkey_init, MRB_ARGS_NONE()); 156 | mrb_define_method(mrb, cPKey, "sign", mrb_ossl_pkey_sign, MRB_ARGS_REQ(2)); 157 | Init_ossl_rsa(mrb); 158 | } 159 | -------------------------------------------------------------------------------- /src/ossl_x509attr.c: -------------------------------------------------------------------------------- 1 | 2 | //LICENSE: https://github.com/ruby/openssl/blob/master/LICENSE.txt 3 | #include "ossl.h" 4 | struct RClass *cX509Attr; 5 | struct RClass *eX509AttrError; 6 | extern struct RClass *mX509; 7 | 8 | static void ossl_x509attr_free(mrb_state *mrb, void *ptr) 9 | { 10 | X509_ATTRIBUTE_free(ptr); 11 | } 12 | 13 | static const mrb_data_type ossl_x509attr_type = {"OpenSSL/X509/ATTRIBUTE", ossl_x509attr_free}; 14 | 15 | static VALUE ossl_x509attr_initialize(mrb_state *mrb, VALUE self) 16 | { 17 | VALUE oid, value; 18 | X509_ATTRIBUTE *attr, *x; 19 | const unsigned char *p; 20 | 21 | if (!(attr = X509_ATTRIBUTE_new())) 22 | mrb_raise(mrb, eX509AttrError, NULL); 23 | 24 | SetX509Attr(self, attr); 25 | if (mrb_get_args(mrb, "S|o", &oid, &value) == 1) { 26 | oid = ossl_to_der_if_possible(mrb, oid); 27 | p = (unsigned char *)RSTRING_PTR(oid); 28 | x = d2i_X509_ATTRIBUTE(&attr, &p, RSTRING_LEN(oid)); 29 | SetX509Attr(self, attr); 30 | if (!x) { 31 | mrb_raise(mrb, eX509AttrError, NULL); 32 | } 33 | return self; 34 | } 35 | mrb_funcall(mrb, self, "oid=", 1, oid); 36 | mrb_funcall(mrb, self, "value=", 1, value); 37 | 38 | return self; 39 | } 40 | #if defined(HAVE_ST_X509_ATTRIBUTE_SINGLE) || defined(HAVE_ST_SINGLE) 41 | #define OSSL_X509ATTR_IS_SINGLE(attr) ((attr)->single) 42 | #define OSSL_X509ATTR_SET_SINGLE(attr) ((attr)->single = 1) 43 | #else 44 | #define OSSL_X509ATTR_IS_SINGLE(attr) (!(attr)->value.set) 45 | #define OSSL_X509ATTR_SET_SINGLE(attr) ((attr)->value.set = 0) 46 | #endif 47 | static VALUE ossl_x509attr_set_value(mrb_state *mrb, VALUE self) 48 | { 49 | X509_ATTRIBUTE *attr; 50 | VALUE asn1_value; 51 | VALUE value; 52 | int i, asn1_tag; 53 | 54 | mrb_get_args(mrb, "o", &value); 55 | OSSL_Check_Kind(mrb, value, cASN1Data); 56 | asn1_tag = mrb_fixnum(mrb_attr_get(mrb, value, mrb_intern_lit(mrb, "@tag"))); 57 | asn1_value = mrb_attr_get(mrb, value, mrb_intern_lit(mrb, "@value")); 58 | 59 | if (asn1_tag != V_ASN1_SET) 60 | mrb_raise(mrb, eASN1Error, "argument must be ASN1::Set"); 61 | if (!mrb_type(asn1_value) == MRB_TT_ARRAY) 62 | mrb_raise(mrb, eASN1Error, "ASN1::Set has non-array value"); 63 | 64 | GetX509Attr(self, attr); 65 | if (X509_ATTRIBUTE_count(attr)) { /* populated, reset first */ 66 | ASN1_OBJECT *obj = X509_ATTRIBUTE_get0_object(attr); 67 | X509_ATTRIBUTE *new_attr = X509_ATTRIBUTE_create_by_OBJ(NULL, obj, 0, NULL, -1); 68 | if (!new_attr) 69 | mrb_raise(mrb, eX509AttrError, NULL); 70 | SetX509Attr(self, new_attr); 71 | X509_ATTRIBUTE_free(attr); 72 | attr = new_attr; 73 | } 74 | 75 | for (i = 0; i < RARRAY_LEN(asn1_value); i++) { 76 | ASN1_TYPE *a1type = ossl_asn1_get_asn1type(mrb, mrb_ary_entry(asn1_value, i)); 77 | if (!X509_ATTRIBUTE_set1_data(attr, ASN1_TYPE_get(a1type), a1type->value.ptr, -1)) { 78 | ASN1_TYPE_free(a1type); 79 | mrb_raise(mrb, eX509AttrError, NULL); 80 | } 81 | ASN1_TYPE_free(a1type); 82 | } 83 | 84 | return value; 85 | } 86 | 87 | static VALUE ossl_x509attr_set_oid(mrb_state *mrb, VALUE self) 88 | { 89 | X509_ATTRIBUTE *attr; 90 | ASN1_OBJECT *obj; 91 | char *s; 92 | VALUE oid; 93 | GetX509Attr(self, attr); 94 | 95 | mrb_get_args(mrb, "S", &oid); 96 | s = mrb_str_to_cstr(mrb, oid); 97 | obj = OBJ_txt2obj(s, 0); 98 | if (!obj) 99 | obj = OBJ_txt2obj(s, 1); 100 | if (!obj) 101 | mrb_raise(mrb, eX509AttrError, NULL); 102 | 103 | if (!X509_ATTRIBUTE_set1_object(attr, obj)) { 104 | ASN1_OBJECT_free(obj); 105 | mrb_raise(mrb, eX509AttrError, "X509_ATTRIBUTE_set1_object"); 106 | } 107 | ASN1_OBJECT_free(obj); 108 | return oid; 109 | } 110 | 111 | static VALUE ossl_x509attr_get_oid(mrb_state *mrb, VALUE self) 112 | { 113 | X509_ATTRIBUTE *attr; 114 | ASN1_OBJECT *oid; 115 | BIO *out; 116 | VALUE ret; 117 | int nid; 118 | 119 | GetX509Attr(self, attr); 120 | oid = X509_ATTRIBUTE_get0_object(attr); 121 | if ((nid = OBJ_obj2nid(oid)) != NID_undef) 122 | ret = mrb_str_new_cstr(mrb, OBJ_nid2sn(nid)); 123 | } 124 | 125 | void Init_ossl_x509attr(mrb_state *mrb) 126 | { 127 | eX509AttrError = mrb_define_class_under(mrb, mX509, "AttributeError", eOSSLError); 128 | 129 | cX509Attr = mrb_define_class_under(mrb, mX509, "Attribute", mrb->object_class); 130 | MRB_SET_INSTANCE_TT(cX509Attr, MRB_TT_DATA); 131 | mrb_define_method(mrb, cX509Attr, "initialize", ossl_x509attr_initialize, MRB_ARGS_ARG(1, 1)); 132 | mrb_define_method(mrb, cX509Attr, "oid=", ossl_x509attr_set_oid, MRB_ARGS_REQ(1)); 133 | mrb_define_method(mrb, cX509Attr, "oid", ossl_x509attr_get_oid, MRB_ARGS_NONE()); 134 | mrb_define_method(mrb, cX509Attr, "value=", ossl_x509attr_set_value, MRB_ARGS_REQ(1)); 135 | } 136 | -------------------------------------------------------------------------------- /src/ossl.h: -------------------------------------------------------------------------------- 1 | 2 | // LICENCE: https://github.com/ruby/openssl/blob/master/LICENSE.txt 3 | 4 | #if !defined(_OSSL_H_) 5 | #define _OSSL_H_ 6 | #include "mruby.h" 7 | #include "mruby/array.h" 8 | #include "mruby/class.h" 9 | #include "mruby/compile.h" 10 | #include "mruby/data.h" 11 | #include "mruby/hash.h" 12 | #if MRUBY_RELEASE_MAJOR > 3 || (MRUBY_RELEASE_MAJOR == 3 && MRUBY_RELEASE_MINOR >= 2) 13 | #include "mruby/internal.h" 14 | #endif 15 | #include "mruby/object.h" 16 | #include "mruby/string.h" 17 | #include "mruby/variable.h" 18 | #include 19 | #include 20 | 21 | extern struct RClass *mOSSL; 22 | extern struct RClass *eOSSLError; 23 | extern struct RClass *mOSSL; 24 | 25 | mrb_value ossl_to_der_if_possible(mrb_state *mrb, mrb_value obj); 26 | mrb_value ossl_to_der(mrb_state *mrb, mrb_value obj); 27 | mrb_value ossl_buf2str(mrb_state *mrb, char *buf, int len); 28 | mrb_value ossl_fetch_error(); 29 | 30 | int ossl_pem_passwd_cb(char *buf, int max_len, int flag, void *pwd); 31 | 32 | #include 33 | 34 | #ifdef HAVE_ASSERT_H 35 | #include 36 | #else 37 | #define assert(condition) 38 | #endif 39 | 40 | #if defined(_WIN32) 41 | #include 42 | #define OSSL_NO_CONF_API 1 43 | #if !defined(OPENSSL_SYS_WIN32) 44 | #define OPENSSL_SYS_WIN32 1 45 | #endif 46 | #include 47 | #endif 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | #if !defined(_WIN32) 59 | #include 60 | #endif 61 | #undef X509_NAME 62 | #undef PKCS7_SIGNER_INFO 63 | #if defined(HAVE_OPENSSL_ENGINE_H) && defined(HAVE_EVP_CIPHER_CTX_ENGINE) 64 | #define OSSL_ENGINE_ENABLED 65 | #include 66 | #endif 67 | #if defined(HAVE_OPENSSL_OCSP_H) 68 | #define OSSL_OCSP_ENABLED 69 | #include 70 | #endif 71 | 72 | #define VALUE mrb_value 73 | #define ID mrb_sym 74 | #define SYM2ID(mrb, o) mrb_sym2str(mrb, mrb_symbol(o)) 75 | #define ID2SYM(o) mrb_symbol(o) 76 | #define INT2NUM mrb_fixnum_value 77 | #define FIX2LONG mrb_fixnum 78 | #define NUM2INT mrb_fixnum 79 | #define NIL_P(name) mrb_nil_p(name) 80 | #define SYMBOL_P(name) mrb_symbol_p(name) 81 | #include "ossl_asn1.h" 82 | #include "ossl_bio.h" 83 | #include "ossl_bn.h" 84 | #include "ossl_config.h" 85 | #include "ossl_digest.h" 86 | #include "ossl_pkey.h" 87 | #include "ossl_pkey_rsa.h" 88 | #include "ossl_x509.h" 89 | #include "ossl_x509attr.h" 90 | #include "ossl_x509name.h" 91 | 92 | #define CLASS_NAME(mrb, obj) mrb_str_to_str(mrb, mrb_funcall(mrb, mrb_obj_value(obj), "name", 0)) 93 | #define RTEST(v) (!mrb_nil_p(v) && mrb_bool(v)) 94 | 95 | #ifndef UNREACHABLE 96 | #define UNREACHABLE /* unreachable */ 97 | #endif 98 | 99 | #ifdef HAVE_ASSERT_H 100 | #include 101 | #else 102 | #define assert(condition) 103 | #endif 104 | 105 | #define OSSL_Check_Kind(mrb, obj, klass) \ 106 | do { \ 107 | if (!mrb_obj_is_kind_of((mrb), (obj), (klass))) { \ 108 | mrb_raisef((mrb), E_TYPE_ERROR, "instance %S class %S", mrb_obj_class((mrb), (obj)), \ 109 | (klass)); \ 110 | } \ 111 | } while (0) 112 | 113 | #define ossl_str_adjust(mrb, str, p) \ 114 | do { \ 115 | long len = RSTRING_LEN(str); \ 116 | long newlen = (long)((p) - (unsigned char *)RSTRING_PTR(str)); \ 117 | assert(newlen <= len); \ 118 | mrb_str_resize((mrb), (str), newlen); \ 119 | } while (0) 120 | 121 | #define OSSL_BIO_reset(bio) \ 122 | (void)BIO_reset((bio)); \ 123 | ERR_clear_error(); 124 | #endif /* _OSSL_H_ */ 125 | #define GetDigest(obj, ctx) \ 126 | do { \ 127 | ctx = DATA_PTR(obj); \ 128 | } while (0) 129 | -------------------------------------------------------------------------------- /src/ossl_pkey_rsa.h: -------------------------------------------------------------------------------- 1 | 2 | // LICENSE: https://github.com/ruby/openssl/blob/master/LICENSE.txt 3 | #ifndef OSSL_PKEY 4 | #define OSSL_PKEY 5 | void Init_ossl_rsa(mrb_state *mrb); 6 | 7 | #define OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, _name, _get) \ 8 | /* \ 9 | * call-seq: \ 10 | * key.##name -> aBN \ 11 | */ \ 12 | static mrb_value ossl_##_keytype##_get_##_name(mrb_state *mrb, mrb_value self) \ 13 | { \ 14 | _type *obj; \ 15 | BIGNUM *bn; \ 16 | \ 17 | Get##_type(self, obj); \ 18 | _get; \ 19 | if (bn == NULL) \ 20 | return mrb_nil_value(); \ 21 | return ossl_bn_new(mrb, bn); \ 22 | } 23 | 24 | #if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined LIBRESSL_VERSION_NUMBER) 25 | #define GetPKeyRSA(obj, pkey) \ 26 | do { \ 27 | GetPKey((obj), (pkey)); \ 28 | if (EVP_PKEY_base_id(pkey) != EVP_PKEY_RSA) { \ 29 | mrb_raise(mrb, E_RUNTIME_ERROR, "THIS IS NOT A RSA!"); \ 30 | } \ 31 | } while (0) 32 | #else 33 | #define GetPKeyRSA(obj, pkey) \ 34 | do { \ 35 | GetPKey((obj), (pkey)); \ 36 | if (EVP_PKEY_type((pkey)->type) != EVP_PKEY_RSA) { /* PARANOIA? */ \ 37 | mrb_raise(mrb, E_RUNTIME_ERROR, "THIS IS NOT A RSA!"); \ 38 | } \ 39 | } while (0) 40 | 41 | 42 | #define OSSL_PKEY_BN(keytype, name) \ 43 | /* \ 44 | * call-seq: \ 45 | * key.##name -> aBN \ 46 | */ \ 47 | static mrb_value ossl_##keytype##_get_##name(mrb_state *mrb, mrb_value self) \ 48 | { \ 49 | EVP_PKEY *pkey; \ 50 | mrb_value value_pkey; \ 51 | BIGNUM *bn; \ 52 | \ 53 | pkey = DATA_PTR(self); \ 54 | bn = pkey->pkey.keytype->name; \ 55 | if (bn == NULL) \ 56 | return mrb_nil_value(); \ 57 | return ossl_bn_new(mrb, bn); \ 58 | } 59 | 60 | 61 | #endif 62 | 63 | mrb_value ossl_rsa_new(mrb_state *mrb, EVP_PKEY *pkey); 64 | 65 | #define OSSL_PKEY_BN_DEF_GETTER3(_keytype, _type, _group, a1, a2, a3) \ 66 | OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a1, _type##_get0_##_group(obj, &bn, NULL, NULL)) \ 67 | OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a2, _type##_get0_##_group(obj, NULL, &bn, NULL)) \ 68 | OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a3, _type##_get0_##_group(obj, NULL, NULL, &bn)) 69 | 70 | #define OSSL_PKEY_BN_DEF3(_keytype, _type, _group, a1, a2, a3) \ 71 | OSSL_PKEY_BN_DEF_GETTER3(_keytype, _type, _group, a1, a2, a3) 72 | 73 | #define DEF_OSSL_PKEY_BN(class, keytype, name) \ 74 | do { \ 75 | mrb_define_method(mrb, (class), #name, ossl_##keytype##_get_##name, MRB_ARGS_NONE()); \ 76 | } while (0) 77 | #endif /* _OSSL_PKEY */ 78 | -------------------------------------------------------------------------------- /src/ossl_x509req.c: -------------------------------------------------------------------------------- 1 | 2 | // LICENSE: https://github.com/ruby/openssl/blob/master/LICENSE.txt 3 | #include "ossl.h" 4 | 5 | extern struct RClass *mX509; 6 | struct RClass *eX509ReqError; 7 | struct RClass *cX509Req; 8 | #define SetX509Req(obj, req) \ 9 | do { \ 10 | if (!(req)) { \ 11 | mrb_raise((mrb), E_RUNTIME_ERROR, "Req wasn't initialized!"); \ 12 | } \ 13 | DATA_PTR(obj) = req; \ 14 | DATA_TYPE(obj) = &ossl_x509_request_type; \ 15 | } while (0) 16 | #define GetX509Req(obj, req) \ 17 | do { \ 18 | req = DATA_PTR(obj); \ 19 | } while (0) 20 | 21 | #define SafeGetX509Req(obj, req) \ 22 | do { \ 23 | OSSL_Check_Kind((mrb), (obj), cX509Req); \ 24 | GetX509Req((obj), (req)); \ 25 | } while (0) 26 | 27 | static void ossl_x509req_free(mrb_state *mrb, void *ptr) 28 | { 29 | X509_REQ_free(ptr); 30 | } 31 | 32 | static const mrb_data_type ossl_x509_request_type = {"OpenSSL/X509/REQ", ossl_x509req_free}; 33 | 34 | static mrb_value ossl_x509req_initialize(mrb_state *mrb, mrb_value self) 35 | { 36 | BIO *in; 37 | X509_REQ *req, *x; 38 | VALUE arg; 39 | 40 | if (!(x = X509_REQ_new())) { 41 | mrb_raise(mrb, eX509ReqError, NULL); 42 | } 43 | SetX509Req(self, x); 44 | 45 | if (mrb_get_args(mrb, "|o", &arg) == 0) 46 | return self; 47 | 48 | arg = ossl_to_der_if_possible(mrb, arg); 49 | in = ossl_obj2bio(mrb, arg); 50 | 51 | req = PEM_read_bio_X509_REQ(in, &x, NULL, NULL); 52 | 53 | SetX509Req(self, x); 54 | 55 | if (!req) { 56 | OSSL_BIO_reset(in); 57 | req = d2i_X509_REQ_bio(in, &x); 58 | SetX509Req(self, x); 59 | } 60 | BIO_free(in); 61 | if (!req) 62 | mrb_raise(mrb, eX509ReqError, NULL); 63 | 64 | return self; 65 | } 66 | 67 | static mrb_value ossl_x509req_set_public_key(mrb_state *mrb, mrb_value self) 68 | { 69 | X509_REQ *req; 70 | EVP_PKEY *pkey; 71 | mrb_value key; 72 | GetX509Req(self, req); 73 | mrb_get_args(mrb, "o", &key); 74 | pkey = GetPKeyPtr(mrb, key); /* NO NEED TO DUP */ 75 | if (!X509_REQ_set_pubkey(req, pkey)) { 76 | mrb_raise(mrb, eX509ReqError, NULL); 77 | } 78 | 79 | return key; 80 | } 81 | 82 | X509_NAME *GetX509NamePtr(mrb_state *mrb, mrb_value obj) 83 | { 84 | X509_NAME *name; 85 | 86 | GetX509Name(obj, name); 87 | 88 | return name; 89 | } 90 | static mrb_value ossl_x509req_set_subject(mrb_state *mrb, mrb_value self) 91 | { 92 | X509_REQ *req; 93 | mrb_value subject; 94 | 95 | mrb_get_args(mrb, "o", &subject); 96 | GetX509Req(self, req); 97 | /* DUPs name */ 98 | if (!X509_REQ_set_subject_name(req, GetX509NamePtr(mrb, subject))) { 99 | mrb_raise(mrb, eX509ReqError, NULL); 100 | } 101 | 102 | return subject; 103 | } 104 | static mrb_value ossl_x509req_set_version(mrb_state *mrb, mrb_value self) 105 | { 106 | X509_REQ *req; 107 | long ver; 108 | 109 | mrb_value version; 110 | 111 | mrb_get_args(mrb, "i", &version); 112 | 113 | if ((ver = mrb_fixnum(version)) < 0) { 114 | mrb_raise(mrb, eX509ReqError, "version must be >= 0!"); 115 | } 116 | GetX509Req(self, req); 117 | if (!X509_REQ_set_version(req, ver)) { 118 | mrb_raise(mrb, eX509ReqError, NULL); 119 | } 120 | 121 | return version; 122 | } 123 | 124 | X509_ATTRIBUTE *GetX509AttrPtr(mrb_state *mrb, VALUE obj) 125 | { 126 | X509_ATTRIBUTE *attr; 127 | 128 | GetX509Attr(obj, attr); 129 | 130 | return attr; 131 | } 132 | 133 | static VALUE ossl_x509req_add_attribute(mrb_state *mrb, VALUE self) 134 | { 135 | X509_REQ *req; 136 | VALUE attr; 137 | mrb_get_args(mrb, "o", &attr); 138 | 139 | GetX509Req(self, req); 140 | 141 | if (!X509_REQ_add1_attr(req, GetX509AttrPtr(mrb, attr))) { 142 | mrb_raisef(mrb, eX509ReqError, "missing add attribute:%S", ossl_fetch_error(mrb)); 143 | } 144 | 145 | return attr; 146 | } 147 | 148 | X509_REQ *GetX509ReqPtr(mrb_state *mrb, VALUE obj) 149 | { 150 | X509_REQ *req; 151 | 152 | GetX509Req(obj, req); 153 | 154 | return req; 155 | } 156 | 157 | static VALUE ossl_x509req_sign(mrb_state *mrb, VALUE self) 158 | { 159 | mrb_value key, digest; 160 | X509_REQ *req; 161 | EVP_PKEY *pkey; 162 | const EVP_MD *md; 163 | mrb_get_args(mrb, "oo", &key, &digest); 164 | 165 | GetX509Req(self, req); 166 | pkey = GetPrivPKeyPtr(mrb, key); /* NO NEED TO DUP */ 167 | md = GetDigestPtr(mrb, digest); 168 | if (!X509_REQ_sign(req, pkey, md)) { 169 | mrb_raise(mrb, eX509ReqError, NULL); 170 | } 171 | 172 | return self; 173 | } 174 | 175 | static VALUE ossl_x509req_to_der(mrb_state *mrb, VALUE self) 176 | { 177 | X509_REQ *req; 178 | VALUE str; 179 | long len; 180 | unsigned char *p; 181 | 182 | GetX509Req(self, req); 183 | if ((len = i2d_X509_REQ(req, NULL)) <= 0) 184 | mrb_raise(mrb, eX509ReqError, NULL); 185 | str = mrb_str_new(mrb, 0, len); 186 | p = (unsigned char *)RSTRING_PTR(str); 187 | if (i2d_X509_REQ(req, &p) <= 0) 188 | mrb_raise(mrb, eX509ReqError, NULL); 189 | ossl_str_adjust(mrb, str, p); 190 | 191 | return str; 192 | } 193 | 194 | void Init_ossl_x509req(mrb_state *mrb) 195 | { 196 | eX509ReqError = mrb_define_class_under(mrb, mX509, "RequestError", eOSSLError); 197 | 198 | cX509Req = mrb_define_class_under(mrb, mX509, "Request", mrb->object_class); 199 | MRB_SET_INSTANCE_TT(cX509Req, MRB_TT_DATA); 200 | mrb_define_method(mrb, cX509Req, "initialize", ossl_x509req_initialize, MRB_ARGS_OPT(1)); 201 | mrb_define_method(mrb, cX509Req, "public_key=", ossl_x509req_set_public_key, MRB_ARGS_REQ(1)); 202 | mrb_define_method(mrb, cX509Req, "subject=", ossl_x509req_set_subject, MRB_ARGS_REQ(1)); 203 | mrb_define_method(mrb, cX509Req, "version=", ossl_x509req_set_version, MRB_ARGS_REQ(1)); 204 | mrb_define_method(mrb, cX509Req, "add_attribute", ossl_x509req_add_attribute, MRB_ARGS_REQ(1)); 205 | mrb_define_method(mrb, cX509Req, "sign", ossl_x509req_sign, MRB_ARGS_REQ(2)); 206 | mrb_define_method(mrb, cX509Req, "to_der", ossl_x509req_to_der, MRB_ARGS_NONE()); 207 | } 208 | -------------------------------------------------------------------------------- /src/ossl_x509ext.c: -------------------------------------------------------------------------------- 1 | 2 | //LICENSE: https://github.com/ruby/openssl/blob/master/LICENSE.txt 3 | #include "ossl.h" 4 | struct RClass *cX509Ext; 5 | struct RClass *cX509ExtFactory; 6 | struct RClass *eX509ExtError; 7 | extern struct RClass *mX509; 8 | 9 | #define MakeX509ExtFactory(obj, ctx) \ 10 | do { \ 11 | if (!((ctx) = OPENSSL_malloc(sizeof(X509V3_CTX)))) \ 12 | mrb_raise(mrb, E_RUNTIME_ERROR, "CTX wasn't allocated!"); \ 13 | X509V3_set_ctx((ctx), NULL, NULL, NULL, NULL, 0); \ 14 | DATA_PTR(obj) = ctx; \ 15 | DATA_TYPE(obj) = &ossl_x509extfactory_type; \ 16 | } while (0) 17 | #define GetX509ExtFactory(obj, ctx) \ 18 | do { \ 19 | ctx = DATA_PTR(obj); \ 20 | } while (0) 21 | 22 | #define SetX509Ext(obj, ext) \ 23 | do { \ 24 | if (!(ext)) { \ 25 | mrb_raise(mrb, E_RUNTIME_ERROR, "EXT wasn't initialized!"); \ 26 | } \ 27 | DATA_PTR(obj) = ext; \ 28 | DATA_TYPE(obj) = &ossl_x509ext_type; \ 29 | } while (0) 30 | 31 | #define GetX509Ext(obj, ctx) \ 32 | do { \ 33 | ctx = DATA_PTR(obj); \ 34 | } while (0) 35 | 36 | static void ossl_x509extfactory_free(mrb_state *mrb, void *ctx) 37 | { 38 | OPENSSL_free(ctx); 39 | } 40 | static const mrb_data_type ossl_x509extfactory_type = {"OpenSSL/X509/EXTENSION/Factory", 41 | ossl_x509extfactory_free}; 42 | 43 | static void ossl_x509ext_free(mrb_state *mrb, void *ptr) 44 | { 45 | X509_EXTENSION_free(ptr); 46 | } 47 | static const mrb_data_type ossl_x509ext_type = {"OpenSSL/X509/EXTENSION", ossl_x509ext_free}; 48 | 49 | static VALUE ossl_x509extfactory_set_issuer_cert(mrb_state *mrb, VALUE self, VALUE cert) 50 | { 51 | X509V3_CTX *ctx; 52 | 53 | GetX509ExtFactory(self, ctx); 54 | mrb_iv_set(mrb, self, "@issuer_certificate", cert); 55 | ctx->issuer_cert = GetX509CertPtr(mrb, cert); /* NO DUP NEEDED */ 56 | 57 | return cert; 58 | } 59 | 60 | static VALUE ossl_x509extfactory_set_subject_cert(mrb_state *mrb, VALUE self, VALUE cert) 61 | { 62 | X509V3_CTX *ctx; 63 | GetX509ExtFactory(self, ctx); 64 | 65 | mrb_iv_set(mrb, self, "@subject_certificate", cert); 66 | ctx->subject_cert = GetX509CertPtr(mrb, cert); /* NO DUP NEEDED */ 67 | 68 | return cert; 69 | } 70 | 71 | static VALUE ossl_x509extfactory_set_subject_req(mrb_state *mrb, VALUE self, VALUE req) 72 | { 73 | X509V3_CTX *ctx; 74 | GetX509ExtFactory(self, ctx); 75 | mrb_iv_set(mrb, self, "@subject_request", req); 76 | ctx->subject_req = GetX509ReqPtr(mrb, req); /* NO DUP NEEDED */ 77 | 78 | return req; 79 | } 80 | 81 | static VALUE ossl_x509extfactory_set_crl(mrb_state *mrb, VALUE self, VALUE crl) 82 | { 83 | X509V3_CTX *ctx; 84 | 85 | GetX509ExtFactory(self, ctx); 86 | mrb_iv_set(mrb, self, "@crl", crl); 87 | ctx->crl = GetX509CRLPtr(mrb, crl); /* NO DUP NEEDED */ 88 | 89 | return crl; 90 | } 91 | 92 | static VALUE ossl_x509extfactory_initialize(mrb_state *mrb, VALUE self) 93 | { 94 | VALUE issuer_cert, subject_cert, subject_req, crl; 95 | X509V3_CTX *ctx; 96 | MakeX509ExtFactory(self, ctx); 97 | int argc; 98 | mrb_iv_set(mrb, self, "@config", mrb_nil_value()); 99 | 100 | argc = mrb_get_args(mrb, "|oooo", &issuer_cert, &subject_cert, &subject_req, &crl); 101 | 102 | if (argc == 1 && !NIL_P(issuer_cert)) 103 | ossl_x509extfactory_set_issuer_cert(mrb, self, issuer_cert); 104 | if (argc == 2 && !NIL_P(subject_cert)) 105 | ossl_x509extfactory_set_subject_cert(mrb, self, subject_cert); 106 | if (argc == 3 && !NIL_P(subject_req)) 107 | ossl_x509extfactory_set_subject_req(mrb, self, subject_req); 108 | if (argc == 4 && !NIL_P(crl)) 109 | ossl_x509extfactory_set_crl(mrb, self, crl); 110 | 111 | return self; 112 | } 113 | 114 | static VALUE ossl_x509ext_to_der(mrb_state *mrb, VALUE self) 115 | { 116 | X509_EXTENSION *ext; 117 | unsigned char *p; 118 | long len; 119 | VALUE str; 120 | 121 | GetX509Ext(self, ext); 122 | if ((len = i2d_X509_EXTENSION(ext, NULL)) <= 0) 123 | mrb_raise(mrb, eX509ExtError, NULL); 124 | str = mrb_str_new(mrb, 0, len); 125 | p = (unsigned char *)RSTRING_PTR(str); 126 | if (i2d_X509_EXTENSION(ext, &p) < 0) 127 | mrb_raise(mrb, eX509ExtError, NULL); 128 | ossl_str_adjust(mrb, str, p); 129 | 130 | return str; 131 | } 132 | 133 | static VALUE ossl_x509extfactory_create_ext(mrb_state *mrb, VALUE self) 134 | { 135 | X509V3_CTX *ctx; 136 | X509_EXTENSION *ext; 137 | VALUE oid, value, critical, valstr, obj; 138 | VALUE rconf; 139 | CONF *conf; 140 | int nid; 141 | 142 | mrb_get_args(mrb, "SS|o", &oid, &value, &critical); 143 | if (NIL_P(critical)) 144 | critical = mrb_false_value(); 145 | 146 | nid = OBJ_ln2nid(RSTRING_PTR(oid)); 147 | if (!nid) 148 | nid = OBJ_sn2nid(RSTRING_PTR(oid)); 149 | if (!nid) 150 | mrb_raisef(mrb, eX509ExtError, "unknown OID `%s'", RSTRING_PTR(oid)); 151 | valstr = mrb_str_new_cstr(mrb, RTEST(critical) ? "critical," : ""); 152 | mrb_str_append(mrb, valstr, value); 153 | GetX509ExtFactory(self, ctx); 154 | 155 | obj = mrb_class_new_instance(mrb, 0, NULL, cX509Ext); 156 | rconf = mrb_iv_get(mrb, self, "@config"); 157 | conf = NIL_P(rconf) ? NULL : GetConfigPtr(mrb, rconf); 158 | X509V3_set_nconf(ctx, conf); 159 | ext = X509V3_EXT_nconf_nid(conf, ctx, nid, RSTRING_PTR(valstr)); 160 | X509V3_set_ctx_nodb(ctx); 161 | 162 | if (!ext) { 163 | mrb_raise(mrb, eX509ExtError, NULL); 164 | } 165 | NCONF_free(conf); 166 | SetX509Ext(obj, ext); 167 | return obj; 168 | } 169 | 170 | static VALUE ossl_x509ext_init(mrb_state *mrb, VALUE self) 171 | { 172 | X509_EXTENSION *ext; 173 | 174 | if (!(ext = X509_EXTENSION_new())) { 175 | mrb_raise(mrb, eX509ExtError, NULL); 176 | } 177 | SetX509Ext(self, ext); 178 | return self; 179 | } 180 | 181 | void Init_ossl_x509ext(mrb_state *mrb) 182 | { 183 | eX509ExtError = mrb_define_class_under(mrb, mX509, "ExtensionError", eOSSLError); 184 | cX509ExtFactory = mrb_define_class_under(mrb, mX509, "ExtensionFactory", mrb->object_class); 185 | MRB_SET_INSTANCE_TT(cX509ExtFactory, MRB_TT_DATA); 186 | mrb_define_method(mrb, cX509ExtFactory, "initialize", ossl_x509extfactory_initialize, 187 | MRB_ARGS_OPT(4)); 188 | mrb_define_method(mrb, cX509ExtFactory, "create_ext", ossl_x509extfactory_create_ext, 189 | MRB_ARGS_ARG(2, 1)); 190 | 191 | cX509Ext = mrb_define_class_under(mrb, mX509, "Extension", mrb->object_class); 192 | MRB_SET_INSTANCE_TT(cX509Ext, MRB_TT_DATA); 193 | mrb_define_method(mrb, cX509Ext, "initialize", ossl_x509ext_init, MRB_ARGS_NONE()); 194 | mrb_define_method(mrb, cX509Ext, "to_der", ossl_x509ext_to_der, MRB_ARGS_NONE()); 195 | } 196 | -------------------------------------------------------------------------------- /src/ossl_pkey_rsa.c: -------------------------------------------------------------------------------- 1 | 2 | //LICENSE: https://github.com/ruby/openssl/blob/master/LICENSE.txt 3 | #include "ossl.h" 4 | 5 | struct RClass *cRSA; 6 | struct RClass *eRSAError; 7 | 8 | #if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined LIBRESSL_VERSION_NUMBER) 9 | static inline int 10 | RSA_HAS_PRIVATE(RSA *rsa) 11 | { 12 | const BIGNUM *p, *q; 13 | 14 | RSA_get0_factors(rsa, &p, &q); 15 | return p && q; /* d? why? */ 16 | } 17 | 18 | #define GetRSA(obj, rsa) do { \ 19 | EVP_PKEY *_pkey; \ 20 | GetPKeyRSA((obj), _pkey); \ 21 | (rsa) = EVP_PKEY_get0_RSA(_pkey); \ 22 | } while (0) 23 | 24 | #else 25 | #define RSA_HAS_PRIVATE(rsa) ((rsa)->p && (rsa)->q) 26 | #endif 27 | 28 | #define OSSL_PKEY_IS_PRIVATE(mrb, obj) (mrb_bool(mrb_iv_get((mrb), (obj), "private"))) 29 | #define RSA_PRIVATE(obj, rsa) (RSA_HAS_PRIVATE(rsa) || OSSL_PKEY_IS_PRIVATE(mrb, obj)) 30 | 31 | static RSA *rsa_generate(int size, unsigned long exp) 32 | { 33 | #if defined(HAVE_RSA_GENERATE_KEY_EX) && HAVE_BN_GENCB 34 | 35 | int i; 36 | BN_GENCB cb; 37 | struct ossl_generate_cb_arg cb_arg; 38 | struct rsa_blocking_gen_arg gen_arg; 39 | RSA *rsa = RSA_new(); 40 | BIGNUM *e = BN_new(); 41 | 42 | if (!rsa || !e) { 43 | if (e) 44 | BN_free(e); 45 | if (rsa) 46 | RSA_free(rsa); 47 | return 0; 48 | } 49 | for (i = 0; i < (int)sizeof(exp) * 8; ++i) { 50 | if (exp & (1UL << i)) { 51 | if (BN_set_bit(e, i) == 0) { 52 | BN_free(e); 53 | RSA_free(rsa); 54 | return 0; 55 | } 56 | } 57 | } 58 | 59 | memset(&cb_arg, 0, sizeof(struct ossl_generate_cb_arg)); 60 | if (rb_block_given_p()) 61 | cb_arg.yield = 1; 62 | BN_GENCB_set(&cb, ossl_generate_cb_2, &cb_arg); 63 | gen_arg.rsa = rsa; 64 | gen_arg.e = e; 65 | gen_arg.size = size; 66 | gen_arg.cb = &cb; 67 | /* we cannot release GVL when callback proc is supplied */ 68 | rsa_blocking_gen(&gen_arg); 69 | if (!gen_arg.result) { 70 | BN_free(e); 71 | RSA_free(rsa); 72 | if (cb_arg.state) 73 | rb_jump_tag(cb_arg.state); 74 | return 0; 75 | } 76 | 77 | BN_free(e); 78 | return rsa; 79 | #else 80 | return RSA_generate_key(size, exp, NULL, NULL); 81 | #endif 82 | } 83 | 84 | static void ossl_evp_pkey_free(mrb_state *mrb, void *ptr) 85 | { 86 | EVP_PKEY_free(ptr); 87 | } 88 | 89 | static const mrb_data_type ossl_evp_pkey_type = {"OpenSSL/EVP_PKEY", ossl_evp_pkey_free}; 90 | 91 | static mrb_value mrb_ossl_pkey_rsa_init(mrb_state *mrb, mrb_value self) 92 | { 93 | ossl_pkey_alloc(mrb, self); 94 | EVP_PKEY *pkey; 95 | RSA *rsa; 96 | BIO *in; 97 | int argc; 98 | char *passwd = NULL; 99 | mrb_value arg, pass; 100 | 101 | GetPKey(self, pkey); 102 | argc = mrb_get_args(mrb, "o|o", &arg, &pass); 103 | if (mrb_fixnum_p(arg) && mrb_fixnum(arg) == 0) { 104 | rsa = RSA_new(); 105 | } else if (mrb_fixnum_p(arg)) { 106 | rsa = rsa_generate(mrb_fixnum(arg), argc == 1 ? RSA_F4 : (unsigned)mrb_fixnum(pass)); 107 | if (!rsa) 108 | mrb_raise(mrb, eRSAError, NULL); 109 | } else { 110 | arg = ossl_to_der_if_possible(mrb, arg); 111 | in = ossl_obj2bio(mrb, arg); 112 | rsa = PEM_read_bio_RSAPrivateKey(in, NULL, ossl_pem_passwd_cb, passwd); 113 | if (!rsa) { 114 | OSSL_BIO_reset(in); 115 | rsa = PEM_read_bio_RSA_PUBKEY(in, NULL, NULL, NULL); 116 | } 117 | if (!rsa) { 118 | OSSL_BIO_reset(in); 119 | rsa = d2i_RSAPrivateKey_bio(in, NULL); 120 | } 121 | if (!rsa) { 122 | OSSL_BIO_reset(in); 123 | rsa = d2i_RSA_PUBKEY_bio(in, NULL); 124 | } 125 | if (!rsa) { 126 | OSSL_BIO_reset(in); 127 | rsa = PEM_read_bio_RSAPublicKey(in, NULL, NULL, NULL); 128 | } 129 | if (!rsa) { 130 | OSSL_BIO_reset(in); 131 | rsa = d2i_RSAPublicKey_bio(in, NULL); 132 | } 133 | BIO_free(in); 134 | if (!rsa) { 135 | mrb_raise(mrb, eRSAError, "Neither PUB key nor PRIV key"); 136 | } 137 | } 138 | 139 | if (!EVP_PKEY_assign_RSA(pkey, rsa)) { 140 | RSA_free(rsa); 141 | mrb_raise(mrb, eRSAError, NULL); 142 | } 143 | return self; 144 | } 145 | 146 | static mrb_value rsa_instance(mrb_state *mrb, struct RClass *klass, RSA *rsa) 147 | { 148 | EVP_PKEY *pkey; 149 | mrb_value obj; 150 | 151 | if (!rsa) { 152 | return mrb_nil_value(); 153 | } 154 | 155 | obj = NewPKey(klass); 156 | if (!(pkey = EVP_PKEY_new())) { 157 | return mrb_nil_value(); 158 | } 159 | if (!EVP_PKEY_assign_RSA(pkey, rsa)) { 160 | EVP_PKEY_free(pkey); 161 | return mrb_nil_value(); 162 | } 163 | SetPKey(obj, pkey); 164 | 165 | return obj; 166 | } 167 | 168 | mrb_value ossl_rsa_new(mrb_state *mrb, EVP_PKEY *pkey) 169 | { 170 | mrb_value obj; 171 | int type; 172 | 173 | if (!pkey) { 174 | obj = rsa_instance(mrb, cRSA, RSA_new()); 175 | } else { 176 | obj = NewPKey(mrb_class_get(mrb, "OpenSSL::PKey::RSA")); 177 | #if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined LIBRESSL_VERSION_NUMBER) 178 | if (EVP_PKEY_base_id(pkey) != EVP_PKEY_RSA) { 179 | #else 180 | if (EVP_PKEY_type(pkey->type) != EVP_PKEY_RSA) { 181 | #endif 182 | mrb_raise(mrb, E_TYPE_ERROR, "Not a RSA key!"); 183 | } 184 | 185 | SetPKey(obj, pkey); 186 | } 187 | if (mrb_nil_p(obj)) { 188 | mrb_raise(mrb, eRSAError, NULL); 189 | } 190 | 191 | return obj; 192 | } 193 | 194 | static mrb_value mrb_ossl_pkey_rsa_public_key(mrb_state *mrb, mrb_value self) 195 | { 196 | EVP_PKEY *pkey; 197 | RSA *rsa; 198 | mrb_value obj; 199 | 200 | GetPKeyRSA(self, pkey); 201 | 202 | #if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined LIBRESSL_VERSION_NUMBER) 203 | rsa = RSAPublicKey_dup(EVP_PKEY_get0_RSA(pkey)); 204 | #else 205 | rsa = RSAPublicKey_dup(pkey->pkey.rsa); 206 | #endif 207 | 208 | obj = rsa_instance(mrb, mrb_class(mrb, self), rsa); 209 | 210 | if (mrb_nil_p(obj)) { 211 | RSA_free(rsa); 212 | mrb_raise(mrb, eRSAError, NULL); 213 | } 214 | 215 | return obj; 216 | } 217 | 218 | mrb_value mrb_ossl_rsa_is_private(mrb_state *mrb, mrb_value self) 219 | { 220 | EVP_PKEY *pkey; 221 | GetPKey(self, pkey); 222 | #if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined LIBRESSL_VERSION_NUMBER) 223 | RSA *rsa; 224 | GetRSA(self, rsa); 225 | return RSA_PRIVATE(self, rsa) ? mrb_bool_value(true) : mrb_bool_value(false); 226 | #else 227 | return (RSA_PRIVATE(self, pkey->pkey.rsa)) ? mrb_bool_value(true) : mrb_bool_value(false); 228 | #endif 229 | } 230 | 231 | #if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined LIBRESSL_VERSION_NUMBER) 232 | OSSL_PKEY_BN_DEF3(rsa, RSA, key, n, e, d) 233 | #else 234 | OSSL_PKEY_BN(rsa, n) 235 | OSSL_PKEY_BN(rsa, e) 236 | #endif 237 | 238 | static VALUE ossl_rsa_export(mrb_state *mrb, VALUE self) 239 | { 240 | EVP_PKEY *pkey; 241 | BIO *out; 242 | const EVP_CIPHER *ciph = NULL; 243 | char *passwd = NULL; 244 | VALUE cipher, pass, str; 245 | 246 | GetPKeyRSA(self, pkey); 247 | 248 | int argc = mrb_get_args(mrb, "|oo", &cipher, &pass); 249 | 250 | if (argc > 0) { 251 | mrb_raise(mrb, E_RUNTIME_ERROR, "unsupport interface"); 252 | } 253 | if (!(out = BIO_new(BIO_s_mem()))) { 254 | mrb_raise(mrb, eRSAError, NULL); 255 | } 256 | #if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined LIBRESSL_VERSION_NUMBER) 257 | 258 | RSA *rsa; 259 | GetRSA(self, rsa); 260 | if (RSA_HAS_PRIVATE(rsa)) { 261 | if (!PEM_write_bio_RSAPrivateKey(out, rsa, ciph, NULL, 0, ossl_pem_passwd_cb, 262 | passwd)) { 263 | #else 264 | if (RSA_HAS_PRIVATE(pkey->pkey.rsa)) { 265 | if (!PEM_write_bio_RSAPrivateKey(out, pkey->pkey.rsa, ciph, NULL, 0, ossl_pem_passwd_cb, 266 | passwd)) { 267 | #endif 268 | BIO_free(out); 269 | mrb_raise(mrb, eRSAError, NULL); 270 | } 271 | } else { 272 | #if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined LIBRESSL_VERSION_NUMBER) 273 | if (!PEM_write_bio_RSA_PUBKEY(out, rsa)) { 274 | #else 275 | if (!PEM_write_bio_RSA_PUBKEY(out, pkey->pkey.rsa)) { 276 | #endif 277 | BIO_free(out); 278 | mrb_raise(mrb, eRSAError, NULL); 279 | } 280 | } 281 | str = ossl_membio2str(mrb, out); 282 | 283 | return str; 284 | } 285 | 286 | void Init_ossl_rsa(mrb_state *mrb) 287 | { 288 | 289 | cRSA = mrb_define_class_under(mrb, mPKey, "RSA", cPKey); 290 | MRB_SET_INSTANCE_TT(cRSA, MRB_TT_DATA); 291 | 292 | eRSAError = mrb_define_class_under(mrb, mPKey, "RSAError", ePKeyError); 293 | 294 | mrb_define_method(mrb, cRSA, "initialize", mrb_ossl_pkey_rsa_init, MRB_ARGS_ARG(1, 1)); 295 | mrb_define_method(mrb, cRSA, "public_key", mrb_ossl_pkey_rsa_public_key, MRB_ARGS_NONE()); 296 | mrb_define_method(mrb, cRSA, "private?", mrb_ossl_rsa_is_private, MRB_ARGS_NONE()); 297 | mrb_define_method(mrb, cRSA, "export", ossl_rsa_export, -1); 298 | mrb_define_alias(mrb, cRSA, "to_pem", "export"); 299 | 300 | DEF_OSSL_PKEY_BN(cRSA, rsa, n); 301 | DEF_OSSL_PKEY_BN(cRSA, rsa, e); 302 | } 303 | -------------------------------------------------------------------------------- /src/ossl_asn1.c: -------------------------------------------------------------------------------- 1 | #include "ossl.h" 2 | // LICENCE: https://github.com/ruby/openssl/blob/master/LICENSE.txt 3 | 4 | struct RClass *mASN1; 5 | struct RClass *eASN1Error; 6 | struct RClass *cASN1Data; 7 | struct RClass *cASN1Primitive; 8 | struct RClass *cASN1Constructive; 9 | 10 | struct RClass *cASN1EndOfContent; 11 | struct RClass *cASN1Boolean; /* BOOLEAN */ 12 | struct RClass *cASN1Integer, *cASN1Enumerated; /* INTEGER */ 13 | struct RClass *cASN1BitString; /* BIT STRING */ 14 | struct RClass *cASN1OctetString, *cASN1UTF8String; /* STRINGs */ 15 | struct RClass *cASN1NumericString, *cASN1PrintableString; 16 | struct RClass *cASN1T61String, *cASN1VideotexString; 17 | struct RClass *cASN1IA5String, *cASN1GraphicString; 18 | struct RClass *cASN1ISO64String, *cASN1GeneralString; 19 | struct RClass *cASN1UniversalString, *cASN1BMPString; 20 | struct RClass *cASN1Null; /* NULL */ 21 | struct RClass *cASN1ObjectId; /* OBJECT IDENTIFIER */ 22 | struct RClass *cASN1UTCTime, *cASN1GeneralizedTime; /* TIME */ 23 | struct RClass *cASN1Sequence, *cASN1Set; /* CONSTRUCTIVE */ 24 | 25 | static ID sIMPLICIT, sEXPLICIT; 26 | static ID sUNIVERSAL, sAPPLICATION, sCONTEXT_SPECIFIC, sPRIVATE; 27 | static ID sivVALUE, sivTAG, sivTAG_CLASS, sivTAGGING, sivINFINITE_LENGTH, sivUNUSED_BITS; 28 | #define ossl_asn1_get_value(o) mrb_attr_get(mrb, (o), sivVALUE) 29 | #define ossl_asn1_get_tag(o) mrb_attr_get(mrb, (o), sivTAG) 30 | #define ossl_asn1_get_tagging(o) mrb_attr_get(mrb, (o), sivTAGGING) 31 | #define ossl_asn1_get_tag_class(o) mrb_attr_get(mrb, (o), sivTAG_CLASS) 32 | #define ossl_asn1_get_infinite_length(o) mrb_attr_get(mrb, (o), sivINFINITE_LENGTH) 33 | 34 | #define ossl_asn1_set_value(o, v) mrb_iv_set(mrb, (o), sivVALUE, (v)) 35 | #define ossl_asn1_set_tag(o, v) mrb_iv_set(mrb, (o), sivTAG, (v)) 36 | #define ossl_asn1_set_tagging(o, v) mrb_iv_set(mrb, (o), sivTAGGING, (v)) 37 | #define ossl_asn1_set_tag_class(o, v) mrb_iv_set(mrb, (o), sivTAG_CLASS, (v)) 38 | #define ossl_asn1_set_infinite_length(o, v) mrb_iv_set(mrb, (o), sivINFINITE_LENGTH, (v)) 39 | 40 | #if OPENSSL_VERSION_NUMBER < 0x00908000L 41 | #define ossl_asn1_object_size(cons, len, tag) \ 42 | (cons) == 2 ? (len) + ASN1_object_size((cons), 0, (tag)) : ASN1_object_size((cons), (len), (tag)) 43 | #define ossl_asn1_put_object(pp, cons, len, tag, xc) \ 44 | (cons) == 2 ? ASN1_put_object((pp), (cons), 0, (tag), (xc)) \ 45 | : ASN1_put_object((pp), (cons), (len), (tag), (xc)) 46 | #else 47 | #define ossl_asn1_object_size(cons, len, tag) ASN1_object_size((cons), (len), (tag)) 48 | #define ossl_asn1_put_object(pp, cons, len, tag, xc) \ 49 | ASN1_put_object((pp), (cons), (len), (tag), (xc)) 50 | #endif 51 | 52 | typedef struct { 53 | const char *name; 54 | struct RClass *klass; 55 | } ossl_asn1_info_t; 56 | static const ossl_asn1_info_t ossl_asn1_info[] = { 57 | { 58 | "EOC", &cASN1EndOfContent, 59 | }, /* 0 */ 60 | { 61 | "BOOLEAN", &cASN1Boolean, 62 | }, /* 1 */ 63 | { 64 | "INTEGER", &cASN1Integer, 65 | }, /* 2 */ 66 | { 67 | "BIT_STRING", &cASN1BitString, 68 | }, /* 3 */ 69 | { 70 | "OCTET_STRING", &cASN1OctetString, 71 | }, /* 4 */ 72 | { 73 | "NULL", &cASN1Null, 74 | }, /* 5 */ 75 | { 76 | "OBJECT", &cASN1ObjectId, 77 | }, /* 6 */ 78 | { 79 | "OBJECT_DESCRIPTOR", NULL, 80 | }, /* 7 */ 81 | { 82 | "EXTERNAL", NULL, 83 | }, /* 8 */ 84 | { 85 | "REAL", NULL, 86 | }, /* 9 */ 87 | { 88 | "ENUMERATED", &cASN1Enumerated, 89 | }, /* 10 */ 90 | { 91 | "EMBEDDED_PDV", NULL, 92 | }, /* 11 */ 93 | { 94 | "UTF8STRING", &cASN1UTF8String, 95 | }, /* 12 */ 96 | { 97 | "RELATIVE_OID", NULL, 98 | }, /* 13 */ 99 | { 100 | "[UNIVERSAL 14]", NULL, 101 | }, /* 14 */ 102 | { 103 | "[UNIVERSAL 15]", NULL, 104 | }, /* 15 */ 105 | { 106 | "SEQUENCE", &cASN1Sequence, 107 | }, /* 16 */ 108 | { 109 | "SET", &cASN1Set, 110 | }, /* 17 */ 111 | { 112 | "NUMERICSTRING", &cASN1NumericString, 113 | }, /* 18 */ 114 | { 115 | "PRINTABLESTRING", &cASN1PrintableString, 116 | }, /* 19 */ 117 | { 118 | "T61STRING", &cASN1T61String, 119 | }, /* 20 */ 120 | { 121 | "VIDEOTEXSTRING", &cASN1VideotexString, 122 | }, /* 21 */ 123 | { 124 | "IA5STRING", &cASN1IA5String, 125 | }, /* 22 */ 126 | { 127 | "UTCTIME", &cASN1UTCTime, 128 | }, /* 23 */ 129 | { 130 | "GENERALIZEDTIME", &cASN1GeneralizedTime, 131 | }, /* 24 */ 132 | { 133 | "GRAPHICSTRING", &cASN1GraphicString, 134 | }, /* 25 */ 135 | { 136 | "ISO64STRING", &cASN1ISO64String, 137 | }, /* 26 */ 138 | { 139 | "GENERALSTRING", &cASN1GeneralString, 140 | }, /* 27 */ 141 | { 142 | "UNIVERSALSTRING", &cASN1UniversalString, 143 | }, /* 28 */ 144 | { 145 | "CHARACTER_STRING", NULL, 146 | }, /* 29 */ 147 | { 148 | "BMPSTRING", &cASN1BMPString, 149 | }, /* 30 */ 150 | }; 151 | 152 | mrb_value get_class_tag_map(mrb_state *mrb) 153 | { 154 | mrb_value class_tag_map; 155 | class_tag_map = mrb_hash_new(mrb); 156 | 157 | mrb_hash_set(mrb, class_tag_map, CLASS_NAME(mrb, cASN1EndOfContent), INT2NUM(V_ASN1_EOC)); 158 | mrb_hash_set(mrb, class_tag_map, CLASS_NAME(mrb, cASN1Boolean), INT2NUM(V_ASN1_BOOLEAN)); 159 | mrb_hash_set(mrb, class_tag_map, CLASS_NAME(mrb, cASN1Integer), INT2NUM(V_ASN1_INTEGER)); 160 | mrb_hash_set(mrb, class_tag_map, CLASS_NAME(mrb, cASN1BitString), INT2NUM(V_ASN1_BIT_STRING)); 161 | mrb_hash_set(mrb, class_tag_map, CLASS_NAME(mrb, cASN1OctetString), INT2NUM(V_ASN1_OCTET_STRING)); 162 | mrb_hash_set(mrb, class_tag_map, CLASS_NAME(mrb, cASN1Null), INT2NUM(V_ASN1_NULL)); 163 | mrb_hash_set(mrb, class_tag_map, CLASS_NAME(mrb, cASN1ObjectId), INT2NUM(V_ASN1_OBJECT)); 164 | mrb_hash_set(mrb, class_tag_map, CLASS_NAME(mrb, cASN1Enumerated), INT2NUM(V_ASN1_ENUMERATED)); 165 | mrb_hash_set(mrb, class_tag_map, CLASS_NAME(mrb, cASN1UTF8String), INT2NUM(V_ASN1_UTF8STRING)); 166 | mrb_hash_set(mrb, class_tag_map, CLASS_NAME(mrb, cASN1Sequence), INT2NUM(V_ASN1_SEQUENCE)); 167 | mrb_hash_set(mrb, class_tag_map, CLASS_NAME(mrb, cASN1Set), INT2NUM(V_ASN1_SET)); 168 | mrb_hash_set(mrb, class_tag_map, CLASS_NAME(mrb, cASN1NumericString), 169 | INT2NUM(V_ASN1_NUMERICSTRING)); 170 | mrb_hash_set(mrb, class_tag_map, CLASS_NAME(mrb, cASN1PrintableString), 171 | INT2NUM(V_ASN1_PRINTABLESTRING)); 172 | mrb_hash_set(mrb, class_tag_map, CLASS_NAME(mrb, cASN1T61String), INT2NUM(V_ASN1_T61STRING)); 173 | mrb_hash_set(mrb, class_tag_map, CLASS_NAME(mrb, cASN1VideotexString), 174 | INT2NUM(V_ASN1_VIDEOTEXSTRING)); 175 | mrb_hash_set(mrb, class_tag_map, CLASS_NAME(mrb, cASN1IA5String), INT2NUM(V_ASN1_IA5STRING)); 176 | mrb_hash_set(mrb, class_tag_map, CLASS_NAME(mrb, cASN1UTCTime), INT2NUM(V_ASN1_UTCTIME)); 177 | mrb_hash_set(mrb, class_tag_map, CLASS_NAME(mrb, cASN1GeneralizedTime), 178 | INT2NUM(V_ASN1_GENERALIZEDTIME)); 179 | mrb_hash_set(mrb, class_tag_map, CLASS_NAME(mrb, cASN1GraphicString), 180 | INT2NUM(V_ASN1_GRAPHICSTRING)); 181 | mrb_hash_set(mrb, class_tag_map, CLASS_NAME(mrb, cASN1ISO64String), INT2NUM(V_ASN1_ISO64STRING)); 182 | mrb_hash_set(mrb, class_tag_map, CLASS_NAME(mrb, cASN1GeneralString), 183 | INT2NUM(V_ASN1_GENERALSTRING)); 184 | mrb_hash_set(mrb, class_tag_map, CLASS_NAME(mrb, cASN1UniversalString), 185 | INT2NUM(V_ASN1_UNIVERSALSTRING)); 186 | mrb_hash_set(mrb, class_tag_map, CLASS_NAME(mrb, cASN1BMPString), INT2NUM(V_ASN1_BMPSTRING)); 187 | return class_tag_map; 188 | } 189 | 190 | static VALUE ossl_asn1data_initialize(mrb_state *mrb, VALUE self) 191 | { 192 | VALUE value, tag, tag_class; 193 | if (!SYMBOL_P(tag_class)) 194 | 195 | mrb_raise(mrb, eASN1Error, "invalid tag class"); 196 | if ((mrb_intern_str(mrb, tag_class) == sUNIVERSAL) && NUM2INT(tag) > 31) 197 | mrb_raise(mrb, eASN1Error, "tag number for Universal too large"); 198 | ossl_asn1_set_tag(self, tag); 199 | ossl_asn1_set_value(self, value); 200 | ossl_asn1_set_tag_class(self, tag_class); 201 | ossl_asn1_set_infinite_length(self, mrb_false_value()); 202 | 203 | return self; 204 | } 205 | 206 | static int ossl_asn1_default_tag(mrb_state *mrb, VALUE obj) 207 | { 208 | VALUE tag; 209 | struct RClass *tmp_class; 210 | mrb_value class_tag_map; 211 | 212 | class_tag_map = get_class_tag_map(mrb); 213 | tmp_class = mrb_class(mrb, obj); 214 | while (tmp_class) { 215 | tag = mrb_hash_get(mrb, class_tag_map, CLASS_NAME(mrb, tmp_class)); 216 | if (!mrb_nil_p(tag)) { 217 | return NUM2INT(tag); 218 | } 219 | tmp_class = tmp_class->super; 220 | } 221 | mrb_raisef(mrb, eASN1Error, "universal tag for %s not found", mrb_obj_class(mrb, obj)); 222 | 223 | return -1; /* dummy */ 224 | } 225 | 226 | static ASN1_BOOLEAN *obj_to_asn1bool(mrb_state *mrb, VALUE obj) 227 | { 228 | if (mrb_nil_p(obj)) 229 | mrb_raise(mrb, E_TYPE_ERROR, "Can't convert nil into Boolean"); 230 | 231 | #if OPENSSL_VERSION_NUMBER < 0x00907000L 232 | return RTEST(obj) ? 0xff : 0x100; 233 | #else 234 | return RTEST(obj) ? 0xff : 0x0; 235 | #endif 236 | } 237 | #if DO_IT_VIA_RUBY 238 | ASN1_INTEGER *num_to_asn1integer(mrb_state *mrb, VALUE obj, ASN1_INTEGER *ai) 239 | { 240 | BIGNUM *bn = NULL; 241 | 242 | if (RTEST(mrb_obj_is_kind_of(mrb, obj, cBN))) { 243 | bn = GetBNPtr(mrb, obj); 244 | } else { 245 | obj = rb_String(obj); 246 | if (!BN_dec2bn(&bn, StringValuePtr(obj))) { 247 | mrb_raise(mrb, eOSSLError, NULL); 248 | } 249 | } 250 | if (!(ai = BN_to_ASN1_INTEGER(bn, ai))) { 251 | BN_free(bn); 252 | mrb_raise(mrb, eOSSLError, NULL); 253 | } 254 | BN_free(bn); 255 | return ai; 256 | } 257 | #else 258 | ASN1_INTEGER *num_to_asn1integer(mrb_state *mrb, VALUE obj, ASN1_INTEGER *ai) 259 | { 260 | BIGNUM *bn; 261 | 262 | if (mrb_nil_p(obj)) 263 | mrb_raise(mrb, E_TYPE_ERROR, "Can't convert nil into Integer"); 264 | 265 | bn = GetBNPtr(mrb, obj); 266 | 267 | if (!(ai = BN_to_ASN1_INTEGER(bn, ai))) 268 | mrb_raise(mrb, eOSSLError, NULL); 269 | 270 | return ai; 271 | } 272 | #endif 273 | 274 | static ASN1_INTEGER *obj_to_asn1int(mrb_state *mrb, VALUE obj) 275 | { 276 | return num_to_asn1integer(mrb, obj, NULL); 277 | } 278 | 279 | static ASN1_BIT_STRING *obj_to_asn1bstr(mrb_state *mrb, VALUE obj, long unused_bits) 280 | { 281 | ASN1_BIT_STRING *bstr; 282 | 283 | if (unused_bits < 0) 284 | unused_bits = 0; 285 | if (!(bstr = ASN1_BIT_STRING_new())) 286 | mrb_raise(mrb, eASN1Error, NULL); 287 | ASN1_BIT_STRING_set(bstr, (unsigned char *)RSTRING_PTR(obj), RSTRING_LEN(obj)); 288 | bstr->flags &= ~(ASN1_STRING_FLAG_BITS_LEFT | 0x07); /* clear */ 289 | bstr->flags |= ASN1_STRING_FLAG_BITS_LEFT | (unused_bits & 0x07); 290 | 291 | return bstr; 292 | } 293 | 294 | static ASN1_NULL *obj_to_asn1null(mrb_state *mrb, VALUE obj) 295 | { 296 | ASN1_NULL *null; 297 | 298 | if (!mrb_nil_p(obj)) 299 | mrb_raise(mrb, eASN1Error, "nil expected"); 300 | if (!(null = ASN1_NULL_new())) 301 | mrb_raise(mrb, eASN1Error, NULL); 302 | 303 | return null; 304 | } 305 | 306 | static ASN1_STRING *obj_to_asn1str(mrb_state *mrb, VALUE obj) 307 | { 308 | ASN1_STRING *str; 309 | 310 | if (!(str = ASN1_STRING_new())) 311 | mrb_raise(mrb, eASN1Error, NULL); 312 | ASN1_STRING_set(str, RSTRING_PTR(obj), RSTRING_LEN(obj)); 313 | 314 | return str; 315 | } 316 | 317 | time_t time_to_time_t(VALUE time) 318 | { 319 | return (time_t)mrb_fixnum(time); 320 | } 321 | 322 | static ASN1_UTCTIME *obj_to_asn1utime(mrb_state *mrb, VALUE time) 323 | { 324 | time_t sec; 325 | ASN1_UTCTIME *t; 326 | 327 | sec = time_to_time_t(time); 328 | if (!(t = ASN1_UTCTIME_set(NULL, sec))) 329 | mrb_raise(mrb, eASN1Error, NULL); 330 | 331 | return t; 332 | } 333 | static ASN1_OBJECT *obj_to_asn1obj(mrb_state *mrb, VALUE obj) 334 | { 335 | ASN1_OBJECT *a1obj; 336 | 337 | a1obj = OBJ_txt2obj(RSTRING_PTR(obj), 0); 338 | if (!a1obj) 339 | a1obj = OBJ_txt2obj(RSTRING_PTR(obj), 1); 340 | if (!a1obj) 341 | mrb_raise(mrb, eASN1Error, "invalid OBJECT ID"); 342 | 343 | return a1obj; 344 | } 345 | 346 | static ASN1_GENERALIZEDTIME *obj_to_asn1gtime(mrb_state *mrb, VALUE time) 347 | { 348 | time_t sec; 349 | ASN1_GENERALIZEDTIME *t; 350 | 351 | sec = time_to_time_t(time); 352 | if (!(t = ASN1_GENERALIZEDTIME_set(NULL, sec))) 353 | mrb_raise(mrb, eASN1Error, NULL); 354 | 355 | return t; 356 | } 357 | 358 | static ASN1_STRING *obj_to_asn1derstr(mrb_state *mrb, VALUE obj) 359 | { 360 | ASN1_STRING *a1str; 361 | VALUE str; 362 | 363 | str = ossl_to_der(mrb, obj); 364 | if (!(a1str = ASN1_STRING_new())) 365 | mrb_raise(mrb, eASN1Error, NULL); 366 | 367 | ASN1_STRING_set(a1str, RSTRING_PTR(str), RSTRING_LEN(str)); 368 | 369 | return a1str; 370 | } 371 | 372 | ASN1_TYPE *ossl_asn1_get_asn1type(mrb_state *mrb, VALUE obj) 373 | { 374 | ASN1_TYPE *ret; 375 | VALUE value, rflag; 376 | void *ptr; 377 | void (*free_func)(); 378 | int tag, flag; 379 | 380 | tag = ossl_asn1_default_tag(mrb, obj); 381 | value = ossl_asn1_get_value(obj); 382 | switch (tag) { 383 | case V_ASN1_BOOLEAN: 384 | // ptr = (void *)(VALUE)obj_to_asn1bool(mrb, value); 385 | ptr = obj_to_asn1bool(mrb, value); 386 | free_func = NULL; 387 | break; 388 | case V_ASN1_INTEGER: /* FALLTHROUGH */ 389 | case V_ASN1_ENUMERATED: 390 | ptr = obj_to_asn1int(mrb, value); 391 | free_func = ASN1_INTEGER_free; 392 | break; 393 | case V_ASN1_BIT_STRING: 394 | rflag = mrb_attr_get(mrb, obj, sivUNUSED_BITS); 395 | flag = mrb_nil_p(rflag) ? -1 : NUM2INT(rflag); 396 | ptr = obj_to_asn1bstr(mrb, value, flag); 397 | free_func = ASN1_BIT_STRING_free; 398 | break; 399 | case V_ASN1_NULL: 400 | ptr = obj_to_asn1null(mrb, value); 401 | free_func = ASN1_NULL_free; 402 | break; 403 | case V_ASN1_OCTET_STRING: /* FALLTHROUGH */ 404 | case V_ASN1_UTF8STRING: /* FALLTHROUGH */ 405 | case V_ASN1_NUMERICSTRING: /* FALLTHROUGH */ 406 | case V_ASN1_PRINTABLESTRING: /* FALLTHROUGH */ 407 | case V_ASN1_T61STRING: /* FALLTHROUGH */ 408 | case V_ASN1_VIDEOTEXSTRING: /* FALLTHROUGH */ 409 | case V_ASN1_IA5STRING: /* FALLTHROUGH */ 410 | case V_ASN1_GRAPHICSTRING: /* FALLTHROUGH */ 411 | case V_ASN1_ISO64STRING: /* FALLTHROUGH */ 412 | case V_ASN1_GENERALSTRING: /* FALLTHROUGH */ 413 | case V_ASN1_UNIVERSALSTRING: /* FALLTHROUGH */ 414 | case V_ASN1_BMPSTRING: 415 | ptr = obj_to_asn1str(mrb, value); 416 | free_func = ASN1_STRING_free; 417 | break; 418 | case V_ASN1_OBJECT: 419 | ptr = obj_to_asn1obj(mrb, value); 420 | free_func = ASN1_OBJECT_free; 421 | break; 422 | case V_ASN1_UTCTIME: 423 | ptr = obj_to_asn1utime(mrb, value); 424 | free_func = ASN1_TIME_free; 425 | break; 426 | case V_ASN1_GENERALIZEDTIME: 427 | ptr = obj_to_asn1gtime(mrb, value); 428 | free_func = ASN1_TIME_free; 429 | break; 430 | case V_ASN1_SET: /* FALLTHROUGH */ 431 | case V_ASN1_SEQUENCE: 432 | ptr = obj_to_asn1derstr(mrb, obj); 433 | free_func = ASN1_STRING_free; 434 | break; 435 | default: 436 | mrb_raise(mrb, eASN1Error, "unsupported ASN.1 type"); 437 | } 438 | if (!(ret = OPENSSL_malloc(sizeof(ASN1_TYPE)))) { 439 | if (free_func) 440 | free_func(ptr); 441 | mrb_raise(mrb, eASN1Error, "ASN1_TYPE alloc failure"); 442 | } 443 | memset(ret, 0, sizeof(ASN1_TYPE)); 444 | ASN1_TYPE_set(ret, tag, ptr); 445 | 446 | return ret; 447 | } 448 | 449 | static int ossl_asn1_tag_class(mrb_state *mrb, VALUE obj) 450 | { 451 | VALUE s; 452 | int ret = -1; 453 | 454 | s = ossl_asn1_get_tag_class(obj); 455 | if (NIL_P(s)) 456 | ret = V_ASN1_UNIVERSAL; 457 | else if (mrb_string_p(s)) { 458 | if (mrb_intern_str(mrb, s) == sUNIVERSAL) 459 | ret = V_ASN1_UNIVERSAL; 460 | else if (mrb_intern_str(mrb, s) == sAPPLICATION) 461 | ret = V_ASN1_APPLICATION; 462 | else if (mrb_intern_str(mrb, s) == sCONTEXT_SPECIFIC) 463 | ret = V_ASN1_CONTEXT_SPECIFIC; 464 | else if (mrb_intern_str(mrb, s) == sPRIVATE) 465 | ret = V_ASN1_PRIVATE; 466 | } 467 | if (ret < 0) { 468 | mrb_raise(mrb, eASN1Error, "invalid tag class"); 469 | } 470 | 471 | return ret; 472 | } 473 | 474 | static int ossl_asn1_is_explicit(mrb_state *mrb, VALUE obj) 475 | { 476 | VALUE s; 477 | int ret = -1; 478 | 479 | s = ossl_asn1_get_tagging(obj); 480 | if (NIL_P(s)) 481 | return 0; 482 | else if (SYMBOL_P(s)) { 483 | if (mrb_intern_str(mrb, s) == sIMPLICIT) 484 | ret = 0; 485 | else if (mrb_intern_str(mrb, s) == sEXPLICIT) 486 | ret = 1; 487 | } 488 | if (ret < 0) { 489 | mrb_raise(mrb, eASN1Error, "invalid tag default"); 490 | } 491 | 492 | return ret; 493 | } 494 | 495 | static VALUE join_der_i(mrb_state *mrb, mrb_value str, mrb_value current) 496 | { 497 | mrb_value i; 498 | i = ossl_to_der_if_possible(mrb, current); 499 | mrb_str_append(mrb, str, i); 500 | return mrb_nil_value(); 501 | } 502 | 503 | static VALUE join_der(mrb_state *mrb, VALUE enumerable) 504 | { 505 | VALUE str = mrb_str_new(mrb, 0, 0); 506 | int len = RARRAY_LEN(enumerable); 507 | for (int i = 0; i < len; ++i) { 508 | join_der_i(mrb, str, mrb_ary_entry(enumerable, i)); 509 | } 510 | 511 | return str; 512 | } 513 | 514 | static VALUE ossl_asn1cons_to_der(mrb_state *mrb, VALUE self) 515 | { 516 | int tag, tn, tc, explicit, constructed = 1; 517 | int found_prim = 0, seq_len; 518 | long length; 519 | unsigned char *p; 520 | VALUE value, str, inf_length; 521 | 522 | tn = mrb_fixnum(ossl_asn1_get_tag(self)); 523 | tc = ossl_asn1_tag_class(mrb, self); 524 | inf_length = ossl_asn1_get_infinite_length(self); 525 | if (mrb_bool(inf_length)) { 526 | VALUE ary, example; 527 | constructed = 2; 528 | if (mrb_class(mrb, self) == cASN1Sequence || mrb_class(mrb, self) == cASN1Set) { 529 | tag = ossl_asn1_default_tag(mrb, self); 530 | } else { /* must be a constructive encoding of a primitive value */ 531 | ary = ossl_asn1_get_value(self); 532 | if (!mrb_obj_is_kind_of(mrb, ary, mrb->array_class)) 533 | mrb_raise(mrb, eASN1Error, "Constructive value must be an Array"); 534 | while (!found_prim) { 535 | example = mrb_ary_entry(ary, 0); 536 | if (mrb_obj_is_kind_of(mrb, example, cASN1Primitive)) { 537 | found_prim = 1; 538 | } else { 539 | if (!mrb_obj_is_kind_of(mrb, example, cASN1Constructive)) { 540 | mrb_raise(mrb, eASN1Error, "invalid constructed encoding"); 541 | return mrb_nil_value(); /* dummy */ 542 | } 543 | ary = ossl_asn1_get_value(example); 544 | } 545 | } 546 | tag = ossl_asn1_default_tag(mrb, example); 547 | } 548 | } else { 549 | if (mrb_class(mrb, self) == cASN1Constructive) 550 | mrb_raise(mrb, eASN1Error, "Constructive shall only be used with infinite length"); 551 | tag = ossl_asn1_default_tag(mrb, self); 552 | } 553 | explicit = ossl_asn1_is_explicit(mrb, self); 554 | value = join_der(mrb, ossl_asn1_get_value(self)); 555 | 556 | seq_len = ossl_asn1_object_size(constructed, RSTRING_LEN(value), tag); 557 | length = ossl_asn1_object_size(constructed, seq_len, tn); 558 | str = mrb_str_new(mrb, 0, length); 559 | p = (unsigned char *)RSTRING_PTR(str); 560 | if (tc == V_ASN1_UNIVERSAL) 561 | ossl_asn1_put_object(&p, constructed, RSTRING_LEN(value), tn, tc); 562 | else { 563 | if (explicit) { 564 | ossl_asn1_put_object(&p, constructed, seq_len, tn, tc); 565 | ossl_asn1_put_object(&p, constructed, RSTRING_LEN(value), tag, V_ASN1_UNIVERSAL); 566 | } else { 567 | ossl_asn1_put_object(&p, constructed, RSTRING_LEN(value), tn, tc); 568 | } 569 | } 570 | memcpy(p, RSTRING_PTR(value), RSTRING_LEN(value)); 571 | p += RSTRING_LEN(value); 572 | if (explicit && mrb_bool(inf_length)) { 573 | ASN1_put_eoc(&p); 574 | } 575 | ossl_str_adjust(mrb, str, p); 576 | return str; 577 | } 578 | 579 | static VALUE ossl_asn1_initialize(mrb_state *mrb, VALUE self) 580 | { 581 | VALUE value, tag, tagging, tag_class; 582 | int argc; 583 | argc = mrb_get_args(mrb, "o|ooo", &value, &tag, &tagging, &tag_class); 584 | if (argc > 1) { 585 | if (NIL_P(tag)) 586 | mrb_raise(mrb, eASN1Error, "must specify tag number"); 587 | if (!NIL_P(tagging) && !SYMBOL_P(tagging)) 588 | mrb_raise(mrb, eASN1Error, "invalid tagging method"); 589 | if (NIL_P(tag_class)) { 590 | if (NIL_P(tagging)) 591 | tag_class = mrb_sym2str(mrb, sUNIVERSAL); 592 | else 593 | tag_class = mrb_sym2str(mrb, sCONTEXT_SPECIFIC); 594 | } 595 | if (!SYMBOL_P(tag_class)) 596 | mrb_raise(mrb, eASN1Error, "invalid tag class"); 597 | if (!NIL_P(tagging) && mrb_intern_str(mrb, tagging) == sIMPLICIT && NUM2INT(tag) > 31) 598 | mrb_raise(mrb, eASN1Error, "tag number for Universal too large"); 599 | } else { 600 | tag = mrb_fixnum_value(ossl_asn1_default_tag(mrb, self)); 601 | tagging = mrb_nil_value(); 602 | tag_class = mrb_sym2str(mrb, sUNIVERSAL); 603 | } 604 | ossl_asn1_set_tag(self, tag); 605 | ossl_asn1_set_value(self, value); 606 | ossl_asn1_set_tagging(self, tagging); 607 | ossl_asn1_set_tag_class(self, tag_class); 608 | ossl_asn1_set_infinite_length(self, mrb_false_value()); 609 | 610 | return self; 611 | } 612 | 613 | static int ossl_i2d_ASN1_TYPE(ASN1_TYPE *a, unsigned char **pp) 614 | { 615 | #if OPENSSL_VERSION_NUMBER < 0x00907000L 616 | if (!a) 617 | return 0; 618 | if (a->type == V_ASN1_BOOLEAN) 619 | return i2d_ASN1_BOOLEAN(a->value.boolean, pp); 620 | #endif 621 | return i2d_ASN1_TYPE(a, pp); 622 | } 623 | static void ossl_ASN1_TYPE_free(ASN1_TYPE *a) 624 | { 625 | #if OPENSSL_VERSION_NUMBER < 0x00907000L 626 | if (!a) 627 | return; 628 | if (a->type == V_ASN1_BOOLEAN) { 629 | OPENSSL_free(a); 630 | return; 631 | } 632 | #endif 633 | ASN1_TYPE_free(a); 634 | } 635 | static VALUE ossl_asn1prim_to_der(mrb_state *mrb, VALUE self) 636 | { 637 | ASN1_TYPE *asn1; 638 | int tn, tc, explicit; 639 | long len, reallen; 640 | unsigned char *buf, *p; 641 | VALUE str; 642 | 643 | tn = mrb_fixnum(ossl_asn1_get_tag(self)); 644 | tc = ossl_asn1_tag_class(mrb, self); 645 | explicit = ossl_asn1_is_explicit(mrb, self); 646 | asn1 = ossl_asn1_get_asn1type(mrb, self); 647 | 648 | len = ossl_asn1_object_size(1, ossl_i2d_ASN1_TYPE(asn1, NULL), tn); 649 | if (!(buf = OPENSSL_malloc(len))) { 650 | ossl_ASN1_TYPE_free(asn1); 651 | mrb_raise(mrb, eASN1Error, "cannot alloc buffer"); 652 | } 653 | p = buf; 654 | if (tc == V_ASN1_UNIVERSAL) { 655 | ossl_i2d_ASN1_TYPE(asn1, &p); 656 | } else if (explicit) { 657 | ossl_asn1_put_object(&p, 1, ossl_i2d_ASN1_TYPE(asn1, NULL), tn, tc); 658 | ossl_i2d_ASN1_TYPE(asn1, &p); 659 | } else { 660 | ossl_i2d_ASN1_TYPE(asn1, &p); 661 | *buf = tc | tn | (*buf & V_ASN1_CONSTRUCTED); 662 | } 663 | ossl_ASN1_TYPE_free(asn1); 664 | reallen = p - buf; 665 | assert(reallen <= len); 666 | str = ossl_buf2str(mrb, (char *)buf, (int)(reallen)); /* buf will be free in ossl_buf2str */ 667 | 668 | return str; 669 | } 670 | 671 | #define OSSL_ASN1_IMPL_FACTORY_METHOD(klass) \ 672 | static VALUE ossl_asn1_##klass(mrb_state *mrb, VALUE self) \ 673 | { \ 674 | return mrb_instance_new(mrb, mrb_obj_value(cASN1##klass)); \ 675 | } 676 | 677 | OSSL_ASN1_IMPL_FACTORY_METHOD(Boolean) 678 | OSSL_ASN1_IMPL_FACTORY_METHOD(Integer) 679 | OSSL_ASN1_IMPL_FACTORY_METHOD(Enumerated) 680 | OSSL_ASN1_IMPL_FACTORY_METHOD(BitString) 681 | OSSL_ASN1_IMPL_FACTORY_METHOD(OctetString) 682 | OSSL_ASN1_IMPL_FACTORY_METHOD(UTF8String) 683 | OSSL_ASN1_IMPL_FACTORY_METHOD(NumericString) 684 | OSSL_ASN1_IMPL_FACTORY_METHOD(PrintableString) 685 | OSSL_ASN1_IMPL_FACTORY_METHOD(T61String) 686 | OSSL_ASN1_IMPL_FACTORY_METHOD(VideotexString) 687 | OSSL_ASN1_IMPL_FACTORY_METHOD(IA5String) 688 | OSSL_ASN1_IMPL_FACTORY_METHOD(GraphicString) 689 | OSSL_ASN1_IMPL_FACTORY_METHOD(ISO64String) 690 | OSSL_ASN1_IMPL_FACTORY_METHOD(GeneralString) 691 | OSSL_ASN1_IMPL_FACTORY_METHOD(UniversalString) 692 | OSSL_ASN1_IMPL_FACTORY_METHOD(BMPString) 693 | OSSL_ASN1_IMPL_FACTORY_METHOD(Null) 694 | OSSL_ASN1_IMPL_FACTORY_METHOD(ObjectId) 695 | OSSL_ASN1_IMPL_FACTORY_METHOD(UTCTime) 696 | OSSL_ASN1_IMPL_FACTORY_METHOD(GeneralizedTime) 697 | OSSL_ASN1_IMPL_FACTORY_METHOD(Sequence) 698 | OSSL_ASN1_IMPL_FACTORY_METHOD(Set) 699 | OSSL_ASN1_IMPL_FACTORY_METHOD(EndOfContent) 700 | 701 | enum { ossl_asn1_info_size = (sizeof(ossl_asn1_info) / sizeof(ossl_asn1_info[0])) }; 702 | #define OSSL_ASN1_DEFINE_CLASS(mrb, name, super) \ 703 | do { \ 704 | cASN1##name = mrb_define_class_under(mrb, mASN1, #name, cASN1##super); \ 705 | mrb_define_module_function(mrb, mASN1, #name, ossl_asn1_##name, MRB_ARGS_ARG(1, 3)); \ 706 | } while (0) 707 | 708 | void mrb_init_ossl_asn1(mrb_state *mrb) 709 | { 710 | int i; 711 | mrb_value ary; 712 | mASN1 = mrb_define_module_under(mrb, mOSSL, "ASN1"); 713 | eASN1Error = mrb_define_class_under(mrb, mASN1, "ASN1Error", eOSSLError); 714 | 715 | cASN1Data = mrb_define_class_under(mrb, mASN1, "ASN1Data", mrb->object_class); 716 | mrb_define_method(mrb, cASN1Data, "initialize", ossl_asn1data_initialize, MRB_ARGS_REQ(3)); 717 | cASN1Primitive = mrb_define_class_under(mrb, mASN1, "Primitive", cASN1Data); 718 | mrb_define_method(mrb, cASN1Primitive, "initialize", ossl_asn1_initialize, MRB_ARGS_ARG(1, 3)); 719 | mrb_define_method(mrb, cASN1Primitive, "to_der", ossl_asn1prim_to_der, 0); 720 | cASN1Constructive = mrb_define_class_under(mrb, mASN1, "Constructive", cASN1Data); 721 | mrb_define_method(mrb, cASN1Constructive, "initialize", ossl_asn1_initialize, MRB_ARGS_ARG(1, 3)); 722 | mrb_define_method(mrb, cASN1Constructive, "to_der", ossl_asn1cons_to_der, MRB_ARGS_NONE()); 723 | 724 | OSSL_ASN1_DEFINE_CLASS(mrb, Boolean, Primitive); 725 | OSSL_ASN1_DEFINE_CLASS(mrb, Integer, Primitive); 726 | OSSL_ASN1_DEFINE_CLASS(mrb, Enumerated, Primitive); 727 | OSSL_ASN1_DEFINE_CLASS(mrb, BitString, Primitive); 728 | OSSL_ASN1_DEFINE_CLASS(mrb, OctetString, Primitive); 729 | OSSL_ASN1_DEFINE_CLASS(mrb, UTF8String, Primitive); 730 | OSSL_ASN1_DEFINE_CLASS(mrb, NumericString, Primitive); 731 | OSSL_ASN1_DEFINE_CLASS(mrb, PrintableString, Primitive); 732 | OSSL_ASN1_DEFINE_CLASS(mrb, T61String, Primitive); 733 | OSSL_ASN1_DEFINE_CLASS(mrb, VideotexString, Primitive); 734 | OSSL_ASN1_DEFINE_CLASS(mrb, IA5String, Primitive); 735 | OSSL_ASN1_DEFINE_CLASS(mrb, GraphicString, Primitive); 736 | OSSL_ASN1_DEFINE_CLASS(mrb, ISO64String, Primitive); 737 | OSSL_ASN1_DEFINE_CLASS(mrb, GeneralString, Primitive); 738 | OSSL_ASN1_DEFINE_CLASS(mrb, UniversalString, Primitive); 739 | OSSL_ASN1_DEFINE_CLASS(mrb, BMPString, Primitive); 740 | OSSL_ASN1_DEFINE_CLASS(mrb, Null, Primitive); 741 | OSSL_ASN1_DEFINE_CLASS(mrb, ObjectId, Primitive); 742 | OSSL_ASN1_DEFINE_CLASS(mrb, UTCTime, Primitive); 743 | OSSL_ASN1_DEFINE_CLASS(mrb, GeneralizedTime, Primitive); 744 | OSSL_ASN1_DEFINE_CLASS(mrb, Sequence, Constructive); 745 | OSSL_ASN1_DEFINE_CLASS(mrb, Set, Constructive); 746 | OSSL_ASN1_DEFINE_CLASS(mrb, EndOfContent, Data); 747 | 748 | ary = mrb_ary_new(mrb); 749 | 750 | /* 751 | * Array storing tag names at the tag's index. 752 | */ 753 | mrb_define_const(mrb, mASN1, "UNIVERSAL_TAG_NAME", ary); 754 | for (i = 0; i < ossl_asn1_info_size; i++) { 755 | if (ossl_asn1_info[i].name[0] == '[') 756 | continue; 757 | mrb_define_const(mrb, mASN1, ossl_asn1_info[i].name, mrb_fixnum_value(i)); 758 | mrb_ary_set(mrb, ary, i, 759 | mrb_str_new(mrb, ossl_asn1_info[i].name, strlen(ossl_asn1_info[i].name))); 760 | } 761 | 762 | sUNIVERSAL = mrb_intern_lit(mrb, "UNIVERSAL"); 763 | sCONTEXT_SPECIFIC = mrb_intern_lit(mrb, "CONTEXT_SPECIFIC"); 764 | sAPPLICATION = mrb_intern_lit(mrb, "APPLICATION"); 765 | sPRIVATE = mrb_intern_lit(mrb, "PRIVATE"); 766 | sEXPLICIT = mrb_intern_lit(mrb, "EXPLICIT"); 767 | sIMPLICIT = mrb_intern_lit(mrb, "IMPLICIT"); 768 | 769 | sivVALUE = mrb_intern_lit(mrb, "@value"); 770 | sivTAG = mrb_intern_lit(mrb, "@tag"); 771 | sivTAGGING = mrb_intern_lit(mrb, "@tagging"); 772 | sivTAG_CLASS = mrb_intern_lit(mrb, "@tag_class"); 773 | sivINFINITE_LENGTH = mrb_intern_lit(mrb, "@infinite_length"); 774 | sivUNUSED_BITS = mrb_intern_lit(mrb, "@unused_bits"); 775 | } 776 | --------------------------------------------------------------------------------