noise-ruby 0.10.1 → 0.11.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 +4 -4
- data/.dockerignore +8 -0
- data/.github/workflows/ruby.yml +37 -11
- data/.gitignore +3 -0
- data/.rubocop.yml +29 -10
- data/.ruby-version +1 -1
- data/Dockerfile +29 -0
- data/Gemfile +2 -7
- data/README.md +6 -0
- data/lib/noise/connection/base.rb +20 -10
- data/lib/noise/exceptions/decrypt_error.rb +1 -0
- data/lib/noise/exceptions/encrypt_error.rb +1 -0
- data/lib/noise/exceptions/invalid_public_key_error.rb +3 -0
- data/lib/noise/exceptions/missing_dependency_error.rb +8 -0
- data/lib/noise/exceptions.rb +1 -0
- data/lib/noise/functions/cipher/aes_gcm.rb +17 -6
- data/lib/noise/functions/cipher/cha_cha_poly.rb +3 -2
- data/lib/noise/functions/dh/ed448.rb +7 -2
- data/lib/noise/functions/dh/secp256k1.rb +6 -2
- data/lib/noise/functions/hash/blake2s.rb +2 -0
- data/lib/noise/functions/hash/blake3.rb +8 -3
- data/lib/noise/functions/hash.rb +10 -6
- data/lib/noise/pattern.rb +60 -26
- data/lib/noise/protocol.rb +21 -16
- data/lib/noise/state/cipher_state.rb +7 -1
- data/lib/noise/state/handshake_state.rb +22 -11
- data/lib/noise/state/symmetric_state.rb +1 -2
- data/lib/noise/utils/string.rb +20 -6
- data/lib/noise/version.rb +1 -1
- data/lib/noise.rb +24 -10
- data/noise.gemspec +13 -3
- metadata +84 -26
- data/lib/noise/utils/hash.rb +0 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 876220b34ef23fb45d18cb2dd0dfd7627b5bb202e51f57af9c0cd5a62c211626
|
|
4
|
+
data.tar.gz: 662c47506e70adf05928da7b8c5e3fb48503e0043242f4127459297e29dba476
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8db525a248cd3a66f1597736745d70470ef109a3e7c7acd9758fda79c5c45aa78350d833c38a4d6d3d2ae9f8a06dd3b6b7f89a6c4928c12ea3dfe899efca4906
|
|
7
|
+
data.tar.gz: 420bd4dbeff8a66570f9d71ec219375aafeb9e5a45d76b1f23f6d9dfe47776b73da860b918f585214983224fea488370dc423f27dbd7c8905c95f33d8a4dce13
|
data/.dockerignore
ADDED
data/.github/workflows/ruby.yml
CHANGED
|
@@ -14,17 +14,43 @@ on:
|
|
|
14
14
|
branches: [ master ]
|
|
15
15
|
|
|
16
16
|
jobs:
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
lint:
|
|
19
18
|
runs-on: ubuntu-latest
|
|
19
|
+
strategy:
|
|
20
|
+
matrix:
|
|
21
|
+
ruby:
|
|
22
|
+
- '3.3'
|
|
23
|
+
steps:
|
|
24
|
+
- name: Checkout code
|
|
25
|
+
uses: actions/checkout@v4
|
|
26
|
+
|
|
27
|
+
- name: Set up Ruby ${{ matrix.ruby }}
|
|
28
|
+
uses: ruby/setup-ruby@v1
|
|
29
|
+
with:
|
|
30
|
+
ruby-version: ${{ matrix.ruby }}
|
|
31
|
+
bundler-cache: true
|
|
32
|
+
- name: Lint code for consistent style
|
|
33
|
+
run: bundle exec rubocop -f github
|
|
20
34
|
|
|
35
|
+
test:
|
|
36
|
+
runs-on: ubuntu-latest
|
|
37
|
+
strategy:
|
|
38
|
+
matrix:
|
|
39
|
+
ruby:
|
|
40
|
+
- '3.0'
|
|
41
|
+
- '3.1'
|
|
42
|
+
- '3.2'
|
|
43
|
+
- '3.3'
|
|
44
|
+
- '3.4'
|
|
21
45
|
steps:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
ruby-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
46
|
+
- name: Checkout code
|
|
47
|
+
uses: actions/checkout@v4
|
|
48
|
+
|
|
49
|
+
- name: Set up Ruby ${{ matrix.ruby }}
|
|
50
|
+
uses: ruby/setup-ruby@v1
|
|
51
|
+
with:
|
|
52
|
+
ruby-version: ${{ matrix.ruby }}
|
|
53
|
+
bundler-cache: true
|
|
54
|
+
- name: Run tests
|
|
55
|
+
run: bundle exec rspec spec
|
|
56
|
+
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
require:
|
|
2
|
+
- rubocop-rspec
|
|
3
|
+
Metrics:
|
|
2
4
|
Enabled: false
|
|
3
5
|
|
|
4
|
-
|
|
6
|
+
Layout/LineLength:
|
|
5
7
|
Max: 120
|
|
6
8
|
|
|
7
|
-
Metrics/MethodLength:
|
|
8
|
-
Max: 30
|
|
9
|
-
|
|
10
|
-
Metrics/AbcSize:
|
|
11
|
-
Max: 20
|
|
12
|
-
|
|
13
9
|
Style/Documentation:
|
|
14
10
|
Enabled: false
|
|
15
11
|
|
|
@@ -23,7 +19,30 @@ Style/WordArray:
|
|
|
23
19
|
MinSize: 100
|
|
24
20
|
|
|
25
21
|
AllCops:
|
|
26
|
-
TargetRubyVersion:
|
|
22
|
+
TargetRubyVersion: 3.0
|
|
23
|
+
SuggestExtensions: false
|
|
27
24
|
|
|
28
|
-
Naming/
|
|
25
|
+
Naming/MethodParameterName:
|
|
29
26
|
MinNameLength: 1
|
|
27
|
+
|
|
28
|
+
RSpec:
|
|
29
|
+
Exclude:
|
|
30
|
+
- "spec/factories/*"
|
|
31
|
+
RSpec/AnyInstance:
|
|
32
|
+
Enabled: false
|
|
33
|
+
RSpec/ContextWording:
|
|
34
|
+
Enabled: false
|
|
35
|
+
RSpec/ExampleLength:
|
|
36
|
+
Enabled: false
|
|
37
|
+
RSpec/MultipleExpectations:
|
|
38
|
+
Enabled: false
|
|
39
|
+
RSpec/NamedSubject:
|
|
40
|
+
Enabled: false
|
|
41
|
+
RSpec/NestedGroups:
|
|
42
|
+
Enabled: false
|
|
43
|
+
RSpec/MultipleMemoizedHelpers:
|
|
44
|
+
Enabled: false
|
|
45
|
+
RSpec/SpecFilePathFormat:
|
|
46
|
+
Enabled: false
|
|
47
|
+
RSpec/IndexedLet:
|
|
48
|
+
Enabled: false
|
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
3.3.5
|
data/Dockerfile
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# check=skip=FromPlatformFlagConstDisallowed
|
|
2
|
+
# The test suite loads the x86_64 libsecp256k1 / libgoldilocks bundled in spec/lib, and
|
|
3
|
+
# spec/spec_helper.rb points at them unconditionally, so this image has to be linux/amd64
|
|
4
|
+
# even on an arm64 host. It mirrors the CI runner (ubuntu x86_64).
|
|
5
|
+
FROM --platform=linux/amd64 ruby:3.3
|
|
6
|
+
|
|
7
|
+
# libsodium: dlopen'd by rbnacl, which lib/noise.rb requires unconditionally.
|
|
8
|
+
# cargo: the blake3 gem is a Rust extension and is built at install time.
|
|
9
|
+
# git: noise.gemspec calls `git ls-files` while the gemspec is evaluated.
|
|
10
|
+
RUN apt-get update -qq \
|
|
11
|
+
&& apt-get install -y --no-install-recommends build-essential git libsodium23 cargo \
|
|
12
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
13
|
+
|
|
14
|
+
WORKDIR /work
|
|
15
|
+
|
|
16
|
+
# Install the gems from the dependency definitions alone so the layer stays cached when only
|
|
17
|
+
# the source changes. Gems live in BUNDLE_PATH (/usr/local/bundle), outside /work, so mounting
|
|
18
|
+
# the working tree over /work at run time does not hide them.
|
|
19
|
+
#
|
|
20
|
+
# Gemfile.lock is gitignored, hence the glob: it is copied when the host has one so the image
|
|
21
|
+
# installs the exact versions the mounted lock will ask for at run time, and skipped otherwise.
|
|
22
|
+
COPY Gemfile* noise.gemspec ./
|
|
23
|
+
COPY lib/noise/version.rb lib/noise/version.rb
|
|
24
|
+
RUN bundle install
|
|
25
|
+
|
|
26
|
+
# Run with the working tree mounted:
|
|
27
|
+
# docker run --rm -v "$PWD":/work noise-dev
|
|
28
|
+
# docker run --rm -v "$PWD":/work noise-dev bundle exec rubocop
|
|
29
|
+
CMD ["bundle", "exec", "rspec"]
|
data/Gemfile
CHANGED
|
@@ -4,11 +4,6 @@ source 'https://rubygems.org'
|
|
|
4
4
|
|
|
5
5
|
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
|
|
6
6
|
|
|
7
|
-
# Specify your gem's dependencies in noise.gemspec
|
|
7
|
+
# Specify your gem's dependencies in noise.gemspec. The optional ed448, secp256k1-ruby and blake3
|
|
8
|
+
# backends are development dependencies there, so the test suite covers all of them.
|
|
8
9
|
gemspec
|
|
9
|
-
|
|
10
|
-
# Use secp256k1 as ecdh function
|
|
11
|
-
# gem 'secp256k1-ruby'
|
|
12
|
-
|
|
13
|
-
# Use blake3 as hash function
|
|
14
|
-
# gem 'blake3'
|
data/README.md
CHANGED
|
@@ -59,6 +59,12 @@ After installing, define an environment variable as follows:
|
|
|
59
59
|
|
|
60
60
|
$ export LIBGOLDILOCKS=/usr/local/lib/libgoldilocks.so
|
|
61
61
|
|
|
62
|
+
and, add this line to your Gemfile:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
gem 'ed448'
|
|
66
|
+
```
|
|
67
|
+
|
|
62
68
|
If you use Secp256k1, you must install [libsecp256k1](https://github.com/bitcoin-core/secp256k1).
|
|
63
69
|
|
|
64
70
|
$ git clone https://github.com/bitcoin-core/secp256k1
|
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
module Noise
|
|
4
4
|
module Connection
|
|
5
5
|
class Base
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
# The Noise spec caps a handshake or transport message at 65535 bytes.
|
|
7
|
+
MAX_MESSAGE_LENGTH = 65_535
|
|
8
|
+
|
|
9
|
+
attr_reader :protocol, :handshake_started, :handshake_finished, :handshake_hash, :handshake_state,
|
|
10
|
+
:cipher_state_encrypt, :cipher_state_decrypt, :cipher_state_handshake, :s, :rs
|
|
8
11
|
attr_accessor :psks, :prologue
|
|
9
|
-
attr_reader :s, :rs
|
|
10
12
|
|
|
11
13
|
def initialize(name, keypairs: { s: nil, e: nil, rs: nil, re: nil })
|
|
12
14
|
@protocol = Protocol.create(name)
|
|
@@ -27,11 +29,15 @@ module Noise
|
|
|
27
29
|
@handshake_started = true
|
|
28
30
|
end
|
|
29
31
|
|
|
32
|
+
# Restarts the handshake with a fallback pattern, carrying over the keys of the aborted one.
|
|
33
|
+
#
|
|
34
|
+
# The roles swap here: the party that wrote the aborted message now reads, and the one that
|
|
35
|
+
# failed to read it now writes. Both sides are already in that state, so @next_message is
|
|
36
|
+
# deliberately left as it is rather than reset through initialize_next_message.
|
|
30
37
|
def fallback(fallback_name)
|
|
31
38
|
@protocol = Protocol.create(fallback_name)
|
|
32
39
|
@handshake_started = false
|
|
33
40
|
@handshake_finished = false
|
|
34
|
-
# initialize_next_message
|
|
35
41
|
@local_keypairs = { e: @handshake_state.e, s: @handshake_state.s }
|
|
36
42
|
@remote_keys = { re: @handshake_state.re, rs: @handshake_state.rs }
|
|
37
43
|
start_handshake
|
|
@@ -55,10 +61,13 @@ module Noise
|
|
|
55
61
|
raise Noise::Exceptions::NoiseHandshakeError if @next_message != :write
|
|
56
62
|
raise Noise::Exceptions::NoiseHandshakeError if @handshake_finished
|
|
57
63
|
|
|
64
|
+
if @handshake_state.expected_message_length(payload.bytesize) > MAX_MESSAGE_LENGTH
|
|
65
|
+
raise Noise::Exceptions::NoiseHandshakeError, 'Message exceeds the maximum length.'
|
|
66
|
+
end
|
|
67
|
+
|
|
58
68
|
@next_message = :read
|
|
59
69
|
buffer = +''
|
|
60
|
-
|
|
61
|
-
@handshake_finished = true if result
|
|
70
|
+
@handshake_finished = @handshake_state.write_message(payload, buffer)
|
|
62
71
|
buffer
|
|
63
72
|
end
|
|
64
73
|
|
|
@@ -67,11 +76,12 @@ module Noise
|
|
|
67
76
|
raise Noise::Exceptions::NoiseHandshakeError unless @handshake_started
|
|
68
77
|
raise Noise::Exceptions::NoiseHandshakeError if @next_message != :read
|
|
69
78
|
raise Noise::Exceptions::NoiseHandshakeError if @handshake_finished
|
|
79
|
+
raise Noise::Exceptions::NoiseHandshakeError, 'Message exceeds the maximum length.' if
|
|
80
|
+
data.bytesize > MAX_MESSAGE_LENGTH
|
|
70
81
|
|
|
71
82
|
@next_message = :write
|
|
72
83
|
buffer = +''
|
|
73
|
-
|
|
74
|
-
@handshake_finished = true if result
|
|
84
|
+
@handshake_finished = @handshake_state.read_message(data, buffer)
|
|
75
85
|
buffer
|
|
76
86
|
end
|
|
77
87
|
|
|
@@ -93,7 +103,7 @@ module Noise
|
|
|
93
103
|
raise Noise::Exceptions::NoisePSKError if @protocol.pattern.psk_count != @psks.count
|
|
94
104
|
end
|
|
95
105
|
|
|
96
|
-
def
|
|
106
|
+
def missing_keypairs?
|
|
97
107
|
keypairs = @local_keypairs.merge(@remote_keys)
|
|
98
108
|
@protocol.pattern.required_keypairs(initiator?).any? { |keypair| !keypairs[keypair] }
|
|
99
109
|
end
|
|
@@ -101,7 +111,7 @@ module Noise
|
|
|
101
111
|
def validate
|
|
102
112
|
validate_psk! if @protocol.psk?
|
|
103
113
|
|
|
104
|
-
raise Noise::Exceptions::NoiseValidationError if
|
|
114
|
+
raise Noise::Exceptions::NoiseValidationError if missing_keypairs?
|
|
105
115
|
|
|
106
116
|
true
|
|
107
117
|
end
|
data/lib/noise/exceptions.rb
CHANGED
|
@@ -6,6 +6,7 @@ module Noise
|
|
|
6
6
|
autoload :EncryptError, 'noise/exceptions/encrypt_error'
|
|
7
7
|
autoload :InvalidPublicKeyError, 'noise/exceptions/invalid_public_key_error'
|
|
8
8
|
autoload :MaxNonceError, 'noise/exceptions/max_nonce_error'
|
|
9
|
+
autoload :MissingDependencyError, 'noise/exceptions/missing_dependency_error'
|
|
9
10
|
autoload :ProtocolNameError, 'noise/exceptions/protocol_name_error'
|
|
10
11
|
autoload :NoiseHandshakeError, 'noise/exceptions/noise_handshake_error'
|
|
11
12
|
autoload :NoiseValidationError, 'noise/exceptions/noise_validation_error'
|
|
@@ -7,28 +7,29 @@ module Noise
|
|
|
7
7
|
MAX_NONCE = 2**64 - 1
|
|
8
8
|
|
|
9
9
|
def encrypt(k, n, ad, plaintext)
|
|
10
|
-
cipher = OpenSSL::Cipher
|
|
10
|
+
cipher = OpenSSL::Cipher.new('aes-256-gcm').encrypt
|
|
11
11
|
cipher.key = k
|
|
12
12
|
cipher.iv = nonce_to_bytes(n)
|
|
13
13
|
cipher.auth_data = ad
|
|
14
|
-
|
|
14
|
+
update(cipher, plaintext) + cipher.final + cipher.auth_tag
|
|
15
15
|
rescue OpenSSL::Cipher::CipherError => e
|
|
16
16
|
raise Noise::Exceptions::EncryptError, "Encrypt failed. #{e.message}", e.backtrace
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def decrypt(k, n, ad, ciphertext)
|
|
20
|
-
cipher = OpenSSL::Cipher
|
|
20
|
+
cipher = OpenSSL::Cipher.new('aes-256-gcm').decrypt
|
|
21
21
|
cipher.key = k
|
|
22
22
|
cipher.iv = nonce_to_bytes(n)
|
|
23
23
|
cipher.auth_data = ad
|
|
24
|
-
cipher.auth_tag = ciphertext[-16
|
|
25
|
-
|
|
24
|
+
cipher.auth_tag = ciphertext[-16..]
|
|
25
|
+
update(cipher, ciphertext[0...-16]) + cipher.final
|
|
26
26
|
rescue OpenSSL::Cipher::CipherError => e
|
|
27
27
|
raise Noise::Exceptions::DecryptError, "Decrpyt failed. #{e.message}", e.backtrace
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
+
# 4 zero bytes followed by n as a big-endian 64 bit integer.
|
|
30
31
|
def nonce_to_bytes(n)
|
|
31
|
-
"\x00" * 4 +
|
|
32
|
+
"\x00" * 4 + [n].pack('Q>')
|
|
32
33
|
end
|
|
33
34
|
|
|
34
35
|
# Returns a new 32-byte cipher key as a pseudorandom function of k.
|
|
@@ -40,6 +41,16 @@ module Noise
|
|
|
40
41
|
def rekey(k)
|
|
41
42
|
encrypt(k, MAX_NONCE, '', "\x00" * 32)[0...32]
|
|
42
43
|
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
# A zero-length payload is normal in a Noise message, but the openssl gem shipped with
|
|
48
|
+
# Ruby 3.0 raises ArgumentError('data must not be empty') instead of returning ''.
|
|
49
|
+
def update(cipher, data)
|
|
50
|
+
return String.new if data.empty?
|
|
51
|
+
|
|
52
|
+
cipher.update(data)
|
|
53
|
+
end
|
|
43
54
|
end
|
|
44
55
|
end
|
|
45
56
|
end
|
|
@@ -20,8 +20,9 @@ module Noise
|
|
|
20
20
|
raise Noise::Exceptions::DecryptError, "Decrpyt failed. #{e.message}", e.backtrace
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
+
# 4 zero bytes followed by n as a little-endian 64 bit integer.
|
|
23
24
|
def nonce_to_bytes(n)
|
|
24
|
-
"\x00" * 4 +
|
|
25
|
+
"\x00" * 4 + [n].pack('Q<')
|
|
25
26
|
end
|
|
26
27
|
|
|
27
28
|
# Returns a new 32-byte cipher key as a pseudorandom function of k.
|
|
@@ -31,7 +32,7 @@ module Noise
|
|
|
31
32
|
# zerolen is a zero-length byte sequence, and zeros is a sequence of
|
|
32
33
|
# 32 bytes filled with zeros.
|
|
33
34
|
def rekey(k)
|
|
34
|
-
encrypt(k, MAX_NONCE, '', "\x00" * 32)[0
|
|
35
|
+
encrypt(k, MAX_NONCE, '', "\x00" * 32)[0...32]
|
|
35
36
|
end
|
|
36
37
|
end
|
|
37
38
|
end
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Noise.require_optional('ed448') { Ed448.init }
|
|
4
4
|
|
|
5
5
|
module Noise
|
|
6
6
|
module Functions
|
|
7
7
|
module DH
|
|
8
8
|
class ED448
|
|
9
|
-
|
|
9
|
+
# Ed448::X448::X448_PRIVATE_BYTES, spelled out so this file loads without the gem.
|
|
10
|
+
DHLEN = 56
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
Noise.optional_dependency!('ed448')
|
|
14
|
+
end
|
|
10
15
|
|
|
11
16
|
def generate_keypair
|
|
12
17
|
private_key = SecureRandom.random_bytes(DHLEN)
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Noise.require_optional 'secp256k1'
|
|
4
4
|
|
|
5
5
|
module Noise
|
|
6
6
|
module Functions
|
|
7
7
|
module DH
|
|
8
8
|
class Secp256k1
|
|
9
|
+
def initialize
|
|
10
|
+
Noise.optional_dependency!('secp256k1')
|
|
11
|
+
end
|
|
12
|
+
|
|
9
13
|
def generate_keypair
|
|
10
14
|
group = ECDSA::Group::Secp256k1
|
|
11
15
|
private_key = 1 + SecureRandom.random_number(group.order - 1)
|
|
@@ -19,7 +23,7 @@ module Noise
|
|
|
19
23
|
def dh(private_key, public_key)
|
|
20
24
|
key = ::Secp256k1::PublicKey.new(pubkey: public_key, raw: true)
|
|
21
25
|
key.ecdh(private_key)
|
|
22
|
-
rescue ::Secp256k1::AssertError
|
|
26
|
+
rescue ::Secp256k1::AssertError
|
|
23
27
|
raise Noise::Exceptions::InvalidPublicKeyError, public_key
|
|
24
28
|
end
|
|
25
29
|
|
|
@@ -60,6 +60,7 @@ module Noise
|
|
|
60
60
|
# @return context
|
|
61
61
|
def init(out_len, key)
|
|
62
62
|
raise ArgumentError if out_len.zero? || out_len > 32
|
|
63
|
+
|
|
63
64
|
h = IV.dup
|
|
64
65
|
h[0] ^= 0x01010000 ^ (key.size << 8) ^ out_len
|
|
65
66
|
t = 0
|
|
@@ -168,6 +169,7 @@ module Noise
|
|
|
168
169
|
|
|
169
170
|
class Context
|
|
170
171
|
attr_accessor :b, :h, :t, :c, :out_len
|
|
172
|
+
|
|
171
173
|
def initialize(b, h, t, c, out_len)
|
|
172
174
|
@b = b
|
|
173
175
|
@h = h
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Noise.require_optional 'blake3'
|
|
4
4
|
|
|
5
5
|
module Noise
|
|
6
6
|
module Functions
|
|
7
7
|
module Hash
|
|
8
8
|
class Blake3
|
|
9
|
-
HASHLEN =
|
|
10
|
-
BLOCKLEN =
|
|
9
|
+
HASHLEN = 32
|
|
10
|
+
BLOCKLEN = 64
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
Noise.optional_dependency!('blake3')
|
|
14
|
+
end
|
|
15
|
+
|
|
11
16
|
def hash(data)
|
|
12
17
|
::Blake3.digest(data)
|
|
13
18
|
end
|
data/lib/noise/functions/hash.rb
CHANGED
|
@@ -10,14 +10,17 @@ module Noise
|
|
|
10
10
|
autoload :Blake3, 'noise/functions/hash/blake3'
|
|
11
11
|
|
|
12
12
|
def self.hmac_hash(key, data, digest)
|
|
13
|
-
|
|
13
|
+
case digest
|
|
14
|
+
when /SHA/
|
|
14
15
|
OpenSSL::HMAC.digest(OpenSSL::Digest.new(digest), key, data)
|
|
15
|
-
|
|
16
|
+
when /BLAKE2b/
|
|
16
17
|
Noise::Functions::Hash::Blake2bHMAC.new(key).update(data).digest
|
|
17
|
-
|
|
18
|
+
when /BLAKE2s/
|
|
18
19
|
Noise::Functions::Hash::Blake2sHMAC.new(key).update(data).digest
|
|
19
|
-
|
|
20
|
+
when /BLAKE3/
|
|
20
21
|
Noise::Functions::Hash::Blake3HMAC.new(key).update(data).digest
|
|
22
|
+
else
|
|
23
|
+
raise Noise::Exceptions::ProtocolNameError, "Unsupported hash function: #{digest}"
|
|
21
24
|
end
|
|
22
25
|
end
|
|
23
26
|
|
|
@@ -30,9 +33,10 @@ module Noise
|
|
|
30
33
|
def self.hkdf(chaining_key, input_key_material, num_outputs, digest)
|
|
31
34
|
temp_key = hmac_hash(chaining_key, input_key_material, digest)
|
|
32
35
|
output1 = hmac_hash(temp_key, "\x01", digest)
|
|
33
|
-
output2 = hmac_hash(temp_key, output1
|
|
36
|
+
output2 = hmac_hash(temp_key, "#{output1}\u0002", digest)
|
|
34
37
|
return [output1, output2] if num_outputs == 2
|
|
35
|
-
|
|
38
|
+
|
|
39
|
+
output3 = hmac_hash(temp_key, "#{output2}\u0003", digest)
|
|
36
40
|
[output1, output2, output3]
|
|
37
41
|
end
|
|
38
42
|
end
|
data/lib/noise/pattern.rb
CHANGED
|
@@ -50,6 +50,7 @@ module Noise
|
|
|
50
50
|
'se'
|
|
51
51
|
end
|
|
52
52
|
end
|
|
53
|
+
|
|
53
54
|
class TokenSS < TokenDH
|
|
54
55
|
def get_key(keypair, _initiator)
|
|
55
56
|
[keypair.s.private_key, keypair.rs]
|
|
@@ -59,6 +60,7 @@ module Noise
|
|
|
59
60
|
'ss'
|
|
60
61
|
end
|
|
61
62
|
end
|
|
63
|
+
|
|
62
64
|
class TokenPSK
|
|
63
65
|
def to_s
|
|
64
66
|
'psk'
|
|
@@ -77,6 +79,7 @@ module Noise
|
|
|
77
79
|
module Modifier
|
|
78
80
|
class Psk
|
|
79
81
|
attr_reader :index
|
|
82
|
+
|
|
80
83
|
def initialize(index)
|
|
81
84
|
@index = index
|
|
82
85
|
end
|
|
@@ -85,29 +88,39 @@ module Noise
|
|
|
85
88
|
class Fallback
|
|
86
89
|
end
|
|
87
90
|
|
|
91
|
+
PSK_REGEX = /\Apsk(\d+)\z/
|
|
92
|
+
|
|
88
93
|
def self.parse(s)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
else
|
|
95
|
-
raise Noise::Exceptions::UnsupportedModifierError
|
|
96
|
-
end
|
|
94
|
+
matched = PSK_REGEX.match(s)
|
|
95
|
+
return Modifier::Psk.new(matched[1].to_i) if matched
|
|
96
|
+
return Modifier::Fallback.new if s == 'fallback'
|
|
97
|
+
|
|
98
|
+
raise Noise::Exceptions::UnsupportedModifierError, "Unsupported modifier: #{s}"
|
|
97
99
|
end
|
|
98
100
|
end
|
|
99
101
|
|
|
100
102
|
class Pattern
|
|
101
|
-
attr_reader :tokens, :modifiers, :psk_count, :fallback
|
|
103
|
+
attr_reader :name, :tokens, :modifiers, :psk_count, :fallback
|
|
104
|
+
|
|
105
|
+
NAME_REGEX = /\A([A-Z1]+)([^A-Z]*)\z/
|
|
102
106
|
|
|
103
107
|
def self.create(name)
|
|
104
|
-
|
|
105
|
-
pattern
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
matched = NAME_REGEX.match(name)
|
|
109
|
+
raise Noise::Exceptions::ProtocolNameError, "Malformed pattern name: #{name}" unless matched
|
|
110
|
+
|
|
111
|
+
modifiers = matched[2].split('+').map { |s| Modifier.parse(s) }
|
|
112
|
+
pattern_class(matched[1]).new(modifiers)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def self.pattern_class(pattern)
|
|
116
|
+
klass = Object.const_get("Noise::Pattern#{pattern}")
|
|
117
|
+
raise NameError unless klass.is_a?(Class) && klass < Pattern
|
|
118
|
+
|
|
119
|
+
klass
|
|
120
|
+
rescue NameError
|
|
121
|
+
raise Noise::Exceptions::ProtocolNameError, "Unsupported pattern: #{pattern}"
|
|
110
122
|
end
|
|
123
|
+
private_class_method :pattern_class
|
|
111
124
|
|
|
112
125
|
def initialize(modifiers)
|
|
113
126
|
@pre_messages = [[], []]
|
|
@@ -126,7 +139,8 @@ module Noise
|
|
|
126
139
|
case modifier
|
|
127
140
|
when Modifier::Psk
|
|
128
141
|
index = modifier.index
|
|
129
|
-
|
|
142
|
+
# psk0 prepends to the first message, pskN appends to the Nth one.
|
|
143
|
+
raise Noise::Exceptions::PSKValueError if index > @tokens.size
|
|
130
144
|
|
|
131
145
|
if index.zero?
|
|
132
146
|
@tokens[0].insert(0, Token::PSK)
|
|
@@ -147,24 +161,48 @@ module Noise
|
|
|
147
161
|
|
|
148
162
|
def required_keypairs_of_initiator
|
|
149
163
|
required = []
|
|
150
|
-
required << :s if
|
|
151
|
-
required << :rs if
|
|
164
|
+
required << :s if initiator_static?
|
|
165
|
+
required << :rs if responder_static_pre_shared?
|
|
152
166
|
required
|
|
153
167
|
end
|
|
154
168
|
|
|
155
169
|
def required_keypairs_of_responder
|
|
156
170
|
required = []
|
|
157
|
-
required << :rs if
|
|
158
|
-
required << :s if
|
|
171
|
+
required << :rs if initiator_static_pre_shared?
|
|
172
|
+
required << :s if responder_static?
|
|
159
173
|
required
|
|
160
174
|
end
|
|
161
175
|
|
|
162
176
|
def initiator_pre_messages
|
|
163
|
-
@pre_messages[0].dup
|
|
177
|
+
(@pre_messages[0] || []).dup
|
|
164
178
|
end
|
|
165
179
|
|
|
166
180
|
def responder_pre_messages
|
|
167
|
-
@pre_messages[1].dup
|
|
181
|
+
(@pre_messages[1] || []).dup
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# A party needs a static keypair when its static public key is either pre-shared with the peer or
|
|
185
|
+
# transmitted during the handshake. Deriving this from the pattern itself keeps deferred patterns
|
|
186
|
+
# (whose names carry a '1', e.g. X1K) working, unlike inspecting single characters of the name.
|
|
187
|
+
def initiator_static?
|
|
188
|
+
initiator_static_pre_shared? || sends_static?(0)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def responder_static?
|
|
192
|
+
responder_static_pre_shared? || sends_static?(1)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def initiator_static_pre_shared?
|
|
196
|
+
initiator_pre_messages.include?(Token::S)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def responder_static_pre_shared?
|
|
200
|
+
responder_pre_messages.include?(Token::S)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# The initiator writes the messages at even indexes, the responder the ones at odd indexes.
|
|
204
|
+
def sends_static?(offset)
|
|
205
|
+
offset.step(@tokens.size - 1, 2).any? { |i| @tokens[i].include?(Token::S) }
|
|
168
206
|
end
|
|
169
207
|
|
|
170
208
|
def one_way?
|
|
@@ -173,10 +211,6 @@ module Noise
|
|
|
173
211
|
end
|
|
174
212
|
|
|
175
213
|
class OneWayPattern < Pattern
|
|
176
|
-
def initialize(modifiers)
|
|
177
|
-
super(modifiers)
|
|
178
|
-
end
|
|
179
|
-
|
|
180
214
|
def one_way?
|
|
181
215
|
true
|
|
182
216
|
end
|
data/lib/noise/protocol.rb
CHANGED
|
@@ -6,27 +6,31 @@ module Noise
|
|
|
6
6
|
attr_reader :name, :pattern
|
|
7
7
|
|
|
8
8
|
CIPHER = {
|
|
9
|
-
'AESGCM'
|
|
10
|
-
'ChaChaPoly'
|
|
11
|
-
}.
|
|
9
|
+
'AESGCM' => Noise::Functions::Cipher::AesGcm,
|
|
10
|
+
'ChaChaPoly' => Noise::Functions::Cipher::ChaChaPoly
|
|
11
|
+
}.freeze
|
|
12
12
|
|
|
13
13
|
DH = {
|
|
14
|
-
'25519'
|
|
15
|
-
'448'
|
|
16
|
-
'secp256k1'
|
|
17
|
-
}.
|
|
14
|
+
'25519' => Noise::Functions::DH::ED25519,
|
|
15
|
+
'448' => Noise::Functions::DH::ED448,
|
|
16
|
+
'secp256k1' => Noise::Functions::DH::Secp256k1
|
|
17
|
+
}.freeze
|
|
18
18
|
|
|
19
19
|
HASH = {
|
|
20
|
-
'BLAKE2b'
|
|
21
|
-
'BLAKE2s'
|
|
22
|
-
'SHA256'
|
|
23
|
-
'SHA512'
|
|
24
|
-
'BLAKE3'
|
|
25
|
-
}.
|
|
20
|
+
'BLAKE2b' => Noise::Functions::Hash::Blake2b,
|
|
21
|
+
'BLAKE2s' => Noise::Functions::Hash::Blake2s,
|
|
22
|
+
'SHA256' => Noise::Functions::Hash::Sha256,
|
|
23
|
+
'SHA512' => Noise::Functions::Hash::Sha512,
|
|
24
|
+
'BLAKE3' => Noise::Functions::Hash::Blake3
|
|
25
|
+
}.freeze
|
|
26
26
|
|
|
27
27
|
def self.create(name)
|
|
28
|
-
|
|
29
|
-
raise Noise::Exceptions::ProtocolNameError
|
|
28
|
+
parts = name.split('_')
|
|
29
|
+
raise Noise::Exceptions::ProtocolNameError, "Malformed protocol name: #{name}" unless parts.size == 5
|
|
30
|
+
|
|
31
|
+
prefix, pattern_name, dh_name, cipher_name, hash_name = parts
|
|
32
|
+
raise Noise::Exceptions::ProtocolNameError, "Malformed protocol name: #{name}" if prefix != 'Noise'
|
|
33
|
+
|
|
30
34
|
new(name, pattern_name, cipher_name, hash_name, dh_name)
|
|
31
35
|
end
|
|
32
36
|
|
|
@@ -43,7 +47,8 @@ module Noise
|
|
|
43
47
|
@cipher_fn = CIPHER[cipher_name]&.new
|
|
44
48
|
@hash_fn = HASH[hash_name]&.new
|
|
45
49
|
@dh_fn = DH[dh_name]&.new
|
|
46
|
-
raise Noise::Exceptions::ProtocolNameError
|
|
50
|
+
raise Noise::Exceptions::ProtocolNameError, "Unsupported function in: #{@name}" unless
|
|
51
|
+
@cipher_fn && @hash_fn && @dh_fn
|
|
47
52
|
end
|
|
48
53
|
|
|
49
54
|
def psk?
|
|
@@ -10,10 +10,11 @@ module Noise
|
|
|
10
10
|
#
|
|
11
11
|
class CipherState
|
|
12
12
|
MAX_NONCE = 2**64 - 1
|
|
13
|
+
TAG_LENGTH = 16
|
|
13
14
|
|
|
14
15
|
attr_reader :k, :n
|
|
15
16
|
|
|
16
|
-
def initialize(cipher:
|
|
17
|
+
def initialize(cipher:)
|
|
17
18
|
@cipher = cipher
|
|
18
19
|
end
|
|
19
20
|
|
|
@@ -36,6 +37,7 @@ module Noise
|
|
|
36
37
|
def encrypt_with_ad(ad, plaintext)
|
|
37
38
|
return plaintext unless key?
|
|
38
39
|
raise Noise::Exceptions::MaxNonceError if @n == MAX_NONCE
|
|
40
|
+
|
|
39
41
|
ciphertext = @cipher.encrypt(@k, @n, ad, plaintext)
|
|
40
42
|
@n += 1
|
|
41
43
|
ciphertext
|
|
@@ -45,6 +47,10 @@ module Noise
|
|
|
45
47
|
def decrypt_with_ad(ad, ciphertext)
|
|
46
48
|
return ciphertext unless key?
|
|
47
49
|
raise Noise::Exceptions::MaxNonceError if @n == MAX_NONCE
|
|
50
|
+
# Without this the ciphers slice a nil authentication tag out of the truncated input.
|
|
51
|
+
raise Noise::Exceptions::DecryptError, 'Ciphertext is shorter than the tag.' if
|
|
52
|
+
ciphertext.bytesize < TAG_LENGTH
|
|
53
|
+
|
|
48
54
|
plaintext = @cipher.decrypt(@k, @n, ad, ciphertext)
|
|
49
55
|
@n += 1
|
|
50
56
|
plaintext
|
|
@@ -17,8 +17,7 @@ module Noise
|
|
|
17
17
|
# message_patterns: A sequence of message patterns.
|
|
18
18
|
# Each message pattern is a sequence of tokens from the set ("e", "s", "ee", "es", "se", "ss").
|
|
19
19
|
class HandshakeState
|
|
20
|
-
attr_reader :message_patterns, :symmetric_state
|
|
21
|
-
attr_reader :s, :rs, :e, :re
|
|
20
|
+
attr_reader :message_patterns, :symmetric_state, :s, :rs, :e, :re
|
|
22
21
|
|
|
23
22
|
def initialize(connection, initiator, prologue, local_keypairs, remote_keys)
|
|
24
23
|
@connection = connection
|
|
@@ -32,8 +31,9 @@ module Noise
|
|
|
32
31
|
|
|
33
32
|
initiator_keypair_getter, responder_keypair_getter = get_keypair_getter(initiator)
|
|
34
33
|
|
|
35
|
-
# Sets message_patterns to the message patterns from handshake_pattern
|
|
36
|
-
|
|
34
|
+
# Sets message_patterns to the message patterns from handshake_pattern. The inner arrays are
|
|
35
|
+
# copied too, so consuming them here can never reach back into the shared Pattern.
|
|
36
|
+
@message_patterns = @protocol.pattern.tokens.map(&:dup)
|
|
37
37
|
|
|
38
38
|
process_initiator_pre_messages(initiator_keypair_getter)
|
|
39
39
|
process_fallback(initiator_keypair_getter)
|
|
@@ -49,11 +49,11 @@ module Noise
|
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
def local_keypair_getter
|
|
52
|
-
->(token) { instance_variable_get(
|
|
52
|
+
->(token) { instance_variable_get("@#{token}").public_key }
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
def remote_keypair_getter
|
|
56
|
-
->(token) { instance_variable_get(
|
|
56
|
+
->(token) { instance_variable_get("@r#{token}") }
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
def process_initiator_pre_messages(keypair_getter)
|
|
@@ -100,6 +100,7 @@ module Noise
|
|
|
100
100
|
end
|
|
101
101
|
|
|
102
102
|
# Takes a payload byte sequence which may be zero-length, and a message_buffer to write the output into
|
|
103
|
+
# @return [Boolean] true if this was the last handshake message, false otherwise.
|
|
103
104
|
def write_message(payload, message_buffer)
|
|
104
105
|
pattern = @message_patterns.shift
|
|
105
106
|
|
|
@@ -118,18 +119,18 @@ module Noise
|
|
|
118
119
|
end
|
|
119
120
|
end
|
|
120
121
|
message_buffer << @symmetric_state.encrypt_and_hash(payload)
|
|
121
|
-
|
|
122
|
+
finish_handshake
|
|
122
123
|
end
|
|
123
124
|
|
|
124
125
|
# Takes a byte sequence containing a Noise handshake message,
|
|
125
126
|
# and a payload_buffer to write the message's plaintext payload into
|
|
127
|
+
# @return [Boolean] true if this was the last handshake message, false otherwise.
|
|
126
128
|
def read_message(message, payload_buffer)
|
|
127
129
|
pattern = @message_patterns.shift
|
|
128
130
|
pattern.each do |token|
|
|
129
131
|
case token
|
|
130
132
|
when Noise::Token::E
|
|
131
|
-
message, re = extract_key(message, false)
|
|
132
|
-
@re ||= re
|
|
133
|
+
message, @re = extract_key(message, false)
|
|
133
134
|
mix_e(@re)
|
|
134
135
|
when Noise::Token::S
|
|
135
136
|
message, @rs = extract_key(message, true)
|
|
@@ -140,11 +141,19 @@ module Noise
|
|
|
140
141
|
end
|
|
141
142
|
end
|
|
142
143
|
payload_buffer << @symmetric_state.decrypt_and_hash(message)
|
|
143
|
-
|
|
144
|
+
finish_handshake
|
|
144
145
|
end
|
|
145
146
|
|
|
146
147
|
private
|
|
147
148
|
|
|
149
|
+
# Splits into the transport cipher states once every message pattern has been processed.
|
|
150
|
+
def finish_handshake
|
|
151
|
+
return false unless @message_patterns.empty?
|
|
152
|
+
|
|
153
|
+
@symmetric_state.split
|
|
154
|
+
true
|
|
155
|
+
end
|
|
156
|
+
|
|
148
157
|
def extract_key(message, is_encrypted)
|
|
149
158
|
len = @protocol.dh_fn.dhlen
|
|
150
159
|
offset =
|
|
@@ -153,8 +162,10 @@ module Noise
|
|
|
153
162
|
else
|
|
154
163
|
0
|
|
155
164
|
end
|
|
165
|
+
raise Noise::Exceptions::NoiseHandshakeError, 'Message is too short.' if message.bytesize < len + offset
|
|
166
|
+
|
|
156
167
|
key = message[0...len + offset]
|
|
157
|
-
message = message[(len + offset)
|
|
168
|
+
message = message[(len + offset)..]
|
|
158
169
|
key = @symmetric_state.decrypt_and_hash(key) if is_encrypted
|
|
159
170
|
[message, key]
|
|
160
171
|
end
|
data/lib/noise/utils/string.rb
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
module Noise
|
|
4
|
+
module Utils
|
|
5
|
+
# Hex conversion helpers. Offered as a refinement so that requiring this gem does not add methods
|
|
6
|
+
# to String for the whole process.
|
|
7
|
+
#
|
|
8
|
+
# using Noise::Utils::HexString
|
|
9
|
+
# '0102'.htb # => "\x01\x02"
|
|
10
|
+
# "\x01\x02".bth # => "0102"
|
|
11
|
+
module HexString
|
|
12
|
+
refine ::String do
|
|
13
|
+
# hex to binary
|
|
14
|
+
def htb
|
|
15
|
+
[self].pack('H*')
|
|
16
|
+
end
|
|
7
17
|
|
|
8
|
-
|
|
9
|
-
|
|
18
|
+
# binary to hex
|
|
19
|
+
def bth
|
|
20
|
+
unpack1('H*')
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
10
24
|
end
|
|
11
25
|
end
|
data/lib/noise/version.rb
CHANGED
data/lib/noise.rb
CHANGED
|
@@ -8,7 +8,6 @@ require 'rbnacl'
|
|
|
8
8
|
require 'ruby_hmac'
|
|
9
9
|
require 'securerandom'
|
|
10
10
|
|
|
11
|
-
require 'noise/utils/hash'
|
|
12
11
|
require 'noise/utils/string'
|
|
13
12
|
|
|
14
13
|
module Noise
|
|
@@ -21,14 +20,29 @@ module Noise
|
|
|
21
20
|
autoload :Functions, 'noise/functions'
|
|
22
21
|
autoload :State, 'noise/state'
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
# Some DH and hash functions are backed by a gem, and often a system library, that is only needed
|
|
24
|
+
# when the function appears in a protocol name. Loading one is therefore allowed to fail; the
|
|
25
|
+
# failure is remembered and reported by optional_dependency! when the function is used.
|
|
26
|
+
@unavailable_dependencies = {}
|
|
27
|
+
|
|
28
|
+
class << self
|
|
29
|
+
def logger
|
|
30
|
+
@logger ||= Logger.new($stdout)
|
|
31
|
+
end
|
|
28
32
|
|
|
29
|
-
def
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
rescue LoadError => e
|
|
33
|
-
|
|
33
|
+
def require_optional(name)
|
|
34
|
+
require name
|
|
35
|
+
yield if block_given?
|
|
36
|
+
rescue LoadError => e
|
|
37
|
+
@unavailable_dependencies[name] = e.message
|
|
38
|
+
logger.warn("Optional dependency '#{name}' is unavailable: #{e.message}")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def optional_dependency!(name)
|
|
42
|
+
reason = @unavailable_dependencies[name]
|
|
43
|
+
return if reason.nil?
|
|
44
|
+
|
|
45
|
+
raise Noise::Exceptions::MissingDependencyError, "'#{name}' could not be loaded: #{reason}"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
34
48
|
end
|
data/noise.gemspec
CHANGED
|
@@ -5,7 +5,8 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
|
5
5
|
require 'noise/version'
|
|
6
6
|
|
|
7
7
|
Gem::Specification.new do |spec|
|
|
8
|
-
spec.name
|
|
8
|
+
spec.name = 'noise-ruby'
|
|
9
|
+
spec.required_ruby_version = '~> 3.0'
|
|
9
10
|
spec.version = Noise::VERSION
|
|
10
11
|
spec.authors = ['Hajime Yamaguchi']
|
|
11
12
|
spec.email = ['gen.yamaguchi0@gmail.com']
|
|
@@ -21,14 +22,23 @@ Gem::Specification.new do |spec|
|
|
|
21
22
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
22
23
|
spec.require_paths = ['lib']
|
|
23
24
|
|
|
24
|
-
spec.add_development_dependency 'blake3'
|
|
25
25
|
spec.add_development_dependency 'bundler', '~> 2.0'
|
|
26
26
|
spec.add_development_dependency 'rake', '>= 12.3.3'
|
|
27
27
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
28
|
+
|
|
29
|
+
spec.add_development_dependency 'rubocop'
|
|
30
|
+
spec.add_development_dependency 'rubocop-rspec'
|
|
31
|
+
spec.add_development_dependency 'simplecov'
|
|
32
|
+
spec.add_development_dependency 'simplecov-json'
|
|
33
|
+
|
|
34
|
+
# Optional backends. Each one is needed only when its function appears in a protocol name, and
|
|
35
|
+
# each also needs a system library that cannot be installed as a gem, so none of them is a runtime
|
|
36
|
+
# dependency. Add the one you need to your own Gemfile; see the README for the system libraries.
|
|
37
|
+
spec.add_development_dependency 'blake3'
|
|
38
|
+
spec.add_development_dependency 'ed448'
|
|
28
39
|
spec.add_development_dependency 'secp256k1-ruby'
|
|
29
40
|
|
|
30
41
|
spec.add_runtime_dependency 'ecdsa'
|
|
31
|
-
spec.add_runtime_dependency 'ed448'
|
|
32
42
|
spec.add_runtime_dependency 'rbnacl'
|
|
33
43
|
spec.add_runtime_dependency 'ruby-hmac'
|
|
34
44
|
end
|
metadata
CHANGED
|
@@ -1,29 +1,15 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: noise-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.11.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Hajime Yamaguchi
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-07-31 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
|
-
- !ruby/object:Gem::Dependency
|
|
14
|
-
name: blake3
|
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
|
16
|
-
requirements:
|
|
17
|
-
- - ">="
|
|
18
|
-
- !ruby/object:Gem::Version
|
|
19
|
-
version: '0'
|
|
20
|
-
type: :development
|
|
21
|
-
prerelease: false
|
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
-
requirements:
|
|
24
|
-
- - ">="
|
|
25
|
-
- !ruby/object:Gem::Version
|
|
26
|
-
version: '0'
|
|
27
13
|
- !ruby/object:Gem::Dependency
|
|
28
14
|
name: bundler
|
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -67,7 +53,7 @@ dependencies:
|
|
|
67
53
|
- !ruby/object:Gem::Version
|
|
68
54
|
version: '3.0'
|
|
69
55
|
- !ruby/object:Gem::Dependency
|
|
70
|
-
name:
|
|
56
|
+
name: rubocop
|
|
71
57
|
requirement: !ruby/object:Gem::Requirement
|
|
72
58
|
requirements:
|
|
73
59
|
- - ">="
|
|
@@ -81,13 +67,55 @@ dependencies:
|
|
|
81
67
|
- !ruby/object:Gem::Version
|
|
82
68
|
version: '0'
|
|
83
69
|
- !ruby/object:Gem::Dependency
|
|
84
|
-
name:
|
|
70
|
+
name: rubocop-rspec
|
|
85
71
|
requirement: !ruby/object:Gem::Requirement
|
|
86
72
|
requirements:
|
|
87
73
|
- - ">="
|
|
88
74
|
- !ruby/object:Gem::Version
|
|
89
75
|
version: '0'
|
|
90
|
-
type: :
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: simplecov
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - ">="
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '0'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - ">="
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '0'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: simplecov-json
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - ">="
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '0'
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - ">="
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '0'
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: blake3
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - ">="
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '0'
|
|
118
|
+
type: :development
|
|
91
119
|
prerelease: false
|
|
92
120
|
version_requirements: !ruby/object:Gem::Requirement
|
|
93
121
|
requirements:
|
|
@@ -96,6 +124,34 @@ dependencies:
|
|
|
96
124
|
version: '0'
|
|
97
125
|
- !ruby/object:Gem::Dependency
|
|
98
126
|
name: ed448
|
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
|
128
|
+
requirements:
|
|
129
|
+
- - ">="
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '0'
|
|
132
|
+
type: :development
|
|
133
|
+
prerelease: false
|
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
135
|
+
requirements:
|
|
136
|
+
- - ">="
|
|
137
|
+
- !ruby/object:Gem::Version
|
|
138
|
+
version: '0'
|
|
139
|
+
- !ruby/object:Gem::Dependency
|
|
140
|
+
name: secp256k1-ruby
|
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
|
142
|
+
requirements:
|
|
143
|
+
- - ">="
|
|
144
|
+
- !ruby/object:Gem::Version
|
|
145
|
+
version: '0'
|
|
146
|
+
type: :development
|
|
147
|
+
prerelease: false
|
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
149
|
+
requirements:
|
|
150
|
+
- - ">="
|
|
151
|
+
- !ruby/object:Gem::Version
|
|
152
|
+
version: '0'
|
|
153
|
+
- !ruby/object:Gem::Dependency
|
|
154
|
+
name: ecdsa
|
|
99
155
|
requirement: !ruby/object:Gem::Requirement
|
|
100
156
|
requirements:
|
|
101
157
|
- - ">="
|
|
@@ -143,12 +199,14 @@ executables: []
|
|
|
143
199
|
extensions: []
|
|
144
200
|
extra_rdoc_files: []
|
|
145
201
|
files:
|
|
202
|
+
- ".dockerignore"
|
|
146
203
|
- ".github/workflows/ruby.yml"
|
|
147
204
|
- ".gitignore"
|
|
148
205
|
- ".rspec"
|
|
149
206
|
- ".rubocop.yml"
|
|
150
207
|
- ".ruby-version"
|
|
151
208
|
- CODE_OF_CONDUCT.md
|
|
209
|
+
- Dockerfile
|
|
152
210
|
- Gemfile
|
|
153
211
|
- README.md
|
|
154
212
|
- Rakefile
|
|
@@ -164,6 +222,7 @@ files:
|
|
|
164
222
|
- lib/noise/exceptions/encrypt_error.rb
|
|
165
223
|
- lib/noise/exceptions/invalid_public_key_error.rb
|
|
166
224
|
- lib/noise/exceptions/max_nonce_error.rb
|
|
225
|
+
- lib/noise/exceptions/missing_dependency_error.rb
|
|
167
226
|
- lib/noise/exceptions/noise_handshake_error.rb
|
|
168
227
|
- lib/noise/exceptions/noise_psk_error.rb
|
|
169
228
|
- lib/noise/exceptions/noise_validation_error.rb
|
|
@@ -192,30 +251,29 @@ files:
|
|
|
192
251
|
- lib/noise/state/cipher_state.rb
|
|
193
252
|
- lib/noise/state/handshake_state.rb
|
|
194
253
|
- lib/noise/state/symmetric_state.rb
|
|
195
|
-
- lib/noise/utils/hash.rb
|
|
196
254
|
- lib/noise/utils/string.rb
|
|
197
255
|
- lib/noise/version.rb
|
|
198
256
|
- noise.gemspec
|
|
199
257
|
homepage: https://github.com/Yamaguchi/noise
|
|
200
258
|
licenses: []
|
|
201
259
|
metadata: {}
|
|
202
|
-
post_install_message:
|
|
260
|
+
post_install_message:
|
|
203
261
|
rdoc_options: []
|
|
204
262
|
require_paths:
|
|
205
263
|
- lib
|
|
206
264
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
207
265
|
requirements:
|
|
208
|
-
- - "
|
|
266
|
+
- - "~>"
|
|
209
267
|
- !ruby/object:Gem::Version
|
|
210
|
-
version: '0'
|
|
268
|
+
version: '3.0'
|
|
211
269
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
212
270
|
requirements:
|
|
213
271
|
- - ">="
|
|
214
272
|
- !ruby/object:Gem::Version
|
|
215
273
|
version: '0'
|
|
216
274
|
requirements: []
|
|
217
|
-
rubygems_version: 3.
|
|
218
|
-
signing_key:
|
|
275
|
+
rubygems_version: 3.5.16
|
|
276
|
+
signing_key:
|
|
219
277
|
specification_version: 4
|
|
220
278
|
summary: A Ruby implementation of the Noise Protocol framework
|
|
221
279
|
test_files: []
|