noise-ruby 0.11.0 → 0.13.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/.github/workflows/ruby.yml +2 -3
- data/.rubocop.yml +1 -1
- data/.ruby-version +1 -1
- data/Dockerfile +4 -5
- data/Gemfile +2 -2
- data/README.md +47 -33
- data/lib/noise/connection/base.rb +107 -11
- data/lib/noise/exceptions/invalid_nonce_error.rb +9 -0
- data/lib/noise/exceptions/message_too_long_error.rb +20 -0
- data/lib/noise/exceptions.rb +2 -0
- data/lib/noise/functions/dh/ed25519.rb +12 -0
- data/lib/noise/functions/dh/ed448.rb +29 -12
- data/lib/noise/functions/dh/secp256k1.rb +84 -12
- data/lib/noise/state/cipher_state.rb +13 -0
- data/lib/noise/state/handshake_state.rb +6 -0
- data/lib/noise/version.rb +1 -1
- data/lib/noise.rb +14 -7
- data/noise.gemspec +9 -7
- metadata +12 -41
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 326691ebec86a68d5b4978756edcd5752c7307e4eba85c615e08262f8133481e
|
|
4
|
+
data.tar.gz: 0626adc9ec4b0821211b04b3573a950684ca526edcd5e956450107d4d8c69ce0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2246a0d4be698a11efb560f6e9ddd70b50a926255c32877d8fd7346780e99e4d2950ac2e10ed81fb787ce9a61f626027e69136e94d680c9959e79c1702c73c47
|
|
7
|
+
data.tar.gz: fe3e968bbb464d11bd574750bdd97410b7ef5564328b327946654f76f8476fae4eadb7fbb8acebf4c4036b4f5edaa2d4a5b265c38705cc2ad33e25f310f78255
|
data/.github/workflows/ruby.yml
CHANGED
|
@@ -19,7 +19,7 @@ jobs:
|
|
|
19
19
|
strategy:
|
|
20
20
|
matrix:
|
|
21
21
|
ruby:
|
|
22
|
-
- '
|
|
22
|
+
- '4.0'
|
|
23
23
|
steps:
|
|
24
24
|
- name: Checkout code
|
|
25
25
|
uses: actions/checkout@v4
|
|
@@ -37,11 +37,10 @@ jobs:
|
|
|
37
37
|
strategy:
|
|
38
38
|
matrix:
|
|
39
39
|
ruby:
|
|
40
|
-
- '3.0'
|
|
41
|
-
- '3.1'
|
|
42
40
|
- '3.2'
|
|
43
41
|
- '3.3'
|
|
44
42
|
- '3.4'
|
|
43
|
+
- '4.0'
|
|
45
44
|
steps:
|
|
46
45
|
- name: Checkout code
|
|
47
46
|
uses: actions/checkout@v4
|
data/.rubocop.yml
CHANGED
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
4.0.6
|
data/Dockerfile
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
#
|
|
2
|
-
#
|
|
3
|
-
#
|
|
4
|
-
|
|
5
|
-
FROM --platform=linux/amd64 ruby:3.3
|
|
1
|
+
# A Linux environment close to the CI runner, for checking a change against the Ruby version CI
|
|
2
|
+
# lints with. Nothing in the suite is architecture specific, so the image builds for the host
|
|
3
|
+
# architecture and needs no emulation.
|
|
4
|
+
FROM ruby:4.0
|
|
6
5
|
|
|
7
6
|
# libsodium: dlopen'd by rbnacl, which lib/noise.rb requires unconditionally.
|
|
8
7
|
# cargo: the blake3 gem is a Rust extension and is built at install time.
|
data/Gemfile
CHANGED
|
@@ -4,6 +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. The optional
|
|
8
|
-
#
|
|
7
|
+
# Specify your gem's dependencies in noise.gemspec. The optional blake3 backend is a development
|
|
8
|
+
# dependency there, so the test suite covers it too.
|
|
9
9
|
gemspec
|
data/README.md
CHANGED
|
@@ -30,6 +30,9 @@ Supported Features:
|
|
|
30
30
|
|
|
31
31
|
## Installation
|
|
32
32
|
|
|
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
|
+
past end of life and are no longer supported; stay on noise-ruby 0.12.0 or earlier if you need them.
|
|
35
|
+
|
|
33
36
|
This gem needs libsodium library.
|
|
34
37
|
To install libsodium, see https://github.com/jedisct1/libsodium
|
|
35
38
|
|
|
@@ -47,39 +50,6 @@ Or install it yourself as:
|
|
|
47
50
|
|
|
48
51
|
$ gem install noise-ruby
|
|
49
52
|
|
|
50
|
-
If you use Ed448 as DH function, you must install [libgoldilocks](https://github.com/otrv4/libgoldilocks).
|
|
51
|
-
|
|
52
|
-
After installing, define an environment variable as follows:
|
|
53
|
-
|
|
54
|
-
* on macOS
|
|
55
|
-
|
|
56
|
-
$ export LIBGOLDILOCKS=/usr/local/lib/libgoldilocks.dylib
|
|
57
|
-
|
|
58
|
-
* on Linux(Ubuntu)
|
|
59
|
-
|
|
60
|
-
$ export LIBGOLDILOCKS=/usr/local/lib/libgoldilocks.so
|
|
61
|
-
|
|
62
|
-
and, add this line to your Gemfile:
|
|
63
|
-
|
|
64
|
-
```
|
|
65
|
-
gem 'ed448'
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
If you use Secp256k1, you must install [libsecp256k1](https://github.com/bitcoin-core/secp256k1).
|
|
69
|
-
|
|
70
|
-
$ git clone https://github.com/bitcoin-core/secp256k1
|
|
71
|
-
$ cd secp256k1
|
|
72
|
-
$ ./autogen.sh
|
|
73
|
-
$ ./configure --enable-module-recovery --enable-experimental --enable-module-ecdh
|
|
74
|
-
$ make
|
|
75
|
-
$ sudo make install
|
|
76
|
-
|
|
77
|
-
and, add this line to your Gemfile:
|
|
78
|
-
|
|
79
|
-
```
|
|
80
|
-
gem 'secp256k1-ruby'
|
|
81
|
-
```
|
|
82
|
-
|
|
83
53
|
If you use BLAKE3, you must install [Rust and Cargo](https://www.rust-lang.org/tools/install).
|
|
84
54
|
And add this line to your Gemfile:
|
|
85
55
|
|
|
@@ -93,6 +63,25 @@ Followings shows handshake protocol with "Noise_NN_25519_ChaChaPoly_BLAKE2b"
|
|
|
93
63
|
|
|
94
64
|
### Handshake
|
|
95
65
|
|
|
66
|
+
#### Supplying keys
|
|
67
|
+
|
|
68
|
+
Patterns other than `NN` need keys before the handshake starts. `Connection::Initiator.new` and
|
|
69
|
+
`Connection::Responder.new` take them in the `keypairs:` hash: `:s` is the local static private key,
|
|
70
|
+
`:rs` and `:re` are the remote party's static and ephemeral public keys.
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
initiator = Noise::Connection::Initiator.new("Noise_XX_25519_ChaChaPoly_SHA256", keypairs: { s: static_private_key })
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`keypairs:` also accepts `:e`, the local ephemeral private key. **It exists only so that
|
|
77
|
+
`spec/vectors_spec.rb` can reproduce the official test vectors, which fix both sides' ephemeral keys
|
|
78
|
+
to make the output deterministic. Never set it in production.** A connection created with `:e` reuses
|
|
79
|
+
that ephemeral keypair instead of generating a fresh one per handshake, which destroys the forward
|
|
80
|
+
secrecy every pattern depends on: an attacker who recovers the key can decrypt every session that
|
|
81
|
+
used it, past and future. Nothing fails and no warning is printed - the handshake still succeeds and
|
|
82
|
+
your own tests still pass - so the loss is invisible. Leave `:e` unset and let the library generate
|
|
83
|
+
it.
|
|
84
|
+
|
|
96
85
|
#### initiator
|
|
97
86
|
|
|
98
87
|
```
|
|
@@ -138,6 +127,31 @@ cipher = initiator.encrypt("Hello, World!") # => "\xDA\xC7\xD7as\v\xFA\xCC,\xB3\
|
|
|
138
127
|
plain = responder.decrypt(cipher) # => "Hello, World!"
|
|
139
128
|
```
|
|
140
129
|
|
|
130
|
+
#### Out-of-order transport messages
|
|
131
|
+
|
|
132
|
+
Each party counts the transport messages of each direction with a nonce. If the transport layer can
|
|
133
|
+
deliver messages out of order or lose them, it has to carry the nonce of each message, and the
|
|
134
|
+
receiver sets the nonce before decrypting. Restore the previous value if the message fails to
|
|
135
|
+
authenticate, so that the following messages are still decryptable.
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
initiator.encryption_nonce # => 0
|
|
139
|
+
responder.decryption_nonce = 2 # decrypt the message numbered 2 next
|
|
140
|
+
plain = responder.decrypt(cipher)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
#### Rekey
|
|
144
|
+
|
|
145
|
+
Rekeying replaces the key of one direction with `REKEY(k)`, so that a key compromised later cannot
|
|
146
|
+
decrypt the messages that came before it. The nonce keeps counting. Both parties must rekey the
|
|
147
|
+
matching direction at the same point of the message stream; when that happens is up to the
|
|
148
|
+
application protocol.
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
initiator.rekey_encryption
|
|
152
|
+
responder.rekey_decryption
|
|
153
|
+
```
|
|
154
|
+
|
|
141
155
|
## Development
|
|
142
156
|
|
|
143
157
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
@@ -3,13 +3,26 @@
|
|
|
3
3
|
module Noise
|
|
4
4
|
module Connection
|
|
5
5
|
class Base
|
|
6
|
-
# The Noise spec caps a handshake or transport message at 65535 bytes.
|
|
6
|
+
# The Noise spec caps a handshake or transport message at 65535 bytes. A transport message is
|
|
7
|
+
# the ciphertext, so the plaintext a caller may hand to encrypt is shorter by the
|
|
8
|
+
# authentication tag that ENCRYPT() appends.
|
|
7
9
|
MAX_MESSAGE_LENGTH = 65_535
|
|
10
|
+
MAX_PLAINTEXT_LENGTH = MAX_MESSAGE_LENGTH - Noise::State::CipherState::TAG_LENGTH
|
|
8
11
|
|
|
9
12
|
attr_reader :protocol, :handshake_started, :handshake_finished, :handshake_hash, :handshake_state,
|
|
10
13
|
:cipher_state_encrypt, :cipher_state_decrypt, :cipher_state_handshake, :s, :rs
|
|
11
14
|
attr_accessor :psks, :prologue
|
|
12
15
|
|
|
16
|
+
# @param [String] name the protocol name, for example 'Noise_XX_25519_ChaChaPoly_SHA256'.
|
|
17
|
+
# @param [Hash] keypairs the keys the pattern needs, as private or public key strings.
|
|
18
|
+
# :s is the local static private key, :rs and :re the remote static and ephemeral public keys.
|
|
19
|
+
#
|
|
20
|
+
# :e sets the local ephemeral private key. It exists only so that spec/vectors_spec.rb can
|
|
21
|
+
# reproduce the official test vectors, which fix both sides' ephemeral keys to make the
|
|
22
|
+
# output deterministic. Never set it outside that use: HandshakeState#write_message reuses
|
|
23
|
+
# the keypair given here instead of generating a fresh one, and an ephemeral key reused
|
|
24
|
+
# across handshakes gives up the forward secrecy every pattern depends on. The handshake
|
|
25
|
+
# still succeeds, so nothing reports the loss.
|
|
13
26
|
def initialize(name, keypairs: { s: nil, e: nil, rs: nil, re: nil })
|
|
14
27
|
@protocol = Protocol.create(name)
|
|
15
28
|
|
|
@@ -61,8 +74,10 @@ module Noise
|
|
|
61
74
|
raise Noise::Exceptions::NoiseHandshakeError if @next_message != :write
|
|
62
75
|
raise Noise::Exceptions::NoiseHandshakeError if @handshake_finished
|
|
63
76
|
|
|
64
|
-
|
|
65
|
-
|
|
77
|
+
length = @handshake_state.expected_message_length(payload.bytesize)
|
|
78
|
+
if length > MAX_MESSAGE_LENGTH
|
|
79
|
+
raise Noise::Exceptions::MessageTooLongError,
|
|
80
|
+
"Message would be #{length} bytes, which exceeds the maximum of #{MAX_MESSAGE_LENGTH}."
|
|
66
81
|
end
|
|
67
82
|
|
|
68
83
|
@next_message = :read
|
|
@@ -76,8 +91,11 @@ module Noise
|
|
|
76
91
|
raise Noise::Exceptions::NoiseHandshakeError unless @handshake_started
|
|
77
92
|
raise Noise::Exceptions::NoiseHandshakeError if @next_message != :read
|
|
78
93
|
raise Noise::Exceptions::NoiseHandshakeError if @handshake_finished
|
|
79
|
-
|
|
80
|
-
|
|
94
|
+
|
|
95
|
+
if data.bytesize > MAX_MESSAGE_LENGTH
|
|
96
|
+
raise Noise::Exceptions::MessageTooLongError,
|
|
97
|
+
"Message is #{data.bytesize} bytes, which exceeds the maximum of #{MAX_MESSAGE_LENGTH}."
|
|
98
|
+
end
|
|
81
99
|
|
|
82
100
|
@next_message = :write
|
|
83
101
|
buffer = +''
|
|
@@ -86,21 +104,82 @@ module Noise
|
|
|
86
104
|
end
|
|
87
105
|
|
|
88
106
|
def encrypt(data)
|
|
89
|
-
|
|
107
|
+
cipher_state = transport_cipher_state(:encrypt)
|
|
108
|
+
if data.bytesize > MAX_PLAINTEXT_LENGTH
|
|
109
|
+
raise Noise::Exceptions::MessageTooLongError,
|
|
110
|
+
"Plaintext is #{data.bytesize} bytes, which exceeds the maximum of #{MAX_PLAINTEXT_LENGTH}."
|
|
111
|
+
end
|
|
90
112
|
|
|
91
|
-
|
|
113
|
+
cipher_state.encrypt_with_ad('', data)
|
|
92
114
|
end
|
|
93
115
|
|
|
94
116
|
def decrypt(data)
|
|
95
|
-
|
|
117
|
+
cipher_state = transport_cipher_state(:decrypt)
|
|
118
|
+
# Rejected before the cipher state is used, so an over-long message leaves n untouched
|
|
119
|
+
# and the connection usable, exactly as a failed decryption does.
|
|
120
|
+
if data.bytesize > MAX_MESSAGE_LENGTH
|
|
121
|
+
raise Noise::Exceptions::MessageTooLongError,
|
|
122
|
+
"Message is #{data.bytesize} bytes, which exceeds the maximum of #{MAX_MESSAGE_LENGTH}."
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
cipher_state.decrypt_with_ad('', data)
|
|
126
|
+
end
|
|
96
127
|
|
|
97
|
-
|
|
128
|
+
# @return [Integer] the nonce the next #encrypt call uses.
|
|
129
|
+
def encryption_nonce
|
|
130
|
+
transport_cipher_state(:encrypt).n
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# @return [Integer] the nonce the next #decrypt call uses.
|
|
134
|
+
def decryption_nonce
|
|
135
|
+
transport_cipher_state(:decrypt).n
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Sets the nonce of the next #encrypt call. Needed when the transport layer numbers the
|
|
139
|
+
# messages itself instead of relying on the sender and the receiver counting in step.
|
|
140
|
+
#
|
|
141
|
+
# @param [Integer] nonce a value between 0 and CipherState::MAX_NONCE.
|
|
142
|
+
def encryption_nonce=(nonce)
|
|
143
|
+
transport_cipher_state(:encrypt).nonce = nonce
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Sets the nonce of the next #decrypt call. This is how the Noise spec handles transport
|
|
147
|
+
# messages that arrive out of order: the receiver sets n to the nonce of the message it is
|
|
148
|
+
# about to decrypt, and restores the previous value if the message fails to authenticate.
|
|
149
|
+
#
|
|
150
|
+
# @param [Integer] nonce a value between 0 and CipherState::MAX_NONCE.
|
|
151
|
+
def decryption_nonce=(nonce)
|
|
152
|
+
transport_cipher_state(:decrypt).nonce = nonce
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Replaces the key used by #encrypt with REKEY(k), so that the old key cannot decrypt the
|
|
156
|
+
# messages that follow. Both parties must rekey the matching direction at the same point of
|
|
157
|
+
# the message stream, which is up to the application protocol to agree on.
|
|
158
|
+
#
|
|
159
|
+
# @return [void]
|
|
160
|
+
def rekey_encryption
|
|
161
|
+
transport_cipher_state(:encrypt).rekey
|
|
162
|
+
nil
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Replaces the key used by #decrypt with REKEY(k). See #rekey_encryption.
|
|
166
|
+
#
|
|
167
|
+
# @return [void]
|
|
168
|
+
def rekey_decryption
|
|
169
|
+
transport_cipher_state(:decrypt).rekey
|
|
170
|
+
nil
|
|
98
171
|
end
|
|
99
172
|
|
|
100
173
|
def validate_psk!
|
|
174
|
+
raise Noise::Exceptions::NoisePSKError, 'psks are not set.' if @psks.nil?
|
|
101
175
|
# Invalid psk length! Has to be 32 bytes long
|
|
102
|
-
raise Noise::Exceptions::NoisePSKError
|
|
103
|
-
|
|
176
|
+
raise Noise::Exceptions::NoisePSKError, 'psks have to be 32 bytes long.' if
|
|
177
|
+
@psks.any? { |psk| psk.bytesize != 32 }
|
|
178
|
+
|
|
179
|
+
return if @protocol.pattern.psk_count == @psks.count
|
|
180
|
+
|
|
181
|
+
raise Noise::Exceptions::NoisePSKError,
|
|
182
|
+
"This protocol needs #{@protocol.pattern.psk_count} psks, got #{@psks.count}."
|
|
104
183
|
end
|
|
105
184
|
|
|
106
185
|
def missing_keypairs?
|
|
@@ -124,6 +203,23 @@ module Noise
|
|
|
124
203
|
@symmetric_state = nil
|
|
125
204
|
@cipher_state_handshake = nil
|
|
126
205
|
end
|
|
206
|
+
|
|
207
|
+
private
|
|
208
|
+
|
|
209
|
+
# Returns the transport CipherState of the given direction.
|
|
210
|
+
#
|
|
211
|
+
# One-way patterns leave the direction the caller cannot use as nil, so the absence of a
|
|
212
|
+
# cipher state is reported the same way as a handshake that has not finished yet.
|
|
213
|
+
#
|
|
214
|
+
# @param [Symbol] direction :encrypt or :decrypt.
|
|
215
|
+
def transport_cipher_state(direction)
|
|
216
|
+
raise Noise::Exceptions::NoiseHandshakeError unless @handshake_finished
|
|
217
|
+
|
|
218
|
+
cipher_state = direction == :encrypt ? @cipher_state_encrypt : @cipher_state_decrypt
|
|
219
|
+
raise Noise::Exceptions::NoiseHandshakeError, "This party cannot #{direction} messages." unless cipher_state
|
|
220
|
+
|
|
221
|
+
cipher_state
|
|
222
|
+
end
|
|
127
223
|
end
|
|
128
224
|
end
|
|
129
225
|
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Noise
|
|
4
|
+
module Exceptions
|
|
5
|
+
# Raised when a message would exceed, or does exceed, the 65535 byte limit the Noise
|
|
6
|
+
# specification places on every message. Raised from the handshake and the transport paths
|
|
7
|
+
# alike, so that a caller handling "the peer sent something too long" rescues one class rather
|
|
8
|
+
# than one per phase.
|
|
9
|
+
#
|
|
10
|
+
# It covers over-long messages only, and the other framing errors keep their own classes: a
|
|
11
|
+
# truncated handshake message raises NoiseHandshakeError, and a transport message shorter than
|
|
12
|
+
# the authentication tag raises DecryptError. A caller that treats every framing error alike
|
|
13
|
+
# has to rescue all three.
|
|
14
|
+
#
|
|
15
|
+
# Kept apart from DecryptError so that a badly framed message can be told from a failed
|
|
16
|
+
# authentication, which has very different security meaning.
|
|
17
|
+
class MessageTooLongError < StandardError
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
data/lib/noise/exceptions.rb
CHANGED
|
@@ -4,8 +4,10 @@ module Noise
|
|
|
4
4
|
module Exceptions
|
|
5
5
|
autoload :DecryptError, 'noise/exceptions/decrypt_error'
|
|
6
6
|
autoload :EncryptError, 'noise/exceptions/encrypt_error'
|
|
7
|
+
autoload :InvalidNonceError, 'noise/exceptions/invalid_nonce_error'
|
|
7
8
|
autoload :InvalidPublicKeyError, 'noise/exceptions/invalid_public_key_error'
|
|
8
9
|
autoload :MaxNonceError, 'noise/exceptions/max_nonce_error'
|
|
10
|
+
autoload :MessageTooLongError, 'noise/exceptions/message_too_long_error'
|
|
9
11
|
autoload :MissingDependencyError, 'noise/exceptions/missing_dependency_error'
|
|
10
12
|
autoload :ProtocolNameError, 'noise/exceptions/protocol_name_error'
|
|
11
13
|
autoload :NoiseHandshakeError, 'noise/exceptions/noise_handshake_error'
|
|
@@ -12,8 +12,20 @@ module Noise
|
|
|
12
12
|
Noise::Key.new(ECDSA::Format::IntegerOctetString.encode(private_key, 32), public_key.to_bytes)
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
+
# Computes the X25519 shared secret for the given remote public key.
|
|
16
|
+
#
|
|
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.
|
|
15
23
|
def dh(private_key, public_key)
|
|
24
|
+
raise Noise::Exceptions::InvalidPublicKeyError, public_key unless public_key.bytesize == DHLEN
|
|
25
|
+
|
|
16
26
|
RbNaCl::GroupElement.new(public_key).mult(private_key).to_bytes
|
|
27
|
+
rescue RbNaCl::CryptoError
|
|
28
|
+
raise Noise::Exceptions::InvalidPublicKeyError, public_key
|
|
17
29
|
end
|
|
18
30
|
|
|
19
31
|
def dhlen
|
|
@@ -1,26 +1,43 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
Noise.require_optional('ed448') { Ed448.init }
|
|
4
|
-
|
|
5
3
|
module Noise
|
|
6
4
|
module Functions
|
|
7
5
|
module DH
|
|
6
|
+
# The 448 DH function of the Noise specification, which is X448 as defined by RFC 7748 and
|
|
7
|
+
# not the Ed448 signature scheme the class name suggests. The name is kept because it is what
|
|
8
|
+
# Protocol::DH maps '448' to and what callers reference.
|
|
9
|
+
#
|
|
10
|
+
# OpenSSL implements X448 and exposes it through the raw key interface, so this function needs
|
|
11
|
+
# no gem and no system library beyond the OpenSSL that AesGcm and the HMAC helpers already use.
|
|
8
12
|
class ED448
|
|
9
|
-
# Ed448::X448::X448_PRIVATE_BYTES, spelled out so this file loads without the gem.
|
|
10
13
|
DHLEN = 56
|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
# The name OpenSSL knows the curve by. Ed448 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 = 'X448'
|
|
15
18
|
|
|
16
19
|
def generate_keypair
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
Noise::Key.new(private_key, public_key)
|
|
20
|
+
pkey = OpenSSL::PKey.generate_key(ALGORITHM)
|
|
21
|
+
Noise::Key.new(pkey.raw_private_key, pkey.raw_public_key)
|
|
20
22
|
end
|
|
21
23
|
|
|
24
|
+
# Computes the X448 shared secret for the given remote public key.
|
|
25
|
+
#
|
|
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.
|
|
22
32
|
def dh(private_key, public_key)
|
|
23
|
-
|
|
33
|
+
raise Noise::Exceptions::InvalidPublicKeyError, public_key unless public_key.bytesize == DHLEN
|
|
34
|
+
|
|
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
|
|
24
41
|
end
|
|
25
42
|
|
|
26
43
|
def dhlen
|
|
@@ -28,8 +45,8 @@ module Noise
|
|
|
28
45
|
end
|
|
29
46
|
|
|
30
47
|
def self.from_private(private_key)
|
|
31
|
-
|
|
32
|
-
Noise::Key.new(private_key,
|
|
48
|
+
pkey = OpenSSL::PKey.new_raw_private_key(ALGORITHM, private_key)
|
|
49
|
+
Noise::Key.new(private_key, pkey.raw_public_key)
|
|
33
50
|
end
|
|
34
51
|
end
|
|
35
52
|
end
|
|
@@ -1,13 +1,35 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
Noise.require_optional 'secp256k1'
|
|
4
|
-
|
|
5
3
|
module Noise
|
|
6
4
|
module Functions
|
|
7
5
|
module DH
|
|
6
|
+
# The secp256k1 DH function, as the Lightning Network uses it in BOLT #8.
|
|
7
|
+
#
|
|
8
|
+
# The shared secret is SHA256 of the shared point in compressed form, not the raw X
|
|
9
|
+
# coordinate. That is what BOLT #8 specifies and what libsecp256k1's ecdh returned when this
|
|
10
|
+
# function was backed by that library, so the value on the wire is unchanged.
|
|
8
11
|
class Secp256k1
|
|
12
|
+
# Length of a compressed secp256k1 point. The 65-byte uncompressed form encodes the same
|
|
13
|
+
# point, but Noise exchanges only the compressed one, so it is rejected.
|
|
14
|
+
DHLEN = 33
|
|
15
|
+
|
|
16
|
+
# Length of a secp256k1 scalar, which is what a private key is.
|
|
17
|
+
PRIVATE_KEY_LEN = 32
|
|
18
|
+
|
|
19
|
+
# The name OpenSSL knows the curve by.
|
|
20
|
+
CURVE = 'secp256k1'
|
|
21
|
+
|
|
22
|
+
# Builds the curve group once, because dh needs it on every call.
|
|
23
|
+
#
|
|
24
|
+
# secp256k1 is a builtin curve of OpenSSL 3, but an OpenSSL restricted to a FIPS provider
|
|
25
|
+
# does not offer it. That leaves the function unusable in the same way a missing system
|
|
26
|
+
# library did, so it is reported as MissingDependencyError rather than as an OpenSSL error
|
|
27
|
+
# raised in the middle of a handshake.
|
|
9
28
|
def initialize
|
|
10
|
-
|
|
29
|
+
@group = OpenSSL::PKey::EC::Group.new(CURVE)
|
|
30
|
+
rescue OpenSSL::PKey::EC::Group::Error => e
|
|
31
|
+
raise Noise::Exceptions::MissingDependencyError,
|
|
32
|
+
"OpenSSL does not offer the #{CURVE} curve: #{e.message}"
|
|
11
33
|
end
|
|
12
34
|
|
|
13
35
|
def generate_keypair
|
|
@@ -15,28 +37,78 @@ module Noise
|
|
|
15
37
|
private_key = 1 + SecureRandom.random_number(group.order - 1)
|
|
16
38
|
public_key = group.generator.multiply_by_scalar(private_key)
|
|
17
39
|
Noise::Key.new(
|
|
18
|
-
ECDSA::Format::IntegerOctetString.encode(private_key,
|
|
40
|
+
ECDSA::Format::IntegerOctetString.encode(private_key, PRIVATE_KEY_LEN),
|
|
19
41
|
ECDSA::Format::PointOctetString.encode(public_key, compression: true)
|
|
20
42
|
)
|
|
21
43
|
end
|
|
22
44
|
|
|
45
|
+
# Computes the ECDH shared secret for the given remote public key.
|
|
46
|
+
#
|
|
47
|
+
# Every way a peer-supplied key can be unusable raises InvalidPublicKeyError, matching the
|
|
48
|
+
# other DH functions: a length other than DHLEN, and an encoding that names no point on the
|
|
49
|
+
# curve. A private key the caller owns is not the peer's fault, so a malformed one raises
|
|
50
|
+
# ArgumentError instead, as it did when the gem parsed it.
|
|
23
51
|
def dh(private_key, public_key)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
52
|
+
raise Noise::Exceptions::InvalidPublicKeyError, public_key unless public_key.bytesize == DHLEN
|
|
53
|
+
|
|
54
|
+
scalar = OpenSSL::BN.new(self.class.decode_private_key(private_key))
|
|
55
|
+
shared = parse_public_key(public_key).mul(scalar)
|
|
56
|
+
# A backstop rather than a reachable case: secp256k1 has cofactor 1, so a point that
|
|
57
|
+
# parsed has order n, and the scalar is already known to be in [1, n-1]. It stays because
|
|
58
|
+
# the cost is one comparison and the failure it guards is severe - the compressed
|
|
59
|
+
# encoding of infinity is the single byte 0x00, whose SHA256 anyone can precompute.
|
|
60
|
+
raise Noise::Exceptions::InvalidPublicKeyError, public_key if shared.infinity?
|
|
61
|
+
|
|
62
|
+
OpenSSL::Digest.digest('SHA256', shared.to_octet_string(:compressed))
|
|
28
63
|
end
|
|
29
64
|
|
|
30
65
|
def dhlen
|
|
31
|
-
|
|
66
|
+
DHLEN
|
|
32
67
|
end
|
|
33
68
|
|
|
34
69
|
def self.from_private(private_key)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
point = group.generator.multiply_by_scalar(scalar)
|
|
70
|
+
scalar = decode_private_key(private_key)
|
|
71
|
+
point = ECDSA::Group::Secp256k1.generator.multiply_by_scalar(scalar)
|
|
38
72
|
Noise::Key.new(private_key, ECDSA::Format::PointOctetString.encode(point, compression: true))
|
|
39
73
|
end
|
|
74
|
+
|
|
75
|
+
# Decodes a private key into the scalar it denotes, rejecting the values that cannot serve
|
|
76
|
+
# as one. Both entry points that take a private key go through this, so a key from_private
|
|
77
|
+
# accepts is one dh can use.
|
|
78
|
+
#
|
|
79
|
+
# Rejected are a length other than PRIVATE_KEY_LEN, zero, and anything at or above the
|
|
80
|
+
# group order: those scalars multiply every point to infinity, whose compressed encoding is
|
|
81
|
+
# the single byte 0x00. Without the check from_private would hand back a Noise::Key holding
|
|
82
|
+
# that one byte as its public key, and the handshake it is used in would fail only once the
|
|
83
|
+
# key reached the wire. A private key belongs to the caller rather than to the peer, so a
|
|
84
|
+
# bad one raises ArgumentError and not InvalidPublicKeyError.
|
|
85
|
+
def self.decode_private_key(private_key)
|
|
86
|
+
unless private_key.bytesize == PRIVATE_KEY_LEN
|
|
87
|
+
raise ArgumentError, "private key must be #{PRIVATE_KEY_LEN} bytes"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
scalar = ECDSA::Format::IntegerOctetString.decode(private_key)
|
|
91
|
+
order = ECDSA::Group::Secp256k1.order
|
|
92
|
+
raise ArgumentError, 'private key is out of range' unless scalar.between?(1, order - 1)
|
|
93
|
+
|
|
94
|
+
scalar
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
|
|
99
|
+
# Decodes a public key into a point on the curve.
|
|
100
|
+
#
|
|
101
|
+
# The key is passed as a String, not as an OpenSSL::BN. OpenSSL then decodes it as an octet
|
|
102
|
+
# string and raises Point::Error for anything that is not a point on the curve, the
|
|
103
|
+
# all-zero key included: 0x00 introduces the encoding of infinity, which is one byte long,
|
|
104
|
+
# so 33 zero bytes are rejected as a malformed encoding. Handing over a BN instead would
|
|
105
|
+
# drop the leading zero byte and produce the point at infinity, whose shared secret is a
|
|
106
|
+
# constant that the sender of such a key could precompute.
|
|
107
|
+
def parse_public_key(public_key)
|
|
108
|
+
OpenSSL::PKey::EC::Point.new(@group, public_key)
|
|
109
|
+
rescue OpenSSL::PKey::EC::Point::Error
|
|
110
|
+
raise Noise::Exceptions::InvalidPublicKeyError, public_key
|
|
111
|
+
end
|
|
40
112
|
end
|
|
41
113
|
end
|
|
42
114
|
end
|
|
@@ -29,7 +29,17 @@ module Noise
|
|
|
29
29
|
!@k.nil?
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
+
# Sets n, the nonce the next encrypt_with_ad or decrypt_with_ad call uses. This is SetNonce()
|
|
33
|
+
# of the Noise spec, needed to decrypt transport messages that arrive out of order.
|
|
34
|
+
#
|
|
35
|
+
# The value is checked here because the ciphers pack n into 8 bytes: a value outside the
|
|
36
|
+
# unsigned 64-bit range silently produces a wrong nonce instead of an error.
|
|
37
|
+
#
|
|
38
|
+
# @param [Integer] nonce a value between 0 and MAX_NONCE.
|
|
39
|
+
# @raise [Noise::Exceptions::InvalidNonceError] if nonce is out of that range.
|
|
32
40
|
def nonce=(nonce)
|
|
41
|
+
raise Noise::Exceptions::InvalidNonceError unless nonce.is_a?(Integer) && nonce.between?(0, MAX_NONCE)
|
|
42
|
+
|
|
33
43
|
@n = nonce
|
|
34
44
|
end
|
|
35
45
|
|
|
@@ -56,6 +66,9 @@ module Noise
|
|
|
56
66
|
plaintext
|
|
57
67
|
end
|
|
58
68
|
|
|
69
|
+
# Replaces k with REKEY(k). n is left as it is, as the Noise spec requires.
|
|
70
|
+
#
|
|
71
|
+
# @return [String] the new 32 bytes key.
|
|
59
72
|
def rekey
|
|
60
73
|
@k = @cipher.rekey(@k)
|
|
61
74
|
end
|
|
@@ -107,6 +107,12 @@ module Noise
|
|
|
107
107
|
pattern.each do |token|
|
|
108
108
|
case token
|
|
109
109
|
when Noise::Token::E
|
|
110
|
+
# A fresh keypair per handshake is what the pattern's forward secrecy rests on. Two
|
|
111
|
+
# things preset @e. Connection::Base#fallback carries the ephemeral of the aborted
|
|
112
|
+
# handshake into the new state, which the fallback patterns require: process_fallback
|
|
113
|
+
# mixes its public key as a pre-message. A caller passing keypairs[:e] to
|
|
114
|
+
# Connection::Base#initialize also presets it, and that is supported for reproducing
|
|
115
|
+
# the official test vectors and nothing else.
|
|
110
116
|
@e ||= @protocol.dh_fn.generate_keypair
|
|
111
117
|
message_buffer << @e.public_key
|
|
112
118
|
mix_e(@e.public_key)
|
data/lib/noise/version.rb
CHANGED
data/lib/noise.rb
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
require 'noise/version'
|
|
4
4
|
|
|
5
5
|
require 'ecdsa'
|
|
6
|
-
require '
|
|
6
|
+
require 'openssl'
|
|
7
7
|
require 'rbnacl'
|
|
8
8
|
require 'ruby_hmac'
|
|
9
9
|
require 'securerandom'
|
|
@@ -26,18 +26,25 @@ module Noise
|
|
|
26
26
|
@unavailable_dependencies = {}
|
|
27
27
|
|
|
28
28
|
class << self
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
# Requires an optional backend gem and yields once it is loaded. A LoadError is not fatal:
|
|
30
|
+
# the reason is recorded so optional_dependency! can raise it later, and a warning goes to
|
|
31
|
+
# $stderr so the missing backend is visible at load time. Kernel#warn is used rather than a
|
|
32
|
+
# Logger because logger is no longer a default gem on Ruby 4.0, and this single message does
|
|
33
|
+
# not justify a runtime dependency on it.
|
|
33
34
|
def require_optional(name)
|
|
34
35
|
require name
|
|
35
|
-
yield if block_given?
|
|
36
36
|
rescue LoadError => e
|
|
37
37
|
@unavailable_dependencies[name] = e.message
|
|
38
|
-
|
|
38
|
+
warn("Optional dependency '#{name}' is unavailable: #{e.message}")
|
|
39
|
+
else
|
|
40
|
+
# The block runs outside the rescue on purpose: a LoadError raised by the block itself is
|
|
41
|
+
# about something other than this dependency, and swallowing it here would hide the cause.
|
|
42
|
+
yield if block_given?
|
|
39
43
|
end
|
|
40
44
|
|
|
45
|
+
# Raises MissingDependencyError if the named optional backend failed to load earlier. Call it
|
|
46
|
+
# from the entry point of a function that needs the backend, so the failure surfaces where the
|
|
47
|
+
# backend is used rather than at require time.
|
|
41
48
|
def optional_dependency!(name)
|
|
42
49
|
reason = @unavailable_dependencies[name]
|
|
43
50
|
return if reason.nil?
|
data/noise.gemspec
CHANGED
|
@@ -6,7 +6,7 @@ require 'noise/version'
|
|
|
6
6
|
|
|
7
7
|
Gem::Specification.new do |spec|
|
|
8
8
|
spec.name = 'noise-ruby'
|
|
9
|
-
spec.required_ruby_version = '
|
|
9
|
+
spec.required_ruby_version = '>= 3.2'
|
|
10
10
|
spec.version = Noise::VERSION
|
|
11
11
|
spec.authors = ['Hajime Yamaguchi']
|
|
12
12
|
spec.email = ['gen.yamaguchi0@gmail.com']
|
|
@@ -22,7 +22,6 @@ Gem::Specification.new do |spec|
|
|
|
22
22
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
23
23
|
spec.require_paths = ['lib']
|
|
24
24
|
|
|
25
|
-
spec.add_development_dependency 'bundler', '~> 2.0'
|
|
26
25
|
spec.add_development_dependency 'rake', '>= 12.3.3'
|
|
27
26
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
28
27
|
|
|
@@ -31,14 +30,17 @@ Gem::Specification.new do |spec|
|
|
|
31
30
|
spec.add_development_dependency 'simplecov'
|
|
32
31
|
spec.add_development_dependency 'simplecov-json'
|
|
33
32
|
|
|
34
|
-
# Optional
|
|
35
|
-
#
|
|
36
|
-
#
|
|
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.
|
|
37
36
|
spec.add_development_dependency 'blake3'
|
|
38
|
-
spec.add_development_dependency 'ed448'
|
|
39
|
-
spec.add_development_dependency 'secp256k1-ruby'
|
|
40
37
|
|
|
41
38
|
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.
|
|
43
|
+
spec.add_runtime_dependency 'openssl', '>= 3.0'
|
|
42
44
|
spec.add_runtime_dependency 'rbnacl'
|
|
43
45
|
spec.add_runtime_dependency 'ruby-hmac'
|
|
44
46
|
end
|
metadata
CHANGED
|
@@ -1,29 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: noise-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.13.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Hajime Yamaguchi
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
|
-
- !ruby/object:Gem::Dependency
|
|
14
|
-
name: bundler
|
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
|
16
|
-
requirements:
|
|
17
|
-
- - "~>"
|
|
18
|
-
- !ruby/object:Gem::Version
|
|
19
|
-
version: '2.0'
|
|
20
|
-
type: :development
|
|
21
|
-
prerelease: false
|
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
-
requirements:
|
|
24
|
-
- - "~>"
|
|
25
|
-
- !ruby/object:Gem::Version
|
|
26
|
-
version: '2.0'
|
|
27
12
|
- !ruby/object:Gem::Dependency
|
|
28
13
|
name: rake
|
|
29
14
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -123,27 +108,13 @@ dependencies:
|
|
|
123
108
|
- !ruby/object:Gem::Version
|
|
124
109
|
version: '0'
|
|
125
110
|
- !ruby/object:Gem::Dependency
|
|
126
|
-
name:
|
|
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
|
|
111
|
+
name: ecdsa
|
|
141
112
|
requirement: !ruby/object:Gem::Requirement
|
|
142
113
|
requirements:
|
|
143
114
|
- - ">="
|
|
144
115
|
- !ruby/object:Gem::Version
|
|
145
116
|
version: '0'
|
|
146
|
-
type: :
|
|
117
|
+
type: :runtime
|
|
147
118
|
prerelease: false
|
|
148
119
|
version_requirements: !ruby/object:Gem::Requirement
|
|
149
120
|
requirements:
|
|
@@ -151,19 +122,19 @@ dependencies:
|
|
|
151
122
|
- !ruby/object:Gem::Version
|
|
152
123
|
version: '0'
|
|
153
124
|
- !ruby/object:Gem::Dependency
|
|
154
|
-
name:
|
|
125
|
+
name: openssl
|
|
155
126
|
requirement: !ruby/object:Gem::Requirement
|
|
156
127
|
requirements:
|
|
157
128
|
- - ">="
|
|
158
129
|
- !ruby/object:Gem::Version
|
|
159
|
-
version: '0'
|
|
130
|
+
version: '3.0'
|
|
160
131
|
type: :runtime
|
|
161
132
|
prerelease: false
|
|
162
133
|
version_requirements: !ruby/object:Gem::Requirement
|
|
163
134
|
requirements:
|
|
164
135
|
- - ">="
|
|
165
136
|
- !ruby/object:Gem::Version
|
|
166
|
-
version: '0'
|
|
137
|
+
version: '3.0'
|
|
167
138
|
- !ruby/object:Gem::Dependency
|
|
168
139
|
name: rbnacl
|
|
169
140
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -220,8 +191,10 @@ files:
|
|
|
220
191
|
- lib/noise/exceptions.rb
|
|
221
192
|
- lib/noise/exceptions/decrypt_error.rb
|
|
222
193
|
- lib/noise/exceptions/encrypt_error.rb
|
|
194
|
+
- lib/noise/exceptions/invalid_nonce_error.rb
|
|
223
195
|
- lib/noise/exceptions/invalid_public_key_error.rb
|
|
224
196
|
- lib/noise/exceptions/max_nonce_error.rb
|
|
197
|
+
- lib/noise/exceptions/message_too_long_error.rb
|
|
225
198
|
- lib/noise/exceptions/missing_dependency_error.rb
|
|
226
199
|
- lib/noise/exceptions/noise_handshake_error.rb
|
|
227
200
|
- lib/noise/exceptions/noise_psk_error.rb
|
|
@@ -257,23 +230,21 @@ files:
|
|
|
257
230
|
homepage: https://github.com/Yamaguchi/noise
|
|
258
231
|
licenses: []
|
|
259
232
|
metadata: {}
|
|
260
|
-
post_install_message:
|
|
261
233
|
rdoc_options: []
|
|
262
234
|
require_paths:
|
|
263
235
|
- lib
|
|
264
236
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
265
237
|
requirements:
|
|
266
|
-
- - "
|
|
238
|
+
- - ">="
|
|
267
239
|
- !ruby/object:Gem::Version
|
|
268
|
-
version: '3.
|
|
240
|
+
version: '3.2'
|
|
269
241
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
270
242
|
requirements:
|
|
271
243
|
- - ">="
|
|
272
244
|
- !ruby/object:Gem::Version
|
|
273
245
|
version: '0'
|
|
274
246
|
requirements: []
|
|
275
|
-
rubygems_version:
|
|
276
|
-
signing_key:
|
|
247
|
+
rubygems_version: 4.0.16
|
|
277
248
|
specification_version: 4
|
|
278
249
|
summary: A Ruby implementation of the Noise Protocol framework
|
|
279
250
|
test_files: []
|