From 01fa0fb4104bd62bba69936170115dbbc61d0706 Mon Sep 17 00:00:00 2001 From: Thomas Orozco Date: Tue, 5 Dec 2017 11:50:55 +0100 Subject: [PATCH] Support ECDSA private keys Technically, this only really works well on Ruby 2.4 where EC keys have a `#private?` method, but at least, it works there (clients on earlier versions can opt to monkey patch). --- lib/mini_ca/certificate.rb | 17 ++++++++++++++++- spec/mini_ca/certificate_spec.rb | 24 +++++++++++++++++++++--- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/lib/mini_ca/certificate.rb b/lib/mini_ca/certificate.rb index 5fa5fc4..b29db22 100644 --- a/lib/mini_ca/certificate.rb +++ b/lib/mini_ca/certificate.rb @@ -28,7 +28,7 @@ def initialize( x509.version = 0x2 x509.serial = serial || 0 - x509.public_key = send(:private_key).public_key + x509.public_key = public_key x509.subject = OpenSSL::X509::Name.new @@ -126,5 +126,20 @@ def bundle_pem def private_key_pem private_key.to_pem end + + def public_key + case private_key + when OpenSSL::PKey::RSA + private_key.public_key + when OpenSSL::PKey::EC + # See: https://github.com/ruby/openssl/issues/29#issuecomment-230664793 + # See: https://alexpeattie.com/blog/signing-a-csr-with-ecdsa-in-ruby + pub = OpenSSL::PKey::EC.new(private_key.group) + pub.public_key = private_key.public_key + pub + else + raise Error, "Unsupported private_key: #{private_key.class}" + end + end end end diff --git a/spec/mini_ca/certificate_spec.rb b/spec/mini_ca/certificate_spec.rb index 43ef459..92b20f4 100644 --- a/spec/mini_ca/certificate_spec.rb +++ b/spec/mini_ca/certificate_spec.rb @@ -53,10 +53,28 @@ end end - it 'initializes with a custom private_key' do + it 'initializes with a custom private_key (RSA)' do k = OpenSSL::PKey::RSA.new(512) - expect(described_class.new('x', private_key: k).private_key_pem) - .to eq(k.to_pem) + + crt = described_class.new('x', private_key: k) + expect(crt.private_key_pem).to eq(k.to_pem) + expect(crt.x509.check_private_key(k)).to be_truthy + end + + it 'initializes with a custom private_key (ECDSA)' do + k = OpenSSL::PKey::EC.new('prime256v1').tap(&:generate_key) + + # Ruby < 2.4 lacks a #private? method on EC keys, which is used when + # signing. We're not going to monkey-patch this for users, but we want to + # monkey patch it for our own specs. + maj, min, = RUBY_VERSION.split('.').map { |e| Integer(e) } + unless maj >= 2 && min >= 4 || maj > 2 + allow(k).to receive(:private?) { k.private_key? } + end + + crt = described_class.new('x', private_key: k) + expect(crt.private_key_pem).to eq(k.to_pem) + expect(crt.x509.check_private_key(k)).to be_truthy end end