noise-ruby 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 326691ebec86a68d5b4978756edcd5752c7307e4eba85c615e08262f8133481e
4
- data.tar.gz: 0626adc9ec4b0821211b04b3573a950684ca526edcd5e956450107d4d8c69ce0
3
+ metadata.gz: a6817736b9a72ca54f30ceb3238c9962fbd1f4a09092e9e0f783fec5a1035e9f
4
+ data.tar.gz: de1d7510423907f0b73fa4f4115916bb2044195dc4dbbf8acb1f83cb484f2e76
5
5
  SHA512:
6
- metadata.gz: 2246a0d4be698a11efb560f6e9ddd70b50a926255c32877d8fd7346780e99e4d2950ac2e10ed81fb787ce9a61f626027e69136e94d680c9959e79c1702c73c47
7
- data.tar.gz: fe3e968bbb464d11bd574750bdd97410b7ef5564328b327946654f76f8476fae4eadb7fbb8acebf4c4036b4f5edaa2d4a5b265c38705cc2ad33e25f310f78255
6
+ metadata.gz: 3235949265d26680b60447ad79e7c2ef02824233343a948b3b6805cd519c5e3f1ff5b573abb2ab1feec11eb7e51523448e47ce787ed81bc0399e8ab86e2b8368
7
+ data.tar.gz: 29d18a35a9d052d117e2ce0422f2a165eedf85d8f53af2d4b77263af500a257d194237cfa274b6ee40d432fbb8366490ed30fffafdaffa7713d70593c4104559
data/Dockerfile CHANGED
@@ -3,11 +3,11 @@
3
3
  # architecture and needs no emulation.
4
4
  FROM ruby:4.0
5
5
 
6
- # libsodium: dlopen'd by rbnacl, which lib/noise.rb requires unconditionally.
7
- # cargo: the blake3 gem is a Rust extension and is built at install time.
8
6
  # git: noise.gemspec calls `git ls-files` while the gemspec is evaluated.
7
+ # No Rust toolchain: the blake3-rb gem ships a precompiled binary for this platform.
8
+ # No libsodium: every function is computed by OpenSSL or in pure Ruby.
9
9
  RUN apt-get update -qq \
10
- && apt-get install -y --no-install-recommends build-essential git libsodium23 cargo \
10
+ && apt-get install -y --no-install-recommends build-essential git \
11
11
  && rm -rf /var/lib/apt/lists/*
12
12
 
13
13
  WORKDIR /work
data/README.md CHANGED
@@ -33,8 +33,14 @@ Supported Features:
33
33
  This gem requires Ruby 3.2 or later, and is tested on 3.2, 3.3, 3.4 and 4.0. Ruby 3.0 and 3.1 are
34
34
  past end of life and are no longer supported; stay on noise-ruby 0.12.0 or earlier if you need them.
35
35
 
36
- This gem needs libsodium library.
37
- To install libsodium, see https://github.com/jedisct1/libsodium
36
+ Every function except BLAKE3, which is optional and covered below, is computed either by the
37
+ OpenSSL that Ruby is linked against or in pure Ruby, so no system library has to be installed
38
+ separately. OpenSSL 1.1.1 or later provides everything this gem asks of it, and that is what the
39
+ official Ruby packages and the usual version managers (rbenv, rvm, asdf) are built with. Two kinds
40
+ of build fall short: a Ruby linked against LibreSSL has no BLAKE2b, no ChaCha20-Poly1305 and no
41
+ X448, and an OpenSSL restricted to the FIPS provider has neither BLAKE2b nor ChaCha20-Poly1305 nor
42
+ the secp256k1 curve. On such a build the protocol names that use a missing function raise an error,
43
+ and the remaining ones keep working.
38
44
 
39
45
  Add this line to your application's Gemfile:
40
46
 
@@ -50,13 +56,16 @@ Or install it yourself as:
50
56
 
51
57
  $ gem install noise-ruby
52
58
 
53
- If you use BLAKE3, you must install [Rust and Cargo](https://www.rust-lang.org/tools/install).
54
- And add this line to your Gemfile:
59
+ If you use BLAKE3, add this line to your Gemfile:
55
60
 
56
61
  ```
57
- gem 'blake3'
62
+ gem 'blake3-rb'
58
63
  ```
59
64
 
65
+ `blake3-rb` ships precompiled binaries for common Linux, macOS and Windows platforms (x86_64 and
66
+ aarch64), so no Rust or C toolchain is needed to install it there. On any other platform bundler
67
+ falls back to the source gem, which does need a C compiler.
68
+
60
69
  ## Usage
61
70
 
62
71
  Followings shows handshake protocol with "Noise_NN_25519_ChaChaPoly_BLAKE2b"
@@ -6,17 +6,32 @@ module Noise
6
6
  class ChaChaPoly
7
7
  MAX_NONCE = 2**64 - 1
8
8
 
9
+ # The name OpenSSL knows the AEAD construction of RFC 8439 by. It takes a 12 byte nonce and
10
+ # produces a 16 byte authentication tag, which is what the Noise specification requires of
11
+ # the ChaChaPoly cipher functions.
12
+ ALGORITHM = 'chacha20-poly1305'
13
+
14
+ # Length in bytes of the authentication tag that encrypt appends to the ciphertext.
15
+ TAGLEN = 16
16
+
9
17
  def encrypt(k, n, ad, plaintext)
10
- cipher = RbNaCl::AEAD::ChaCha20Poly1305IETF.new(String.new(k).force_encoding('ASCII-8BIT'))
11
- cipher.encrypt(nonce_to_bytes(n), plaintext, ad)
12
- rescue ::RbNaCl::CryptoError => e
18
+ cipher = OpenSSL::Cipher.new(ALGORITHM).encrypt
19
+ cipher.key = k
20
+ cipher.iv = nonce_to_bytes(n)
21
+ cipher.auth_data = ad
22
+ update(cipher, plaintext) + cipher.final + cipher.auth_tag
23
+ rescue OpenSSL::Cipher::CipherError => e
13
24
  raise Noise::Exceptions::EncryptError, "Encrypt failed. #{e.message}", e.backtrace
14
25
  end
15
26
 
16
27
  def decrypt(k, n, ad, ciphertext)
17
- cipher = RbNaCl::AEAD::ChaCha20Poly1305IETF.new(String.new(k).force_encoding('ASCII-8BIT'))
18
- cipher.decrypt(nonce_to_bytes(n), ciphertext, ad)
19
- rescue ::RbNaCl::CryptoError => e
28
+ cipher = OpenSSL::Cipher.new(ALGORITHM).decrypt
29
+ cipher.key = k
30
+ cipher.iv = nonce_to_bytes(n)
31
+ cipher.auth_data = ad
32
+ cipher.auth_tag = ciphertext[-TAGLEN..]
33
+ update(cipher, ciphertext[0...-TAGLEN]) + cipher.final
34
+ rescue OpenSSL::Cipher::CipherError => e
20
35
  raise Noise::Exceptions::DecryptError, "Decrpyt failed. #{e.message}", e.backtrace
21
36
  end
22
37
 
@@ -34,6 +49,16 @@ module Noise
34
49
  def rekey(k)
35
50
  encrypt(k, MAX_NONCE, '', "\x00" * 32)[0...32]
36
51
  end
52
+
53
+ private
54
+
55
+ # A zero-length payload is normal in a Noise message, but OpenSSL::Cipher#update raises
56
+ # ArgumentError('data must not be empty') instead of returning ''.
57
+ def update(cipher, data)
58
+ return String.new if data.empty?
59
+
60
+ cipher.update(data)
61
+ end
37
62
  end
38
63
  end
39
64
  end
@@ -3,29 +3,41 @@
3
3
  module Noise
4
4
  module Functions
5
5
  module DH
6
+ # The 25519 DH function of the Noise specification, which is X25519 as defined by RFC 7748 and
7
+ # not the Ed25519 signature scheme the class name suggests. The name is kept because it is
8
+ # what Protocol::DH maps '25519' to and what callers reference.
9
+ #
10
+ # OpenSSL implements X25519 and exposes it through the raw key interface, so this function
11
+ # needs no gem and no system library beyond the OpenSSL the other functions already use.
6
12
  class ED25519
7
13
  DHLEN = 32
14
+
15
+ # The name OpenSSL knows the curve by. Ed25519 is a different algorithm, and asking for it
16
+ # here would produce a signing key rather than one that can derive a shared secret.
17
+ ALGORITHM = 'X25519'
18
+
8
19
  def generate_keypair
9
- private_key = 1 + SecureRandom.random_number(RbNaCl::GroupElement::STANDARD_GROUP_ORDER - 1)
10
- scalar_as_string = ECDSA::Format::IntegerOctetString.encode(private_key, 32)
11
- public_key = RbNaCl::GroupElements::Curve25519.base.mult(scalar_as_string)
12
- Noise::Key.new(ECDSA::Format::IntegerOctetString.encode(private_key, 32), public_key.to_bytes)
20
+ pkey = OpenSSL::PKey.generate_key(ALGORITHM)
21
+ Noise::Key.new(pkey.raw_private_key, pkey.raw_public_key)
13
22
  end
14
23
 
15
24
  # Computes the X25519 shared secret for the given remote public key.
16
25
  #
17
- # RbNaCl reports a public key it cannot use in two different ways: a wrong length
18
- # raises RbNaCl::LengthError, and an all-zero or low-order point raises
19
- # RbNaCl::CryptoError. Both are translated to InvalidPublicKeyError so that a
20
- # caller handling a peer-supplied key rescues the same class for every DH function.
21
- # The length is checked before the call so that RbNaCl::LengthError raised for a
22
- # malformed private key keeps propagating as itself.
26
+ # OpenSSL reports a public key it cannot use as OpenSSL::PKey::PKeyError, both when the key
27
+ # is not DHLEN bytes and when the derivation would produce the all-zero output that RFC 7748
28
+ # requires be rejected. Both are translated to InvalidPublicKeyError so that a caller
29
+ # handling a peer-supplied key rescues the same class for every DH function. The local key
30
+ # is built outside the rescue so that a malformed private key keeps propagating as
31
+ # PKeyError instead of being reported as the peer's fault.
23
32
  def dh(private_key, public_key)
24
33
  raise Noise::Exceptions::InvalidPublicKeyError, public_key unless public_key.bytesize == DHLEN
25
34
 
26
- RbNaCl::GroupElement.new(public_key).mult(private_key).to_bytes
27
- rescue RbNaCl::CryptoError
28
- raise Noise::Exceptions::InvalidPublicKeyError, public_key
35
+ local = OpenSSL::PKey.new_raw_private_key(ALGORITHM, private_key)
36
+ begin
37
+ local.derive(OpenSSL::PKey.new_raw_public_key(ALGORITHM, public_key))
38
+ rescue OpenSSL::PKey::PKeyError
39
+ raise Noise::Exceptions::InvalidPublicKeyError, public_key
40
+ end
29
41
  end
30
42
 
31
43
  def dhlen
@@ -33,8 +45,8 @@ module Noise
33
45
  end
34
46
 
35
47
  def self.from_private(private_key)
36
- public_key = RbNaCl::GroupElements::Curve25519.base.mult(private_key)
37
- Noise::Key.new(private_key, public_key.to_bytes)
48
+ pkey = OpenSSL::PKey.new_raw_private_key(ALGORITHM, private_key)
49
+ Noise::Key.new(private_key, pkey.raw_public_key)
38
50
  end
39
51
  end
40
52
  end
@@ -6,8 +6,14 @@ module Noise
6
6
  class Blake2b
7
7
  HASHLEN = 64
8
8
  BLOCKLEN = 128
9
+
10
+ # The name OpenSSL knows BLAKE2b with a 64 byte output by. OpenSSL only implements the
11
+ # fixed 512 bit output length, which is exactly the HASHLEN the Noise specification gives
12
+ # the BLAKE2b hash function.
13
+ DIGEST_NAME = 'BLAKE2b512'
14
+
9
15
  def hash(data)
10
- RbNaCl::Hash.blake2b(data)
16
+ OpenSSL::Digest.digest(DIGEST_NAME, data)
11
17
  end
12
18
 
13
19
  def hashlen
@@ -19,9 +25,25 @@ module Noise
19
25
  end
20
26
  end
21
27
 
28
+ # Builds an OpenSSL BLAKE2b digest with no argument, which is how HMAC::Base creates the
29
+ # digests it feeds the inner and outer blocks to. OpenSSL defines no OpenSSL::Digest::BLAKE2b512
30
+ # constant, so the algorithm name is bound here instead of being passed in at every call.
31
+ class Blake2bDigester < OpenSSL::Digest
32
+ def initialize
33
+ super(Blake2b::DIGEST_NAME)
34
+ end
35
+
36
+ # HMAC::Base hashes a key longer than the block size through this class method. The one
37
+ # inherited from OpenSSL::Digest takes the algorithm name as its first argument, which this
38
+ # class has already bound, so it is redefined to take the data alone.
39
+ def self.digest(data)
40
+ new.digest(data)
41
+ end
42
+ end
43
+
22
44
  class Blake2bHMAC < HMAC::Base
23
45
  def initialize(key = nil)
24
- super(RbNaCl::Hash::Blake2b, 128, 64, key)
46
+ super(Blake2bDigester, Blake2b::BLOCKLEN, Blake2b::HASHLEN, key)
25
47
  end
26
48
  public_class_method :new, :digest, :hexdigest
27
49
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- Noise.require_optional 'blake3'
3
+ Noise.require_optional 'blake3-rb'
4
4
 
5
5
  module Noise
6
6
  module Functions
@@ -10,11 +10,11 @@ module Noise
10
10
  BLOCKLEN = 64
11
11
 
12
12
  def initialize
13
- Noise.optional_dependency!('blake3')
13
+ Noise.optional_dependency!('blake3-rb')
14
14
  end
15
15
 
16
16
  def hash(data)
17
- ::Blake3.digest(data)
17
+ ::Digest::Blake3.digest(data)
18
18
  end
19
19
 
20
20
  def hashlen
@@ -28,7 +28,7 @@ module Noise
28
28
 
29
29
  class Blake3HMAC < HMAC::Base
30
30
  def initialize(key = nil)
31
- super(::Blake3::Hasher, Blake3::BLOCKLEN, Blake3::HASHLEN, key)
31
+ super(::Digest::Blake3, Blake3::BLOCKLEN, Blake3::HASHLEN, key)
32
32
  end
33
33
  public_class_method :new, :digest, :hexdigest
34
34
  end
@@ -7,7 +7,7 @@ module Noise
7
7
  HASHLEN = 32
8
8
  BLOCKLEN = 64
9
9
  def hash(data)
10
- RbNaCl::Hash.sha256(data)
10
+ OpenSSL::Digest.digest('SHA256', data)
11
11
  end
12
12
 
13
13
  def hashlen
@@ -7,7 +7,7 @@ module Noise
7
7
  HASHLEN = 64
8
8
  BLOCKLEN = 128
9
9
  def hash(data)
10
- RbNaCl::Hash.sha512(data)
10
+ OpenSSL::Digest.digest('SHA512', data)
11
11
  end
12
12
 
13
13
  def hashlen
data/lib/noise/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Noise
4
- VERSION = '0.13.0'
4
+ VERSION = '0.14.0'
5
5
  end
data/lib/noise.rb CHANGED
@@ -4,7 +4,6 @@ require 'noise/version'
4
4
 
5
5
  require 'ecdsa'
6
6
  require 'openssl'
7
- require 'rbnacl'
8
7
  require 'ruby_hmac'
9
8
  require 'securerandom'
10
9
 
data/noise.gemspec CHANGED
@@ -30,17 +30,15 @@ Gem::Specification.new do |spec|
30
30
  spec.add_development_dependency 'simplecov'
31
31
  spec.add_development_dependency 'simplecov-json'
32
32
 
33
- # Optional backend. BLAKE3 is needed only when it appears in a protocol name, and it needs Rust
34
- # to build, so it is not a runtime dependency. Add it to your own Gemfile if you need it; see the
35
- # README.
36
- spec.add_development_dependency 'blake3'
33
+ # Optional backend. BLAKE3 is needed only when it appears in a protocol name, so it is not a
34
+ # runtime dependency. Add it to your own Gemfile if you need it; see the README.
35
+ spec.add_development_dependency 'blake3-rb'
37
36
 
38
37
  spec.add_runtime_dependency 'ecdsa'
39
- # The 448 DH function needs the raw key API (OpenSSL::PKey.new_raw_private_key and friends),
40
- # which arrived in openssl 3.0. Every supported interpreter bundles a newer default gem, but the
41
- # floor is stated so an older openssl pinned in an application's Gemfile fails to resolve rather
42
- # than failing at runtime.
38
+ # The 25519 and 448 DH functions need the raw key API (OpenSSL::PKey.new_raw_private_key and
39
+ # friends), which arrived in openssl 3.0. Every supported interpreter bundles a newer default
40
+ # gem, but the floor is stated so an older openssl pinned in an application's Gemfile fails to
41
+ # resolve rather than failing at runtime.
43
42
  spec.add_runtime_dependency 'openssl', '>= 3.0'
44
- spec.add_runtime_dependency 'rbnacl'
45
43
  spec.add_runtime_dependency 'ruby-hmac'
46
44
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: noise-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.0
4
+ version: 0.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hajime Yamaguchi
@@ -94,7 +94,7 @@ dependencies:
94
94
  - !ruby/object:Gem::Version
95
95
  version: '0'
96
96
  - !ruby/object:Gem::Dependency
97
- name: blake3
97
+ name: blake3-rb
98
98
  requirement: !ruby/object:Gem::Requirement
99
99
  requirements:
100
100
  - - ">="
@@ -135,20 +135,6 @@ dependencies:
135
135
  - - ">="
136
136
  - !ruby/object:Gem::Version
137
137
  version: '3.0'
138
- - !ruby/object:Gem::Dependency
139
- name: rbnacl
140
- requirement: !ruby/object:Gem::Requirement
141
- requirements:
142
- - - ">="
143
- - !ruby/object:Gem::Version
144
- version: '0'
145
- type: :runtime
146
- prerelease: false
147
- version_requirements: !ruby/object:Gem::Requirement
148
- requirements:
149
- - - ">="
150
- - !ruby/object:Gem::Version
151
- version: '0'
152
138
  - !ruby/object:Gem::Dependency
153
139
  name: ruby-hmac
154
140
  requirement: !ruby/object:Gem::Requirement