diff --git a/Gemfile b/Gemfile index 19fd6e3..8ecad8a 100644 --- a/Gemfile +++ b/Gemfile @@ -7,6 +7,7 @@ gemspec gem "appraisal", "~> 2.2" gem "byebug", "~> 11.0" +gem "ed25519", "~> 1.2" gem "rake", "~> 13.0" gem "rspec", "~> 3.0" gem "rubocop", "~> 0.80.1" diff --git a/Gemfile.lock b/Gemfile.lock index 4a93322..2978aa9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -14,6 +14,7 @@ GEM ast (2.4.0) byebug (11.1.1) diff-lcs (1.3) + ed25519 (1.2.4) jaro_winkler (1.5.4) openssl (2.2.0) parallel (1.19.1) @@ -53,6 +54,7 @@ PLATFORMS DEPENDENCIES appraisal (~> 2.2) byebug (~> 11.0) + ed25519 (~> 1.2) openssl-signature_algorithm! rake (~> 13.0) rspec (~> 3.0) diff --git a/README.md b/README.md index 83beee6..21cd37b 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,26 @@ algorithm.verify_key = verify_key algorithm.verify(signature, to_be_signed) ``` +### EdDSA + +```ruby +to_be_signed = "to-be-signed" + +# Signer +algorithm = OpenSSL::SignatureAlgorithm::EdDSA.new +signing_key = algorithm.generate_signing_key +signature = algorithm.sign(to_be_signed) + +# Signer sends verify key to Verifier +verify_key_string = signing_key.verify_key.serialize + +# Verifier +verify_key = OpenSSL::SignatureAlgorithm::EdDSA::VerifyKey.deserialize(verify_key_string) +algorithm = OpenSSL::SignatureAlgorithm::EdDSA.new +algorithm.verify_key = verify_key +algorithm.verify(signature, to_be_signed) +``` + ### RSA-PSS ```ruby diff --git a/gemfiles/openssl_2_0.gemfile b/gemfiles/openssl_2_0.gemfile index b0d7205..d887a9d 100644 --- a/gemfiles/openssl_2_0.gemfile +++ b/gemfiles/openssl_2_0.gemfile @@ -7,6 +7,7 @@ gem "byebug", "~> 11.0" gem "rake", "~> 13.0" gem "rspec", "~> 3.0" gem "rubocop", "~> 0.80.1" +gem "ed25519", "~> 1.2" gem "openssl", "~> 2.0.0" gemspec path: "../" diff --git a/gemfiles/openssl_2_1.gemfile b/gemfiles/openssl_2_1.gemfile index 1759155..d95a33a 100644 --- a/gemfiles/openssl_2_1.gemfile +++ b/gemfiles/openssl_2_1.gemfile @@ -7,6 +7,7 @@ gem "byebug", "~> 11.0" gem "rake", "~> 13.0" gem "rspec", "~> 3.0" gem "rubocop", "~> 0.80.1" +gem "ed25519", "~> 1.2" gem "openssl", "~> 2.1.0" gemspec path: "../" diff --git a/gemfiles/openssl_2_2.gemfile b/gemfiles/openssl_2_2.gemfile index 2c1f786..a470723 100644 --- a/gemfiles/openssl_2_2.gemfile +++ b/gemfiles/openssl_2_2.gemfile @@ -7,6 +7,7 @@ gem "byebug", "~> 11.0" gem "rake", "~> 13.0" gem "rspec", "~> 3.0" gem "rubocop", "~> 0.80.1" +gem "ed25519", "~> 1.2" gem "openssl", "~> 2.2.0" gemspec path: "../" diff --git a/lib/openssl/signature_algorithm.rb b/lib/openssl/signature_algorithm.rb index b80a3fb..d3dbc91 100644 --- a/lib/openssl/signature_algorithm.rb +++ b/lib/openssl/signature_algorithm.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "openssl/signature_algorithm/ecdsa" +require "openssl/signature_algorithm/eddsa" require "openssl/signature_algorithm/error" require "openssl/signature_algorithm/rsapss" require "openssl/signature_algorithm/rsapkcs1" diff --git a/lib/openssl/signature_algorithm/eddsa.rb b/lib/openssl/signature_algorithm/eddsa.rb new file mode 100644 index 0000000..88a755c --- /dev/null +++ b/lib/openssl/signature_algorithm/eddsa.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +begin + gem "ed25519", ">= 1.0.0" + require "ed25519" +rescue LoadError + warn "OpenSSL::SignatureAlgorithm::EdDSA requires the ed25519 gem, version 1.0 or higher. "\ + "Please add it to your Gemfile: `gem \"ed25519\", \"~> 1.0\"`" + raise +end + +require "openssl/signature_algorithm/base" + +module OpenSSL + module SignatureAlgorithm + class EdDSA < Base + class SigningKey < ::Ed25519::SigningKey + def verify_key + VerifyKey.new(keypair[32, 32]) + end + end + + class VerifyKey < ::Ed25519::VerifyKey + def self.deserialize(key_bytes) + new(key_bytes) + end + + def serialize + to_bytes + end + end + + def generate_signing_key + @signing_key = SigningKey.generate + end + + def sign(data) + signing_key.sign(data) + end + + def verify(signature, verification_data) + verify_key.verify(signature, verification_data) + rescue ::Ed25519::VerifyError + raise(OpenSSL::SignatureAlgorithm::SignatureVerificationError, "Signature verification failed") + end + end + end +end diff --git a/openssl-signature_algorithm.gemspec b/openssl-signature_algorithm.gemspec index e8325f0..361371a 100644 --- a/openssl-signature_algorithm.gemspec +++ b/openssl-signature_algorithm.gemspec @@ -9,7 +9,7 @@ Gem::Specification.new do |spec| spec.email = ["gonzalo@cedarcode.com"] spec.license = "Apache-2.0" - spec.summary = "ECDSA, RSA-PSS and RSA-PKCS#1 algorithms for ruby" + spec.summary = "ECDSA, EdDSA, RSA-PSS and RSA-PKCS#1 algorithms for ruby" spec.description = spec.summary spec.homepage = "https://github.com/cedarcode/openssl-signature_algorithm" diff --git a/spec/openssl/signature_algorithm/eddsa_spec.rb b/spec/openssl/signature_algorithm/eddsa_spec.rb new file mode 100644 index 0000000..4a390b9 --- /dev/null +++ b/spec/openssl/signature_algorithm/eddsa_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require "openssl/signature_algorithm/eddsa" + +RSpec.describe "OpenSSL::SignatureAlgorithm::EdDSA" do + let(:to_be_signed) { "to-be-signed" } + let(:signature) do + signing_key + signer_algorithm.sign(to_be_signed) + end + let(:signer_algorithm) { OpenSSL::SignatureAlgorithm::EdDSA.new } + let(:signing_key) { signer_algorithm.generate_signing_key } + let(:verifier_algorithm) { OpenSSL::SignatureAlgorithm::EdDSA.new } + + context "when everything is in place" do + it "works" do + # Signer sends verify key to Verifier + verify_key_string = signing_key.verify_key.serialize + + # Verifier + verifier_algorithm.verify_key = OpenSSL::SignatureAlgorithm::EdDSA::VerifyKey.deserialize(verify_key_string) + expect(verifier_algorithm.verify(signature, to_be_signed)).to be_truthy + end + end + + context "when signature is invalid" do + let(:signature) do + signing_key + signature = signer_algorithm.sign(to_be_signed) + signature[63] = 'X' # Change the last byte to make it incorrect + + signature + end + + it "raises an error" do + # Signer sends verify key to Verifier + verify_key_string = signing_key.verify_key.serialize + + # Verifier + verifier_algorithm.verify_key = OpenSSL::SignatureAlgorithm::EdDSA::VerifyKey.deserialize(verify_key_string) + expect { verifier_algorithm.verify(signature, to_be_signed) } + .to raise_error(OpenSSL::SignatureAlgorithm::SignatureVerificationError) + end + end +end