noise-ruby 0.7.1 → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 52f9bedd93319f9cf54acb51c1a83c64fcfd25cfdaef69204f709b59c83060c4
4
- data.tar.gz: c72fb7afa8aab1c577b305f1d57316ab34ad183b6bbf249d420b3b17d4bb81e6
3
+ metadata.gz: 9fcf6ecf59b631aacb32b66661719e57a205ccea71a487748d3bb2979fc2ee4b
4
+ data.tar.gz: 22bee13bda1fe1a9339777261eccd6d173e32679e969e78b065abe457d06884e
5
5
  SHA512:
6
- metadata.gz: be31e461a923db0af2f6ae19b42fa74ac3b70fcac8e332a281a46377ac0fa0164ce799567ba19e862fbbc40b367b06672bd095ea850bcd4bfaec2291b1b4ab16
7
- data.tar.gz: e3495a893d23263a534d7f490fdcd84a5f33c81b4e5d8437d1a268abc8111940916ecf6f832f2b13bf610d5ec9f1059129c16a9f2a0728a823b96a147482e7ac
6
+ metadata.gz: 2cbd93529da1345b646b9be626591bf81eb1fe53d211d70a75fe6fe1f56d99c7b55bdedc7102aed3d20c27080fcf4f04b0619b7bacd8f664f8301397f3f4e069
7
+ data.tar.gz: '0845488f8a48edb3f09fba247a8f7950aefd0c5a6cade1ae114e3b3e683e4f66dbc3d34267b2ee298f066d170ddd84343da3d843a5a89587ce592cac906da849'
data/README.md CHANGED
@@ -49,7 +49,53 @@ gem 'secp256k1-ruby'
49
49
 
50
50
  ## Usage
51
51
 
52
- TODO: Write usage instructions here
52
+ Followings shows handshake protocol with "Noise_NN_25519_ChaChaPoly_BLAKE2b"
53
+
54
+ ### Handshake
55
+
56
+ #### initiator
57
+
58
+ ```
59
+ initiator = Noise::Connection::Initiator.new("Noise_NN_25519_ChaChaPoly_BLAKE2b")
60
+ initiator.prologue = "test" # => "test"
61
+ initiator.start_handshake # => true
62
+ cipher = initiator.write_message("") # => "\xB6\xF7gmxi\xAB\xBCY|t\xF0\x9D\x01A\ad\x92\xBBvp\x80ZNU\f=\x83\x81^\xFD\x15"
63
+ ```
64
+
65
+ then initiator sends `cipher` to responder.
66
+
67
+ #### responder
68
+
69
+ Responder receive `cipher` from initiator.
70
+ Responder respond messages to initiator.
71
+
72
+ ```
73
+ responder = Noise::Connection::Responder.new("Noise_NN_25519_ChaChaPoly_BLAKE2b")
74
+ responder.prologue = "test" # => "test"
75
+ responder.start_handshake # => true
76
+ plain = responder.read_message(cipher) # => ""
77
+ cipher = responder.write_message("") # => "\v\xD9\x97'\xC0\xB1\xC9\xFFD\x8C\x7F\x18L\xB0\xF2\x14\xB0\x11\xC0\x90\xAAZ\xE1\x03\x17z)\xB81/5L\x16\xE3\xD1\xBE<{\xB8\xBB\xD6\xF1\x00\x10]\x99=\xD7"
78
+ ```
79
+
80
+
81
+ ### initiator
82
+
83
+ ```
84
+ plain = initiator.read_message(cipher) # => ""
85
+ ```
86
+
87
+ ### Send transport message (after handshake finished)
88
+
89
+ ```
90
+ cipher = initiator.encrypt("Hello, World!") # => "\xDA\xC7\xD7as\v\xFA\xCC,\xB3\xC7\xD0/xL\xE8I,\xD9\n\xEExh\x8F\xFA\xD6\x01\x99W"
91
+ ```
92
+
93
+ ### Receive transport message
94
+
95
+ ```
96
+ plain = responder.decrypt(cipher) # => "Hello, World!"
97
+ ```
98
+
53
99
 
54
100
  ## Development
55
101
 
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ # frozen_string_literal: true
4
+
3
5
  require 'bundler/setup'
4
6
  require 'noise'
5
7
 
@@ -12,6 +12,7 @@ require 'noise/utils/string'
12
12
 
13
13
  module Noise
14
14
  autoload :Connection, 'noise/connection'
15
+ autoload :Key, 'noise/key'
15
16
  autoload :KeyPair, 'noise/key_pair'
16
17
  autoload :Protocol, 'noise/protocol'
17
18
  autoload :Pattern, 'noise/pattern'
@@ -1,94 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Noise
4
- class Connection
5
- module Status
6
- STATIC = 1
7
- REMOTE_STATIC = 2
8
- EPHEMERAL = 3
9
- REMOTE_EPHEMERAL = 4
10
- end
11
-
12
- attr_accessor :protocol, :handshake_started, :handshake_finished, :fn
13
-
14
- def initialize(name)
15
- @protocol = Protocol.create(name)
16
- @handshake_started = false
17
- @handshake_finished = false
18
- @fn = nil
19
- @write_message_proc = ->(payload) { write_message(payload) }
20
- @read_message_proc = ->(payload) { read_message(payload) }
21
- end
22
-
23
- def psks=(psks)
24
- @protocol.psks = psks
25
- end
26
-
27
- def prologue=(prologue)
28
- @protocol.prologue = prologue
29
- end
30
-
31
- def set_as_initiator!
32
- @protocol.initiator = true
33
- @fn = @write_message_proc
34
- end
35
-
36
- def set_as_responder!
37
- @protocol.initiator = false
38
- @fn = @read_message_proc
39
- end
40
-
41
- def set_keypair_from_private(keypair, private_key)
42
- @protocol.keypairs[keypair.to_sym] = @protocol.dh_fn.class.from_private(private_key)
43
- end
44
-
45
- def set_keypair_from_public(keypair, public_key)
46
- @protocol.keypairs[keypair.to_sym] = @protocol.dh_fn.class.from_public(public_key)
47
- end
48
-
49
- def start_handshake
50
- @protocol.validate
51
- @protocol.initialise_handshake_state
52
- @handshake_started = true
53
- end
54
-
55
- def write_message(payload = '')
56
- # Call NoiseConnection.start_handshake first
57
- raise Noise::Exceptions::NoiseHandshakeError unless @handshake_started
58
- raise Noise::Exceptions::NoiseHandshakeError if @fn != @write_message_proc
59
- # Handshake finished. NoiseConnection.encrypt should be used now
60
- raise Noise::Exceptions::NoiseHandshakeError if @handshake_finished
61
- @fn = @read_message_proc
62
- buffer = +''
63
- result = @protocol.handshake_state.write_message(payload, buffer)
64
- @handshake_finished = true if result
65
- buffer
66
- end
67
-
68
- def read_message(data)
69
- # Call NoiseConnection.start_handshake first
70
- raise Noise::Exceptions::NoiseHandshakeError unless @handshake_started
71
- raise Noise::Exceptions::NoiseHandshakeError if @fn != @read_message_proc
72
- # Handshake finished. NoiseConnection.encrypt should be used now
73
- raise Noise::Exceptions::NoiseHandshakeError if @handshake_finished
74
-
75
- @fn = @write_message_proc
76
- buffer = +''
77
- result = @protocol.handshake_state.read_message(data, buffer)
78
- @handshake_finished = true if result
79
- buffer
80
- end
81
-
82
- def encrypt(data)
83
- raise Noise::Exceptions::NoiseHandshakeError unless @handshake_finished
84
- # raise Noise::Exceptions::NoiseInvalidMessage
85
- @protocol.cipher_state_encrypt.encrypt_with_ad('', data)
86
- end
87
-
88
- def decrypt(data)
89
- raise Noise::Exceptions::NoiseHandshakeError unless @handshake_finished
90
- # raise Noise::Exceptions::NoiseInvalidMessage
91
- @protocol.cipher_state_decrypt.decrypt_with_ad('', data)
92
- end
4
+ module Connection
5
+ autoload :Base, 'noise/connection/base'
6
+ autoload :Initiator, 'noise/connection/initiator'
7
+ autoload :Responder, 'noise/connection/responder'
93
8
  end
94
9
  end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Noise
4
+ module Connection
5
+ class Base
6
+ attr_reader :protocol, :handshake_started, :handshake_finished, :handshake_hash
7
+ attr_reader :cipher_state_encrypt, :cipher_state_decrypt, :cipher_state_handshake
8
+ attr_accessor :psks, :prologue
9
+
10
+ def initialize(name, keypairs: { s: nil, e: nil, rs: nil, re: nil })
11
+ @protocol = Protocol.create(name)
12
+
13
+ @keypairs = keypairs
14
+ # parameter keypairs[:e] and keypairs[:s] are strings, so should convert Noise::Key object.
15
+ @keypairs[:e] = @protocol.dh_fn.class.from_private(@keypairs[:e]) if @keypairs[:e]
16
+ @keypairs[:s] = @protocol.dh_fn.class.from_private(@keypairs[:s]) if @keypairs[:s]
17
+
18
+ @handshake_started = false
19
+ @handshake_finished = false
20
+ @next_message = nil
21
+ end
22
+
23
+ def start_handshake
24
+ validate
25
+ initialise_handshake_state
26
+ @handshake_started = true
27
+ end
28
+
29
+ def initialise_handshake_state
30
+ @handshake_state = Noise::State::HandshakeState.new(
31
+ self,
32
+ protocol,
33
+ initiator?,
34
+ @prologue,
35
+ @keypairs
36
+ )
37
+ @symmetric_state = @handshake_state.symmetric_state
38
+ @cipher_state_handshake = @symmetric_state.cipher_state
39
+ end
40
+
41
+ def write_message(payload = '')
42
+ # Call NoiseConnection.start_handshake first
43
+ raise Noise::Exceptions::NoiseHandshakeError unless @handshake_started
44
+ raise Noise::Exceptions::NoiseHandshakeError if @next_message != :write
45
+ # Handshake finished. NoiseConnection.encrypt should be used now
46
+ raise Noise::Exceptions::NoiseHandshakeError if @handshake_finished
47
+ @next_message = :read
48
+ buffer = +''
49
+ result = @handshake_state.write_message(payload, buffer)
50
+ @handshake_finished = true if result
51
+ buffer
52
+ end
53
+
54
+ def read_message(data)
55
+ # Call NoiseConnection.start_handshake first
56
+ raise Noise::Exceptions::NoiseHandshakeError unless @handshake_started
57
+ raise Noise::Exceptions::NoiseHandshakeError if @next_message != :read
58
+ # Handshake finished. NoiseConnection.encrypt should be used now
59
+ raise Noise::Exceptions::NoiseHandshakeError if @handshake_finished
60
+
61
+ @next_message = :write
62
+ buffer = +''
63
+ result = @handshake_state.read_message(data, buffer)
64
+ @handshake_finished = true if result
65
+ buffer
66
+ end
67
+
68
+ def encrypt(data)
69
+ raise Noise::Exceptions::NoiseHandshakeError unless @handshake_finished
70
+ # raise Noise::Exceptions::NoiseInvalidMessage
71
+ @cipher_state_encrypt.encrypt_with_ad('', data)
72
+ end
73
+
74
+ def decrypt(data)
75
+ raise Noise::Exceptions::NoiseHandshakeError unless @handshake_finished
76
+ # raise Noise::Exceptions::NoiseInvalidMessage
77
+ @cipher_state_decrypt.decrypt_with_ad('', data)
78
+ end
79
+
80
+ def validate_psk!
81
+ # Invalid psk length! Has to be 32 bytes long
82
+ raise Noise::Exceptions::NoisePSKError if @psks.any? { |psk| psk.bytesize != 32 }
83
+ # Bad number of PSKs provided to this protocol! {} are required,
84
+ # given {}'.format(self.pattern.psk_count, len(self.psks)))
85
+ raise Noise::Exceptions::NoisePSKError if @protocol.pattern.psk_count != @psks.count
86
+ end
87
+
88
+ def valid_keypairs?
89
+ @protocol.pattern.required_keypairs(initiator?).any? { |keypair| !@keypairs[keypair] }
90
+ end
91
+
92
+ def validate
93
+ validate_psk! if psk_handshake?
94
+
95
+ # 'Keypair {} has to be set for chosen handshake pattern'.format(keypair)
96
+ raise Noise::Exceptions::NoiseValidationError if valid_keypairs?
97
+ true
98
+ end
99
+
100
+ def psk_handshake?
101
+ @protocol.is_psk_handshake
102
+ end
103
+
104
+ def handshake_done(_c1, _c2)
105
+ @handshake_hash = @symmetric_state.handshake_hash
106
+ @handshake_state = nil
107
+ @symmetric_state = nil
108
+ @cipher_state_handshake = nil
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Noise
4
+ module Connection
5
+ class Initiator < Base
6
+ def initialize(name, keypairs: { s: nil, e: nil, rs: nil, re: nil })
7
+ super
8
+ @next_message = :write
9
+ end
10
+
11
+ def initiator?
12
+ true
13
+ end
14
+
15
+ def handshake_done(c1, c2)
16
+ super
17
+ @cipher_state_encrypt = c1
18
+ @cipher_state_decrypt = @protocol.pattern.one_way ? nil : c2
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Noise
4
+ module Connection
5
+ class Responder < Base
6
+ def initialize(name, keypairs: { s: nil, e: nil, rs: nil, re: nil })
7
+ super
8
+ @next_message = :read
9
+ end
10
+
11
+ def initiator?
12
+ false
13
+ end
14
+
15
+ def handshake_done(c1, c2)
16
+ super
17
+ @cipher_state_decrypt = c1
18
+ @cipher_state_encrypt = @protocol.pattern.one_way ? nil : c2
19
+ end
20
+ end
21
+ end
22
+ end
@@ -8,14 +8,14 @@ module Noise
8
8
  @cipher = RbNaCl::AEAD::ChaCha20Poly1305IETF.new(String.new(k).force_encoding('ASCII-8BIT'))
9
9
  @cipher.encrypt(nonce_to_bytes(n), plaintext, ad)
10
10
  rescue ::RbNaCl::CryptoError => e
11
- raise Noise::Exceptions::EncryptError.new(e)
11
+ raise Noise::Exceptions::EncryptError, e
12
12
  end
13
13
 
14
14
  def decrypt(k, n, ad, ciphertext)
15
15
  @cipher = RbNaCl::AEAD::ChaCha20Poly1305IETF.new(String.new(k).force_encoding('ASCII-8BIT'))
16
16
  @cipher.decrypt(nonce_to_bytes(n), ciphertext, ad)
17
17
  rescue ::RbNaCl::CryptoError => e
18
- raise Noise::Exceptions::DecryptError.new(e)
18
+ raise Noise::Exceptions::DecryptError, e
19
19
  end
20
20
 
21
21
  def nonce_to_bytes(n)
@@ -9,7 +9,7 @@ module Noise
9
9
  private_key = 1 + SecureRandom.random_number(RbNaCl::GroupElement::STANDARD_GROUP_ORDER - 1)
10
10
  scalar_as_string = ECDSA::Format::IntegerOctetString.encode(private_key, 32)
11
11
  public_key = RbNaCl::GroupElements::Curve25519.base.mult(scalar_as_string)
12
- [ECDSA::Format::IntegerOctetString.encode(private_key, 32), public_key.to_bytes]
12
+ Noise::Key.new(ECDSA::Format::IntegerOctetString.encode(private_key, 32), public_key.to_bytes)
13
13
  end
14
14
 
15
15
  def dh(private_key, public_key)
@@ -22,11 +22,7 @@ module Noise
22
22
 
23
23
  def self.from_private(private_key)
24
24
  public_key = RbNaCl::GroupElements::Curve25519.base.mult(private_key)
25
- [private_key, public_key.to_bytes]
26
- end
27
-
28
- def self.from_public(public_key)
29
- [nil, public_key]
25
+ Noise::Key.new(private_key, public_key.to_bytes)
30
26
  end
31
27
  end
32
28
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Noise
2
4
  module Functions
3
5
  module DH
@@ -7,18 +9,13 @@ module Noise
7
9
  throw NotImplementedError
8
10
  end
9
11
 
10
- def dh(key_pair, public_key)
12
+ def dh(_key_pair, _public_key)
11
13
  throw NotImplementedError
12
14
  end
13
15
 
14
16
  def dhlen
15
17
  DHLEN
16
18
  end
17
-
18
- def self.from_private(private_key)
19
- end
20
- def self.from_public(private_key)
21
- end
22
19
  end
23
20
  end
24
21
  end
@@ -5,7 +5,6 @@ begin
5
5
  rescue LoadError
6
6
  end
7
7
 
8
-
9
8
  module Noise
10
9
  module Functions
11
10
  module DH
@@ -14,17 +13,17 @@ module Noise
14
13
  group = ECDSA::Group::Secp256k1
15
14
  private_key = 1 + SecureRandom.random_number(group.order - 1)
16
15
  public_key = group.generator.multiply_by_scalar(private_key)
17
- [
16
+ Noise::Key.new(
18
17
  ECDSA::Format::IntegerOctetString.encode(private_key, 32),
19
18
  ECDSA::Format::PointOctetString.encode(public_key, compression: true)
20
- ]
19
+ )
21
20
  end
22
21
 
23
22
  def dh(private_key, public_key)
24
23
  key = ::Secp256k1::PublicKey.new(pubkey: public_key, raw: true)
25
24
  key.ecdh(private_key)
26
25
  rescue ::Secp256k1::AssertError => _
27
- raise Noise::Exceptions::InvalidPublicKeyError.new(public_key)
26
+ raise Noise::Exceptions::InvalidPublicKeyError, public_key
28
27
  end
29
28
 
30
29
  def dhlen
@@ -35,11 +34,7 @@ module Noise
35
34
  group = ECDSA::Group::Secp256k1
36
35
  scalar = ECDSA::Format::IntegerOctetString.decode(private_key)
37
36
  point = group.generator.multiply_by_scalar(scalar)
38
- [private_key, ECDSA::Format::PointOctetString.encode(point, compression: true)]
39
- end
40
-
41
- def self.from_public(public_key)
42
- [nil, public_key]
37
+ Noise::Key.new(private_key, ECDSA::Format::PointOctetString.encode(point, compression: true))
43
38
  end
44
39
  end
45
40
  end
@@ -27,46 +27,46 @@ module Noise
27
27
  end
28
28
 
29
29
  class Blake2sDigester
30
- IV = [0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A,0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19]
30
+ IV = [0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19].freeze
31
31
  SIGMA = [
32
- [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 ],
33
- [ 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 ],
34
- [ 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 ],
35
- [ 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 ],
36
- [ 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 ],
37
- [ 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 ],
38
- [ 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 ],
39
- [ 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 ],
40
- [ 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 ],
41
- [ 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0 ]
42
- ]
32
+ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
33
+ [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3],
34
+ [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4],
35
+ [7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8],
36
+ [9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13],
37
+ [2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9],
38
+ [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11],
39
+ [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10],
40
+ [6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5],
41
+ [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0]
42
+ ].freeze
43
43
 
44
44
  def initialize(key: '')
45
45
  @key = key
46
- @ctx = init(Blake2s::HASHLEN, @key.unpack("C*"))
46
+ @ctx = init(Blake2s::HASHLEN, @key.unpack('C*'))
47
47
  end
48
48
 
49
49
  def update(data)
50
- update_internal(@ctx, data.unpack("C*"))
50
+ update_internal(@ctx, data.unpack('C*'))
51
51
  self
52
52
  end
53
53
 
54
54
  def digest
55
55
  out = []
56
56
  final(@ctx, out)
57
- out.pack("C*")
57
+ out.pack('C*')
58
58
  end
59
59
 
60
60
  # @return context
61
61
  def init(out_len, key)
62
- raise ArgumentError if out_len == 0 || out_len > 32
62
+ raise ArgumentError if out_len.zero? || out_len > 32
63
63
  h = IV.dup
64
64
  h[0] ^= 0x01010000 ^ (key.size << 8) ^ out_len
65
65
  t = 0
66
66
  c = 0
67
67
  b = Array.new(Blake2s::BLOCKLEN).fill(0, key.size)
68
68
  ctx = Context.new(b, h, t, c, out_len)
69
- if key.size > 0
69
+ if key.size.positive?
70
70
  update_internal(ctx, key)
71
71
  ctx.c = 64
72
72
  end
@@ -101,12 +101,12 @@ module Noise
101
101
  private
102
102
 
103
103
  def to_int32(x)
104
- x = x & 0xFFFFFFFF
104
+ x &= 0xFFFFFFFF
105
105
  x < 0x80000000 ? x : x - 2**32
106
106
  end
107
107
 
108
- def rshift(x, y, range=32)
109
- (x + (x > 0 ? 0 : 2 ** range)) >> y
108
+ def rshift(x, y, range = 32)
109
+ (x + (x.positive? ? 0 : 2**range)) >> y
110
110
  end
111
111
 
112
112
  def rotr32(x, y)
@@ -151,11 +151,11 @@ module Noise
151
151
  end
152
152
 
153
153
  10.times do |i|
154
- mix_g(v, 0, 4, 8, 12, m[SIGMA[i][ 0]], m[SIGMA[i][ 1]])
155
- mix_g(v, 1, 5, 9, 13, m[SIGMA[i][ 2]], m[SIGMA[i][ 3]])
156
- mix_g(v, 2, 6, 10, 14, m[SIGMA[i][ 4]], m[SIGMA[i][ 5]])
157
- mix_g(v, 3, 7, 11, 15, m[SIGMA[i][ 6]], m[SIGMA[i][ 7]])
158
- mix_g(v, 0, 5, 10, 15, m[SIGMA[i][ 8]], m[SIGMA[i][ 9]])
154
+ mix_g(v, 0, 4, 8, 12, m[SIGMA[i][0]], m[SIGMA[i][1]])
155
+ mix_g(v, 1, 5, 9, 13, m[SIGMA[i][2]], m[SIGMA[i][3]])
156
+ mix_g(v, 2, 6, 10, 14, m[SIGMA[i][4]], m[SIGMA[i][5]])
157
+ mix_g(v, 3, 7, 11, 15, m[SIGMA[i][6]], m[SIGMA[i][7]])
158
+ mix_g(v, 0, 5, 10, 15, m[SIGMA[i][8]], m[SIGMA[i][9]])
159
159
  mix_g(v, 1, 6, 11, 12, m[SIGMA[i][10]], m[SIGMA[i][11]])
160
160
  mix_g(v, 2, 7, 8, 13, m[SIGMA[i][12]], m[SIGMA[i][13]])
161
161
  mix_g(v, 3, 4, 9, 14, m[SIGMA[i][14]], m[SIGMA[i][15]])
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Noise
4
+ class Key
5
+ attr_reader :private_key, :public_key
6
+
7
+ def initialize(private_key, public_key)
8
+ @private_key = private_key
9
+ @public_key = public_key
10
+ end
11
+ end
12
+ end
@@ -2,14 +2,9 @@
2
2
 
3
3
  module Noise
4
4
  class Protocol
5
- attr_accessor :prologue, :initiator
6
- attr_accessor :cipher_state_encrypt, :cipher_state_decrypt
7
- attr_accessor :cipher_state_handshake
8
- attr_accessor :psks
9
- attr_reader :name, :cipher_fn, :hash_fn, :dh_fn, :hkdf_fn, :pattern
10
- attr_reader :handshake_state, :keypairs, :keypair_fn
11
- attr_reader :handshake_hash
12
- attr_accessor :ck
5
+ attr_accessor :is_psk_handshake
6
+ attr_accessor :cipher_fn, :hash_fn, :dh_fn, :hkdf_fn
7
+ attr_reader :name, :pattern
13
8
 
14
9
  CIPHER = {
15
10
  'AESGCM': Noise::Functions::Cipher::AesGcm,
@@ -38,9 +33,7 @@ module Noise
38
33
  def initialize(name, pattern_name, cipher_name, hash_name, dh_name)
39
34
  @name = name
40
35
  @pattern = Noise::Pattern.create(pattern_name)
41
- @keypairs = { s: [], e: [], rs: [], re: [] }
42
36
  @hkdf_fn = Noise::Functions::Hash.create_hkdf_fn(hash_name)
43
- @psks = []
44
37
  @is_psk_handshake = @pattern.modifiers.any? { |m| m.start_with?('psk') }
45
38
 
46
39
  @pattern.apply_pattern_modifiers
@@ -55,68 +48,6 @@ module Noise
55
48
  raise Noise::Exceptions::ProtocolNameError unless @cipher_fn && @hash_fn && @dh_fn
56
49
  end
57
50
 
58
- def handshake_done
59
- if @pattern.one_way
60
- if @initiator
61
- @cipher_state_decrypt = nil
62
- else
63
- @cipher_state_encrypt = nil
64
- end
65
- end
66
- @handshake_hash = @symmetric_state.handshake_hash
67
- @keypairs = @handshake_state.keypairs
68
-
69
- @handshake_state = nil
70
- @ck = @symmetric_state.ck
71
- @symmetric_state = nil
72
- @cipher_state_handshake = nil
73
- @prologue = nil
74
- @initiator = nil
75
- @dh_fn = nil
76
- @hash_fn = nil
77
- @keypair_fn = nil
78
- end
79
-
80
- def validate_psk!
81
- # Invalid psk length! Has to be 32 bytes long
82
- raise Noise::Exceptions::NoisePSKError if @psks.any? { |psk| psk.bytesize != 32 }
83
- # Bad number of PSKs provided to this protocol! {} are required,
84
- # given {}'.format(self.pattern.psk_count, len(self.psks)))
85
- raise Noise::Exceptions::NoisePSKError if @pattern.psk_count != @psks.count
86
- end
87
-
88
- def valid_keypairs?
89
- @pattern.required_keypairs(@initiator).any? { |keypair| !@keypairs[keypair] }
90
- end
91
-
92
- def validate
93
- validate_psk! if psk_handshake?
94
-
95
- # You need to set role with NoiseConnection.set_as_initiator
96
- # or NoiseConnection.set_as_responder
97
- raise Noise::Exceptions::NoiseValidationError if @initiator.nil?
98
-
99
- # 'Keypair {} has to be set for chosen handshake pattern'.format(keypair)
100
- raise Noise::Exceptions::NoiseValidationError if valid_keypairs?
101
-
102
- if @keypairs[:e] || @keypairs[:re]
103
- # warnings
104
- # One of ephemeral keypairs is already set.
105
- # This is OK for testing, but should NEVER happen in production!
106
- end
107
- true
108
- end
109
-
110
- def initialise_handshake_state
111
- @handshake_state = Noise::State::HandshakeState.new(
112
- self,
113
- @initiator,
114
- @prologue,
115
- @keypairs
116
- )
117
- @symmetric_state = @handshake_state.symmetric_state
118
- end
119
-
120
51
  def psk_handshake?
121
52
  @is_psk_handshake
122
53
  end
@@ -17,11 +17,13 @@ module Noise
17
17
  @cipher = cipher
18
18
  end
19
19
 
20
+ # @param [String] 32 bytes key
20
21
  def initialize_key(key)
21
22
  @k = key
22
23
  @n = 0
23
24
  end
24
25
 
26
+ # @return [Boolean] true if k is non-empty, false otherwise.
25
27
  def key?
26
28
  !@k.nil?
27
29
  end
@@ -30,6 +32,7 @@ module Noise
30
32
  @n = nonce
31
33
  end
32
34
 
35
+ # @return [String] ENCRYPT(k, n++, ad, plaintext) if k is non-empty, otherwise returns plaintext.
33
36
  def encrypt_with_ad(ad, plaintext)
34
37
  return plaintext unless key?
35
38
  raise Noise::Exceptions::MaxNonceError if @n == MAX_NONCE
@@ -38,6 +41,7 @@ module Noise
38
41
  ciphertext
39
42
  end
40
43
 
44
+ # @return DECRYPT(k, n++, ad, ciphertext) if k is non-empty, otherwise returns ciphertext.
41
45
  def decrypt_with_ad(ad, ciphertext)
42
46
  return ciphertext unless key?
43
47
  raise Noise::Exceptions::MaxNonceError if @n == MAX_NONCE
@@ -10,15 +10,20 @@ module Noise
10
10
  # rs: The remote party's static public key
11
11
  # re: The remote party's ephemeral public key
12
12
  #
13
+ # A HandshakeState also has variables to track its role, and the remaining portion of the handshake pattern:
14
+ #
15
+ # initiator: A boolean indicating the initiator or responder role.
16
+ #
17
+ # message_patterns: A sequence of message patterns.
18
+ # Each message pattern is a sequence of tokens from the set ("e", "s", "ee", "es", "se", "ss").
13
19
  class HandshakeState
14
-
15
20
  attr_reader :message_patterns, :symmetric_state
16
21
 
17
- def initialize(protocol, initiator, prologue, keypairs)
18
- # @protocol = handshake_pattern.to_protocol
22
+ def initialize(connection, protocol, initiator, prologue, keypairs)
23
+ @connection = connection
19
24
  @protocol = protocol
20
25
  @symmetric_state = SymmetricState.new
21
- @symmetric_state.initialize_symmetric(@protocol)
26
+ @symmetric_state.initialize_symmetric(@protocol, connection)
22
27
  @symmetric_state.mix_hash(prologue)
23
28
  @initiator = initiator
24
29
  @s = keypairs[:s]
@@ -29,7 +34,7 @@ module Noise
29
34
  # TODO : Calls MixHash() once for each public key listed in the pre-messages from handshake_pattern, with the
30
35
  # specified public key as input (see Section 7 for an explanation of pre-messages). If both initiator and
31
36
  # responder have pre-messages, the initiator's public keys are hashed first.
32
- get_local_keypair = ->(token) { instance_variable_get('@' + token) }
37
+ get_local_keypair = ->(token) { instance_variable_get('@' + token).public_key }
33
38
  get_remote_keypair = ->(token) { instance_variable_get('@r' + token) }
34
39
 
35
40
  if initiator
@@ -42,17 +47,18 @@ module Noise
42
47
 
43
48
  @protocol.pattern.initiator_pre_messages&.map do |message|
44
49
  keypair = initiator_keypair_getter.call(message)
45
- @symmetric_state.mix_hash(keypair[1])
50
+ @symmetric_state.mix_hash(keypair)
46
51
  end
47
52
 
48
53
  @protocol.pattern.responder_pre_messages&.map do |message|
49
54
  keypair = responder_keypair_getter.call(message)
50
- @symmetric_state.mix_hash(keypair[1])
55
+ @symmetric_state.mix_hash(keypair)
51
56
  end
52
57
  # Sets message_patterns to the message patterns from handshake_pattern
53
58
  @message_patterns = @protocol.pattern.tokens.dup
54
59
  end
55
60
 
61
+ # Takes a payload byte sequence which may be zero-length, and a message_buffer to write the output into
56
62
  def write_message(payload, message_buffer)
57
63
  pattern = @message_patterns.shift
58
64
  dh_fn = @protocol.dh_fn
@@ -60,37 +66,32 @@ module Noise
60
66
  pattern.each do |token|
61
67
  case token
62
68
  when 'e'
63
- @e = dh_fn.generate_keypair if @e.empty?
64
- message_buffer << @e[1]
65
- @symmetric_state.mix_hash(@e[1])
66
- @symmetric_state.mix_key(@e[1]) if @protocol.psk_handshake?
67
- next
69
+ @e = dh_fn.generate_keypair if @e.nil?
70
+ message_buffer << @e.public_key
71
+ @symmetric_state.mix_hash(@e.public_key)
72
+ @symmetric_state.mix_key(@e.public_key) if @protocol.psk_handshake?
68
73
  when 's'
69
- message_buffer << @symmetric_state.encrypt_and_hash(@s[1])
70
- next
74
+ message_buffer << @symmetric_state.encrypt_and_hash(@s.public_key)
71
75
  when 'ee'
72
- @symmetric_state.mix_key(dh_fn.dh(@e[0], @re[1]))
73
- next
76
+ @symmetric_state.mix_key(dh_fn.dh(@e.private_key, @re))
74
77
  when 'es'
75
- private_key, public_key = @initiator ? [@e[0], @rs[1]] : [@s[0], @re[1]]
78
+ private_key, public_key = @initiator ? [@e.private_key, @rs] : [@s.private_key, @re]
76
79
  @symmetric_state.mix_key(dh_fn.dh(private_key, public_key))
77
- next
78
80
  when 'se'
79
- private_key, public_key = @initiator ? [@s[0], @re[1]] : [@e[0], @rs[1]]
81
+ private_key, public_key = @initiator ? [@s.private_key, @re] : [@e.private_key, @rs]
80
82
  @symmetric_state.mix_key(dh_fn.dh(private_key, public_key))
81
- next
82
83
  when 'ss'
83
- @symmetric_state.mix_key(dh_fn.dh(@s[0], @rs[1]))
84
- next
84
+ @symmetric_state.mix_key(dh_fn.dh(@s.private_key, @rs))
85
85
  when 'psk'
86
- @symmetric_state.mix_key_and_hash(@protocol.psks.shift)
87
- next
86
+ @symmetric_state.mix_key_and_hash(@connection.psks.shift)
88
87
  end
89
88
  end
90
89
  message_buffer << @symmetric_state.encrypt_and_hash(payload)
91
90
  @symmetric_state.split if @message_patterns.empty?
92
91
  end
93
92
 
93
+ # Takes a byte sequence containing a Noise handshake message,
94
+ # and a payload_buffer to write the message's plaintext payload into
94
95
  def read_message(message, payload_buffer)
95
96
  pattern = @message_patterns.shift
96
97
  dh_fn = @protocol.dh_fn
@@ -98,44 +99,32 @@ module Noise
98
99
  pattern.each do |token|
99
100
  case token
100
101
  when 'e'
101
- @re = @protocol.dh_fn.class.from_public(message[0...len]) if @re.empty?
102
+ @re = message[0...len] if @re.nil?
102
103
  message = message[len..-1]
103
- @symmetric_state.mix_hash(@re[1])
104
- @symmetric_state.mix_key(@re[1]) if @protocol.psk_handshake?
105
- next
104
+ @symmetric_state.mix_hash(@re)
105
+ @symmetric_state.mix_key(@re) if @protocol.psk_handshake?
106
106
  when 's'
107
- offset = @protocol.cipher_state_handshake.key? ? 16 : 0
107
+ offset = @connection.cipher_state_handshake.key? ? 16 : 0
108
108
  temp = message[0...len + offset]
109
109
  message = message[(len + offset)..-1]
110
- @rs = @protocol.dh_fn.class.from_public(@symmetric_state.decrypt_and_hash(temp))
111
- next
110
+ @rs = @symmetric_state.decrypt_and_hash(temp)
112
111
  when 'ee'
113
- @symmetric_state.mix_key(dh_fn.dh(@e[0], @re[1]))
114
- next
112
+ @symmetric_state.mix_key(dh_fn.dh(@e.private_key, @re))
115
113
  when 'es'
116
- private_key, public_key = @initiator ? [@e[0], @rs[1]] : [@s[0], @re[1]]
114
+ private_key, public_key = @initiator ? [@e.private_key, @rs] : [@s.private_key, @re]
117
115
  @symmetric_state.mix_key(dh_fn.dh(private_key, public_key))
118
- next
119
116
  when 'se'
120
- private_key, public_key = @initiator ? [@s[0], @re[1]] : [@e[0], @rs[1]]
117
+ private_key, public_key = @initiator ? [@s.private_key, @re] : [@e.private_key, @rs]
121
118
  @symmetric_state.mix_key(dh_fn.dh(private_key, public_key))
122
- next
123
119
  when 'ss'
124
- @symmetric_state.mix_key(dh_fn.dh(@s[0], @rs[1]))
125
- next
120
+ @symmetric_state.mix_key(dh_fn.dh(@s.private_key, @rs))
126
121
  when 'psk'
127
- @symmetric_state.mix_key_and_hash(@protocol.psks.shift)
128
- next
122
+ @symmetric_state.mix_key_and_hash(@connection.psks.shift)
129
123
  end
130
124
  end
131
125
  payload_buffer << @symmetric_state.decrypt_and_hash(message)
132
126
  @symmetric_state.split if @message_patterns.empty?
133
127
  end
134
-
135
- # no need for ephemeral keys after handshake has completed.
136
- def keypairs
137
- { s: @s, rs: @rs }
138
- end
139
128
  end
140
129
  end
141
130
  end
@@ -11,21 +11,13 @@ module Noise
11
11
  attr_reader :h, :ck
12
12
  attr_reader :cipher_state
13
13
 
14
- def initialize_h(protocol)
15
- if protocol.name.length <= protocol.hash_fn.hashlen
16
- protocol.name.ljust(protocol.hash_fn.hashlen, "\x00")
17
- else
18
- protocol.hash_fn.hash(protocol.name)
19
- end
20
- end
21
-
22
- def initialize_symmetric(protocol)
14
+ def initialize_symmetric(protocol, connection)
23
15
  @protocol = protocol
16
+ @connection = connection
24
17
  @ck = @h = initialize_h(protocol)
25
18
 
26
19
  @cipher_state = CipherState.new(cipher: @protocol.cipher_fn)
27
20
  @cipher_state.initialize_key(nil)
28
- @protocol.cipher_state_handshake = @cipher_state
29
21
  end
30
22
 
31
23
  def mix_key(input_key_meterial)
@@ -34,11 +26,12 @@ module Noise
34
26
  @cipher_state.initialize_key(temp_k)
35
27
  end
36
28
 
37
- # data [String] binary string
29
+ # @param [String] data binary string to be hashed
38
30
  def mix_hash(data)
39
31
  @h = @protocol.hash_fn.hash(@h + data)
40
32
  end
41
33
 
34
+ # This function is used for handling pre-shared symmetric keys.
42
35
  def mix_key_and_hash(input_key_meterial)
43
36
  @ck, temp_h, temp_k = @protocol.hkdf_fn.call(@ck, input_key_meterial, 3)
44
37
  mix_hash(temp_h)
@@ -46,42 +39,58 @@ module Noise
46
39
  @cipher_state.initialize_key(temp_k)
47
40
  end
48
41
 
42
+ # Returns h. This function should only be called at the end of a handshake,
43
+ # i.e. after the Split() function has been called.
44
+ # This function is used for channel binding
45
+ # @returns [String] h
49
46
  def handshake_hash
50
47
  @h
51
48
  end
52
49
 
50
+ # Note that if k is empty, the EncryptWithAd() call will set ciphertext equal to plaintext.
53
51
  def encrypt_and_hash(plaintext)
54
52
  ciphertext = @cipher_state.encrypt_with_ad(@h, plaintext)
55
53
  mix_hash(ciphertext)
56
54
  ciphertext
57
55
  end
58
56
 
57
+ # Note that if k is empty, the DecryptWithAd() call will set plaintext equal to ciphertext.
59
58
  def decrypt_and_hash(ciphertext)
60
59
  plaintext = @cipher_state.decrypt_with_ad(@h, ciphertext)
61
60
  mix_hash(ciphertext)
62
61
  plaintext
63
62
  end
64
63
 
65
- def create_cipher_state(k)
66
- k = truncate(k)
67
- c = CipherState.new(cipher: @protocol.cipher_fn)
68
- c.initialize_key(k)
69
- c
70
- end
71
-
64
+ # @return [CipherState, CipherState] a pair of CipherState objects for encrypting transport messages.
72
65
  def split
73
66
  temp_k1, temp_k2 = @protocol.hkdf_fn.call(@ck, '', 2)
74
67
  c1 = create_cipher_state(temp_k1)
75
68
  c2 = create_cipher_state(temp_k2)
76
- @protocol.cipher_state_encrypt = @protocol.initiator ? c1 : c2
77
- @protocol.cipher_state_decrypt = @protocol.initiator ? c2 : c1
78
- @protocol.handshake_done
69
+ @connection.handshake_done(c1, c2)
79
70
  [c1, c2]
80
71
  end
81
72
 
73
+ private
74
+
75
+ def initialize_h(protocol)
76
+ if protocol.name.length <= protocol.hash_fn.hashlen
77
+ protocol.name.ljust(protocol.hash_fn.hashlen, "\x00")
78
+ else
79
+ protocol.hash_fn.hash(protocol.name)
80
+ end
81
+ end
82
+
83
+ # truncates temp_k to 32 bytes if HASHLEN is 64
82
84
  def truncate(k)
83
85
  @protocol.hash_fn.hashlen == 64 ? k[0, 32] : k
84
86
  end
87
+
88
+ def create_cipher_state(k)
89
+ k = truncate(k)
90
+ c = CipherState.new(cipher: @protocol.cipher_fn)
91
+ c.initialize_key(k)
92
+ c
93
+ end
85
94
  end
86
95
  end
87
96
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Noise
4
- VERSION = '0.7.1'
4
+ VERSION = '0.7.3'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: noise-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 0.7.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hajime Yamaguchi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-04-19 00:00:00.000000000 Z
11
+ date: 2019-04-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -128,6 +128,9 @@ files:
128
128
  - bin/setup
129
129
  - lib/noise.rb
130
130
  - lib/noise/connection.rb
131
+ - lib/noise/connection/base.rb
132
+ - lib/noise/connection/initiator.rb
133
+ - lib/noise/connection/responder.rb
131
134
  - lib/noise/exceptions.rb
132
135
  - lib/noise/exceptions/decrypt_error.rb
133
136
  - lib/noise/exceptions/encrypt_error.rb
@@ -151,6 +154,7 @@ files:
151
154
  - lib/noise/functions/hash/blake2s.rb
152
155
  - lib/noise/functions/hash/sha256.rb
153
156
  - lib/noise/functions/hash/sha512.rb
157
+ - lib/noise/key.rb
154
158
  - lib/noise/key_pair.rb
155
159
  - lib/noise/pattern.rb
156
160
  - lib/noise/protocol.rb