noise-ruby 0.10.1 → 0.12.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.
@@ -1,11 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_force 'secp256k1'
3
+ Noise.require_optional 'secp256k1'
4
4
 
5
5
  module Noise
6
6
  module Functions
7
7
  module DH
8
8
  class Secp256k1
9
+ # Length of a compressed secp256k1 point. libsecp256k1 also accepts the 65-byte
10
+ # uncompressed form, but Noise exchanges only the compressed one.
11
+ DHLEN = 33
12
+
13
+ def initialize
14
+ Noise.optional_dependency!('secp256k1')
15
+ end
16
+
9
17
  def generate_keypair
10
18
  group = ECDSA::Group::Secp256k1
11
19
  private_key = 1 + SecureRandom.random_number(group.order - 1)
@@ -16,15 +24,24 @@ module Noise
16
24
  )
17
25
  end
18
26
 
27
+ # Computes the ECDH shared secret for the given remote public key.
28
+ #
29
+ # A point that is not on the curve makes libsecp256k1 raise Secp256k1::AssertError,
30
+ # while a public key of any other length raises ArgumentError before the point is
31
+ # even parsed. Both are translated to InvalidPublicKeyError, matching the other DH
32
+ # functions. The length is checked here rather than left to the gem so that
33
+ # ArgumentError raised for a malformed private key keeps propagating as itself.
19
34
  def dh(private_key, public_key)
35
+ raise Noise::Exceptions::InvalidPublicKeyError, public_key unless public_key.bytesize == DHLEN
36
+
20
37
  key = ::Secp256k1::PublicKey.new(pubkey: public_key, raw: true)
21
38
  key.ecdh(private_key)
22
- rescue ::Secp256k1::AssertError => _
39
+ rescue ::Secp256k1::AssertError
23
40
  raise Noise::Exceptions::InvalidPublicKeyError, public_key
24
41
  end
25
42
 
26
43
  def dhlen
27
- 33
44
+ DHLEN
28
45
  end
29
46
 
30
47
  def self.from_private(private_key)
@@ -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
- require_force 'blake3'
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 = 64
10
- BLOCKLEN = 128
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
@@ -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
- if digest.include?('SHA')
13
+ case digest
14
+ when /SHA/
14
15
  OpenSSL::HMAC.digest(OpenSSL::Digest.new(digest), key, data)
15
- elsif digest.include?('BLAKE2b')
16
+ when /BLAKE2b/
16
17
  Noise::Functions::Hash::Blake2bHMAC.new(key).update(data).digest
17
- elsif digest.include?('BLAKE2s')
18
+ when /BLAKE2s/
18
19
  Noise::Functions::Hash::Blake2sHMAC.new(key).update(data).digest
19
- elsif digest.include?('BLAKE3')
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 + "\x02", digest)
36
+ output2 = hmac_hash(temp_key, "#{output1}\u0002", digest)
34
37
  return [output1, output2] if num_outputs == 2
35
- output3 = hmac_hash(temp_key, output2 + "\x03", digest)
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
- if s.start_with?('psk')
90
- index = s.gsub(/psk/, '').to_i
91
- Modifier::Psk.new(index)
92
- elsif s == 'fallback'
93
- Modifier::Fallback.new
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
- pattern_set = name.scan(/([A-Z1]+)([^A-Z]*)/)&.first
105
- pattern = pattern_set&.first
106
- modifiers = pattern_set[1].split('+').map { |s| Modifier.parse(s) }
107
- class_name = "Noise::Pattern#{pattern}"
108
- klass = Object.const_get(class_name)
109
- klass.new(modifiers)
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
- raise Noise::Exceptions::PSKValueError if index / 2 > @tokens.size
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 %w[K X I].include?(@name[0])
151
- required << :rs if one_way? || @name[1] == 'K'
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 @name[0] == 'K'
158
- required << :s if one_way? || %w[K X].include?(@name[1])
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
@@ -6,27 +6,31 @@ module Noise
6
6
  attr_reader :name, :pattern
7
7
 
8
8
  CIPHER = {
9
- 'AESGCM': Noise::Functions::Cipher::AesGcm,
10
- 'ChaChaPoly': Noise::Functions::Cipher::ChaChaPoly
11
- }.stringify_keys.freeze
9
+ 'AESGCM' => Noise::Functions::Cipher::AesGcm,
10
+ 'ChaChaPoly' => Noise::Functions::Cipher::ChaChaPoly
11
+ }.freeze
12
12
 
13
13
  DH = {
14
- '25519': Noise::Functions::DH::ED25519,
15
- '448': Noise::Functions::DH::ED448,
16
- 'secp256k1': Noise::Functions::DH::Secp256k1
17
- }.stringify_keys.freeze
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': 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
- }.stringify_keys.freeze
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
- prefix, pattern_name, dh_name, cipher_name, hash_name = name.split('_')
29
- raise Noise::Exceptions::ProtocolNameError if prefix != 'Noise'
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 unless @cipher_fn && @hash_fn && @dh_fn
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: AesGcm.new)
17
+ def initialize(cipher:)
17
18
  @cipher = cipher
18
19
  end
19
20
 
@@ -28,7 +29,17 @@ module Noise
28
29
  !@k.nil?
29
30
  end
30
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.
31
40
  def nonce=(nonce)
41
+ raise Noise::Exceptions::InvalidNonceError unless nonce.is_a?(Integer) && nonce.between?(0, MAX_NONCE)
42
+
32
43
  @n = nonce
33
44
  end
34
45
 
@@ -36,6 +47,7 @@ module Noise
36
47
  def encrypt_with_ad(ad, plaintext)
37
48
  return plaintext unless key?
38
49
  raise Noise::Exceptions::MaxNonceError if @n == MAX_NONCE
50
+
39
51
  ciphertext = @cipher.encrypt(@k, @n, ad, plaintext)
40
52
  @n += 1
41
53
  ciphertext
@@ -45,11 +57,18 @@ module Noise
45
57
  def decrypt_with_ad(ad, ciphertext)
46
58
  return ciphertext unless key?
47
59
  raise Noise::Exceptions::MaxNonceError if @n == MAX_NONCE
60
+ # Without this the ciphers slice a nil authentication tag out of the truncated input.
61
+ raise Noise::Exceptions::DecryptError, 'Ciphertext is shorter than the tag.' if
62
+ ciphertext.bytesize < TAG_LENGTH
63
+
48
64
  plaintext = @cipher.decrypt(@k, @n, ad, ciphertext)
49
65
  @n += 1
50
66
  plaintext
51
67
  end
52
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.
53
72
  def rekey
54
73
  @k = @cipher.rekey(@k)
55
74
  end
@@ -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
- @message_patterns = @protocol.pattern.tokens.dup
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('@' + token.to_s).public_key }
52
+ ->(token) { instance_variable_get("@#{token}").public_key }
53
53
  end
54
54
 
55
55
  def remote_keypair_getter
56
- ->(token) { instance_variable_get('@r' + token.to_s) }
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
- @symmetric_state.split if @message_patterns.empty?
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
- @symmetric_state.split if @message_patterns.empty?
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)..-1]
168
+ message = message[(len + offset)..]
158
169
  key = @symmetric_state.decrypt_and_hash(key) if is_encrypted
159
170
  [message, key]
160
171
  end
@@ -8,8 +8,7 @@ module Noise
8
8
  # - h: A hash output of HASHLEN bytes.
9
9
  #
10
10
  class SymmetricState
11
- attr_reader :h, :ck
12
- attr_reader :cipher_state
11
+ attr_reader :h, :ck, :cipher_state
13
12
 
14
13
  def initialize(protocol, connection)
15
14
  @protocol = protocol
@@ -1,11 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class String
4
- def htb
5
- [self].pack('H*')
6
- end
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
- def bth
9
- unpack('H*').first
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Noise
4
- VERSION = '0.10.1'
4
+ VERSION = '0.12.0'
5
5
  end
data/lib/noise.rb CHANGED
@@ -4,11 +4,11 @@ require 'noise/version'
4
4
 
5
5
  require 'ecdsa'
6
6
  require 'logger'
7
+ require 'openssl'
7
8
  require 'rbnacl'
8
9
  require 'ruby_hmac'
9
10
  require 'securerandom'
10
11
 
11
- require 'noise/utils/hash'
12
12
  require 'noise/utils/string'
13
13
 
14
14
  module Noise
@@ -21,14 +21,29 @@ module Noise
21
21
  autoload :Functions, 'noise/functions'
22
22
  autoload :State, 'noise/state'
23
23
 
24
- def self.logger
25
- @logger ||= Logger.new(STDOUT)
26
- end
27
- end
24
+ # Some DH and hash functions are backed by a gem, and often a system library, that is only needed
25
+ # when the function appears in a protocol name. Loading one is therefore allowed to fail; the
26
+ # failure is remembered and reported by optional_dependency! when the function is used.
27
+ @unavailable_dependencies = {}
28
+
29
+ class << self
30
+ def logger
31
+ @logger ||= Logger.new($stdout)
32
+ end
28
33
 
29
- def require_force(name)
30
- require name
31
- yield if block_given?
32
- rescue LoadError => e
33
- Noise.logger.warn(e.message)
34
+ def require_optional(name)
35
+ require name
36
+ yield if block_given?
37
+ rescue LoadError => e
38
+ @unavailable_dependencies[name] = e.message
39
+ logger.warn("Optional dependency '#{name}' is unavailable: #{e.message}")
40
+ end
41
+
42
+ def optional_dependency!(name)
43
+ reason = @unavailable_dependencies[name]
44
+ return if reason.nil?
45
+
46
+ raise Noise::Exceptions::MissingDependencyError, "'#{name}' could not be loaded: #{reason}"
47
+ end
48
+ end
34
49
  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 = 'noise-ruby'
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,26 @@ 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'
28
38
  spec.add_development_dependency 'secp256k1-ruby'
29
39
 
30
40
  spec.add_runtime_dependency 'ecdsa'
31
- spec.add_runtime_dependency 'ed448'
41
+ # The 448 DH function needs the raw key API (OpenSSL::PKey.new_raw_private_key and friends),
42
+ # which arrived in openssl 3.0. Ruby 3.0 still ships 2.2 as its default gem, so the version has
43
+ # to be requested explicitly rather than left to whatever the interpreter bundles.
44
+ spec.add_runtime_dependency 'openssl', '>= 3.0'
32
45
  spec.add_runtime_dependency 'rbnacl'
33
46
  spec.add_runtime_dependency 'ruby-hmac'
34
47
  end