From 172b4770764a5b1a720e78c607a5216183a606e5 Mon Sep 17 00:00:00 2001 From: Jesse Shawl Date: Mon, 19 Feb 2024 14:39:08 -0600 Subject: [PATCH] optional libsodium (#43) --- CHANGELOG.md | 3 +++ README.md | 3 +++ bin/minisign | 5 +++++ lib/minisign.rb | 8 +++++++- lib/minisign/error.rb | 3 +++ lib/minisign/nacl.rb | 30 ++++++++++++++++++++++++++++++ lib/minisign/utils.rb | 6 +++--- spec/minisign/nacl_spec.rb | 13 +++++++++++++ 8 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 lib/minisign/nacl.rb create mode 100644 spec/minisign/nacl_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 997d773..a954cde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- Make `libsodium` dependency optional for consumers who want to verify signatures _only_ + ## [0.2.1] - 2024-02-19 ### Added diff --git a/README.md b/README.md index 6007ccf..6f353ba 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,9 @@ A ruby implemenation of [Minisign](http://jedisct1.github.io/minisign/). gem install minisign ``` +**Note**: This gem has a conditional dependency on [`libsodium`](https://doc.libsodium.org/) for the functionality related to key derivation and signature creation. +Signature verification _does not_ need `libsodium`. + ### Read a public key ```rb diff --git a/bin/minisign b/bin/minisign index bd121ed..0a91afa 100755 --- a/bin/minisign +++ b/bin/minisign @@ -31,6 +31,11 @@ rescue OptionParser::InvalidOption exit 1 end +if (options[:G] || options[:R] || options[:C] || options[:S]) && !RbNaCl.const_defined?(:PasswordHash) + warn 'Error: libsodium is not installed!' + exit 1 +end + Minisign::CLI.generate(options) if options[:G] Minisign::CLI.recreate(options) if options[:R] Minisign::CLI.change_password(options) if options[:C] diff --git a/lib/minisign.rb b/lib/minisign.rb index b1e9a7d..2f1aa51 100644 --- a/lib/minisign.rb +++ b/lib/minisign.rb @@ -2,7 +2,12 @@ require 'ed25519' require 'base64' -require 'rbnacl' +require 'openssl' +begin + require 'rbnacl' +rescue LoadError + # errors handled when invoked (see Minisign::NaCl) +end require 'minisign/cli' require 'minisign/utils' @@ -10,4 +15,5 @@ require 'minisign/signature' require 'minisign/private_key' require 'minisign/key_pair' +require 'minisign/nacl' require 'minisign/error' diff --git a/lib/minisign/error.rb b/lib/minisign/error.rb index a3741e5..6444ec3 100644 --- a/lib/minisign/error.rb +++ b/lib/minisign/error.rb @@ -9,4 +9,7 @@ class PasswordMissingError < StandardError class PasswordIncorrectError < StandardError end + + class LibSodiumDependencyError < StandardError + end end diff --git a/lib/minisign/nacl.rb b/lib/minisign/nacl.rb new file mode 100644 index 0000000..6d4ed38 --- /dev/null +++ b/lib/minisign/nacl.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module Minisign + # A module that invokes RbNaCl with user-focused actionable error messages. + module NaCl + def self.assert_libsodium_dependency_met! + return if RbNaCl.const_defined?(:PasswordHash) + + raise Minisign::LibSodiumDependencyError, 'libsodium is not installed!' + end + + module Hash + # see RbNaCl::Hash::Blake2b + module Blake2b + def self.digest(*args) + NaCl.assert_libsodium_dependency_met! + RbNaCl::Hash::Blake2b.digest(*args) + end + end + end + + # see RbNaCl::PasswordHash + module PasswordHash + def self.scrypt(*args) + NaCl.assert_libsodium_dependency_met! + RbNaCl::PasswordHash.scrypt(*args) + end + end + end +end diff --git a/lib/minisign/utils.rb b/lib/minisign/utils.rb index 74258d8..ff40e60 100644 --- a/lib/minisign/utils.rb +++ b/lib/minisign/utils.rb @@ -4,11 +4,11 @@ module Minisign # Helpers used in multiple classes module Utils def blake2b256(message) - RbNaCl::Hash::Blake2b.digest(message, { digest_size: 32 }) + Minisign::NaCl::Hash::Blake2b.digest(message, { digest_size: 32 }) end def blake2b512(message) - RbNaCl::Hash::Blake2b.digest(message, { digest_size: 64 }) + OpenSSL::Digest.new('blake2b512').digest(message) end # @return [Array<32 bit unsigned ints>] @@ -25,7 +25,7 @@ def hex(bytes) # @return [String] the used to xor the ed25519 keys def derive_key(password, kdf_salt, kdf_opslimit, kdf_memlimit) - RbNaCl::PasswordHash.scrypt( + Minisign::NaCl::PasswordHash.scrypt( password, kdf_salt, kdf_opslimit, diff --git a/spec/minisign/nacl_spec.rb b/spec/minisign/nacl_spec.rb new file mode 100644 index 0000000..7e8c5f0 --- /dev/null +++ b/spec/minisign/nacl_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +describe Minisign::NaCl do + it 'raises LibSodiumDependencyError if libsodium not installed' do + hash = RbNaCl.send(:remove_const, :Hash) + password_hash = RbNaCl.send(:remove_const, :PasswordHash) + expect do + Minisign::NaCl::Hash::Blake2b.digest('message', { digest_size: 32 }) + end.to raise_error(Minisign::LibSodiumDependencyError) + RbNaCl.const_set(:Hash, hash) + RbNaCl.const_set(:PasswordHash, password_hash) + end +end