hrr_rb_ssh 0.2.0 → 0.2.1

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: fa22df4a3d01a18e8f66b69b73f5b189233de3d4c755f1d371f15043c0202c8f
4
- data.tar.gz: e426371cf62d6d6705915a58b5811774e984be1f55b1e2f11db5925d0f14d08f
3
+ metadata.gz: 61d3c0613da093138597cc6a8e7ba24168e7374b0fffd31d26d58efabc234224
4
+ data.tar.gz: 60c45a6fb8eedc3cdadfa42bb79f686a42bbe955623637199e08bce44272d805
5
5
  SHA512:
6
- metadata.gz: 3e47080fab89ae9eafab849adf065dfa4a2f58f18f189aaef269d4b8d50729acad8d7d0c5306e462e583200878da878b84b4579a63788e8dd9859501102c1440
7
- data.tar.gz: 974b64d45859b689e6965d98ab749375ed082f6bbc72bc1ee59c1c2e6a8e59ca8bf7e25ff1304a6e9eb415763457dc9678447e363300d3106c63a84a7a9bbd03
6
+ metadata.gz: bde652be2f850130028aecca6f6dc401e334198aa73f7fa28dcd88fa059a79378bf2e0b1d075c134fc49403aad491b6d49d4d4cbc53098bd12459f27829293b1
7
+ data.tar.gz: acf3496654c007e03ef390cfd1057b99ba85d0f82c965eea31647ea8bd919b4362783039405922bcb3243ab2ff0fc269e53a4faf05ebde8a0039210ebecffd15
data/README.md CHANGED
@@ -19,6 +19,7 @@ hrr_rb_ssh is a pure Ruby SSH 2.0 server implementation.
19
19
  - [Defining authentications](#defining-authentications)
20
20
  - [Password authentication](#password-authentication)
21
21
  - [Publickey authentication](#publickey-authentication)
22
+ - [Keyboard-interactive authentication](#keyboard-interactive-authentication)
22
23
  - [None authentication (NOT recomended)](#none-authentication-not-recomended)
23
24
  - [Handling session channel requests](#handling-session-channel-requests)
24
25
  - [Reference request handlers](#reference-request-handlers)
@@ -180,9 +181,38 @@ The `context` variable in public key authentication context provides the `#verif
180
181
 
181
182
  And public keys that is in OpenSSH public key format is now available. To use OpenSSH public keys, it is easy to use $USER_HOME/.ssh/authorized_keys file.
182
183
 
184
+ ##### Keyboard-interactive authentication
185
+
186
+ The third one is keyboard-interactive authentication. This is also known as challenge-response authentication.
187
+
188
+ To define a keyboard-interactive authentication, the `HrrRbSsh::Authentication::Authenticator.new { |context| ... }` block is used as well. When the block returns `true`, then the authentication succeeded as well. However, `context` variable behaves differently.
189
+
190
+ ```ruby
191
+ auth_keyboard_interactive = HrrRbSsh::Authentication::Authenticator.new { |context|
192
+ user_name = 'user1'
193
+ req_name = 'demo keyboard interactive authentication'
194
+ req_instruction = 'demo instruction'
195
+ req_language_tag = ''
196
+ req_prompts = [
197
+ #[prompt[n], echo[n]]
198
+ ['Password1: ', false],
199
+ ['Password2: ', true],
200
+ ]
201
+ info_response = context.info_request req_name, req_instruction, req_language_tag, req_prompts
202
+ context.username == user_name && info_response.responses == ['password1', 'password2']
203
+ }
204
+ options['authentication_keyboard_interactive_authenticator'] = auth_keyboard_interactive
205
+ ```
206
+
207
+ The `context` variable in keyboard-interactive authentication context does NOT provides the `#verify` method. Instead, `#info_request` method is available. Since keyboard-interactive authentication has multiple times interactions between server and client, the values in responses needs to be verified respectively.
208
+
209
+ The `#info_request` method takes four arguments: name, instruction, language tag, and prompts. The name, instruction, and language tag can be empty string. The prompts needs to have at least one charactor for prompt message, and `true` or `false` value to specify whether echo back is enabled or not.
210
+
211
+ The responses are listed in the same order as request prompts.
212
+
183
213
  ##### None authentication (NOT recomended)
184
214
 
185
- The third one is none authentication. None authentication is usually NOT used.
215
+ The last one is none authentication. None authentication is usually NOT used.
186
216
 
187
217
  To define a none authentication, the `HrrRbSsh::Authentication::Authenticator.new { |context| ... }` block is used as well. When the block returns `true`, then the authentication succeeded as well. However, `context` variable behaves differently.
188
218
 
@@ -316,6 +346,7 @@ The following features are currently supported.
316
346
  - ecdsa-sha2-nistp256
317
347
  - ecdsa-sha2-nistp384
318
348
  - ecdsa-sha2-nistp521
349
+ - Keyboard interactive (generic interactive / challenge response) authentication
319
350
 
320
351
  ### Transport layer
321
352
 
data/demo/server.rb CHANGED
@@ -46,6 +46,19 @@ def start_service io, logger=nil
46
46
  context.verify user, pass
47
47
  }
48
48
  }
49
+ auth_keyboard_interactive = HrrRbSsh::Authentication::Authenticator.new { |context|
50
+ user_name = 'user1'
51
+ req_name = 'demo keyboard interactive authentication'
52
+ req_instruction = 'demo instruction'
53
+ req_language_tag = ''
54
+ req_prompts = [
55
+ #[prompt[n], echo[n]]
56
+ ['Password1: ', false],
57
+ ['Password2: ', true],
58
+ ]
59
+ info_response = context.info_request req_name, req_instruction, req_language_tag, req_prompts
60
+ context.username == user_name && info_response.responses == ['password1', 'password2']
61
+ }
49
62
 
50
63
 
51
64
  options = {}
@@ -65,9 +78,10 @@ OfeosJOO9twerD7pPhmXREkygblPsEXaVA==
65
78
  -----END EC PRIVATE KEY-----
66
79
  EOB
67
80
 
68
- options['authentication_none_authenticator'] = auth_none
69
- options['authentication_publickey_authenticator'] = auth_publickey
70
- options['authentication_password_authenticator'] = auth_password
81
+ options['authentication_none_authenticator'] = auth_none
82
+ options['authentication_publickey_authenticator'] = auth_publickey
83
+ options['authentication_password_authenticator'] = auth_password
84
+ options['authentication_keyboard_interactive_authenticator'] = auth_keyboard_interactive
71
85
 
72
86
  options['connection_channel_request_pty_req'] = HrrRbSsh::Connection::RequestHandler::ReferencePtyReqRequestHandler.new
73
87
  options['connection_channel_request_env'] = HrrRbSsh::Connection::RequestHandler::ReferenceEnvRequestHandler.new
@@ -0,0 +1,36 @@
1
+ # coding: utf-8
2
+ # vim: et ts=2 sw=2
3
+
4
+ require 'hrr_rb_ssh/logger'
5
+ require 'hrr_rb_ssh/authentication/method/keyboard_interactive/info_request'
6
+ require 'hrr_rb_ssh/authentication/method/keyboard_interactive/info_response'
7
+
8
+ module HrrRbSsh
9
+ class Authentication
10
+ class Method
11
+ class KeyboardInteractive
12
+ class Context
13
+ attr_reader \
14
+ :username,
15
+ :submethods,
16
+ :info_response
17
+
18
+ def initialize transport, username, submethods
19
+ @transport = transport
20
+ @username = username
21
+ @submethods = submethods
22
+
23
+ @logger = Logger.new self.class.name
24
+ end
25
+
26
+ def info_request name, instruction, language_tag, prompts
27
+ @logger.info { "send userauth info request" }
28
+ @transport.send InfoRequest.new(name, instruction, language_tag, prompts).to_payload
29
+ @logger.info { "receive userauth info response" }
30
+ @info_response = InfoResponse.new @transport.receive
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,45 @@
1
+ # coding: utf-8
2
+ # vim: et ts=2 sw=2
3
+
4
+ require 'hrr_rb_ssh/logger'
5
+ require 'hrr_rb_ssh/message'
6
+
7
+ module HrrRbSsh
8
+ class Authentication
9
+ class Method
10
+ class KeyboardInteractive
11
+ class InfoRequest
12
+ def initialize name, instruction, language_tag, prompts
13
+ @name = name
14
+ @instruction = instruction
15
+ @language_tag = language_tag
16
+ @prompts = prompts
17
+
18
+ @logger = Logger.new self.class.name
19
+ end
20
+
21
+ def to_message
22
+ message = {
23
+ :'message number' => Message::SSH_MSG_USERAUTH_INFO_REQUEST::VALUE,
24
+ :'name' => @name,
25
+ :'instruction' => @instruction,
26
+ :'language tag' => @language_tag,
27
+ :'num-prompts' => @prompts.size,
28
+ }
29
+ message_prompts = @prompts.map.with_index{ |(prompt, echo), i|
30
+ [
31
+ [:"prompt[#{i+1}]", prompt],
32
+ [:"echo[#{i+1}]", echo],
33
+ ].to_h
34
+ }.inject(Hash.new){ |a, b| a.merge(b) }
35
+ message.merge(message_prompts)
36
+ end
37
+
38
+ def to_payload
39
+ Message::SSH_MSG_USERAUTH_INFO_REQUEST.encode self.to_message
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ # vim: et ts=2 sw=2
3
+
4
+ require 'hrr_rb_ssh/message'
5
+
6
+ module HrrRbSsh
7
+ class Authentication
8
+ class Method
9
+ class KeyboardInteractive
10
+ class InfoResponse
11
+ attr_reader \
12
+ :num_responses,
13
+ :responses
14
+
15
+ def initialize payload
16
+ case payload[0,1].unpack("C")[0]
17
+ when Message::SSH_MSG_USERAUTH_INFO_RESPONSE::VALUE
18
+ message = Message::SSH_MSG_USERAUTH_INFO_RESPONSE.decode payload
19
+ @num_responses = message[:'num-responses']
20
+ @responses = Array.new(message[:'num-responses']){ |i| message[:"response[#{i+1}]"] }
21
+ else
22
+ raise "Expected SSH_MSG_USERAUTH_INFO_RESPONSE, but got message number #{payload[0,1].unpack("C")[0]}"
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ # vim: et ts=2 sw=2
3
+
4
+ require 'hrr_rb_ssh/logger'
5
+
6
+ module HrrRbSsh
7
+ class Authentication
8
+ class Method
9
+ class KeyboardInteractive < Method
10
+ NAME = 'keyboard-interactive'
11
+ PREFERENCE = 30
12
+
13
+ def initialize transport, options
14
+ @logger = Logger.new(self.class.name)
15
+ @transport = transport
16
+ @authenticator = options.fetch( 'authentication_keyboard_interactive_authenticator', Authenticator.new { false } )
17
+ end
18
+
19
+ def authenticate userauth_request_message
20
+ @logger.info { "authenticate" }
21
+ @logger.debug { "userauth request: " + userauth_request_message.inspect }
22
+ username = userauth_request_message[:'user name']
23
+ submethods = userauth_request_message[:'submethods']
24
+ context = Context.new(@transport, username, submethods)
25
+ @authenticator.authenticate context
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ require 'hrr_rb_ssh/authentication/method/keyboard_interactive/context'
@@ -10,7 +10,7 @@ module HrrRbSsh
10
10
  NAME = 'none'
11
11
  PREFERENCE = 0
12
12
 
13
- def initialize options
13
+ def initialize transport, options
14
14
  @logger = Logger.new(self.class.name)
15
15
  @authenticator = options.fetch( 'authentication_none_authenticator', Authenticator.new { false } )
16
16
  end
@@ -10,7 +10,7 @@ module HrrRbSsh
10
10
  NAME = 'password'
11
11
  PREFERENCE = 10
12
12
 
13
- def initialize options
13
+ def initialize transport, options
14
14
  @logger = Logger.new(self.class.name)
15
15
  @authenticator = options.fetch( 'authentication_password_authenticator', Authenticator.new { false } )
16
16
  end
@@ -10,7 +10,7 @@ module HrrRbSsh
10
10
  NAME = 'publickey'
11
11
  PREFERENCE = 20
12
12
 
13
- def initialize options
13
+ def initialize transport, options
14
14
  @logger = Logger.new(self.class.name)
15
15
  @session_id = options['session id']
16
16
  @authenticator = options.fetch( 'authentication_publickey_authenticator', Authenticator.new { false } )
@@ -17,3 +17,4 @@ end
17
17
  require 'hrr_rb_ssh/authentication/method/none'
18
18
  require 'hrr_rb_ssh/authentication/method/password'
19
19
  require 'hrr_rb_ssh/authentication/method/publickey'
20
+ require 'hrr_rb_ssh/authentication/method/keyboard_interactive'
@@ -69,7 +69,7 @@ module HrrRbSsh
69
69
  when Message::SSH_MSG_USERAUTH_REQUEST::VALUE
70
70
  userauth_request_message = Message::SSH_MSG_USERAUTH_REQUEST.decode payload
71
71
  method_name = userauth_request_message[:'method name']
72
- method = Method[method_name].new({'session id' => @transport.session_id}.merge(@options))
72
+ method = Method[method_name].new(@transport, {'session id' => @transport.session_id}.merge(@options))
73
73
  result = method.authenticate(userauth_request_message)
74
74
  case result
75
75
  when TrueClass
@@ -43,11 +43,19 @@ module HrrRbSsh
43
43
  [DataType::String, :'plaintext password'],
44
44
  ]
45
45
 
46
+ KEYBOARD_INTERACTIVE_DEFINITION = [
47
+ #[DataType, Field Name]
48
+ #[DataType::String, :'method name' : "keyboard-interactive"],
49
+ [DataType::String, :'language tag'],
50
+ [DataType::String, :'submethods'],
51
+ ]
52
+
46
53
  CONDITIONAL_DEFINITION = {
47
54
  # Field Name => {Field Value => Conditional Definition}
48
55
  :'method name' => {
49
- "publickey" => PUBLICKEY_DEFINITION,
50
- "password" => PASSWORD_DEFINITION,
56
+ "publickey" => PUBLICKEY_DEFINITION,
57
+ "password" => PASSWORD_DEFINITION,
58
+ "keyboard-interactive" => KEYBOARD_INTERACTIVE_DEFINITION,
51
59
  },
52
60
  :'with signature' => {
53
61
  true => PUBLICKEY_SIGNATURE_DEFINITION,
@@ -0,0 +1,43 @@
1
+ # coding: utf-8
2
+ # vim: et ts=2 sw=2
3
+
4
+ require 'hrr_rb_ssh/data_type'
5
+ require 'hrr_rb_ssh/codable'
6
+
7
+ module HrrRbSsh
8
+ module Message
9
+ module SSH_MSG_USERAUTH_INFO_REQUEST
10
+ class << self
11
+ include Codable
12
+ end
13
+
14
+ ID = self.name.split('::').last
15
+ VALUE = 60
16
+
17
+ DEFINITION = [
18
+ #[DataType, Field Name]
19
+ [DataType::Byte, :'message number'],
20
+ [DataType::String, :'name'],
21
+ [DataType::String, :'instruction'],
22
+ [DataType::String, :'language tag'],
23
+ [DataType::Uint32, :'num-prompts'],
24
+ ]
25
+
26
+ PER_NUM_PROMPTS_DEFINITION = Hash.new{ |hash, key|
27
+ Array.new(key){ |i|
28
+ [
29
+ #[DataType, Field Name]
30
+ #[DataType::String, :'num-prompts' : "> 0"],
31
+ [DataType::String, :"prompt[#{i+1}]"],
32
+ [DataType::Boolean, :"echo[#{i+1}]"],
33
+ ]
34
+ }.inject(:+)
35
+ }
36
+
37
+ CONDITIONAL_DEFINITION = {
38
+ # Field Name => {Field Value => Conditional Definition}
39
+ :'num-prompts' => PER_NUM_PROMPTS_DEFINITION,
40
+ }
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,39 @@
1
+ # coding: utf-8
2
+ # vim: et ts=2 sw=2
3
+
4
+ require 'hrr_rb_ssh/data_type'
5
+ require 'hrr_rb_ssh/codable'
6
+
7
+ module HrrRbSsh
8
+ module Message
9
+ module SSH_MSG_USERAUTH_INFO_RESPONSE
10
+ class << self
11
+ include Codable
12
+ end
13
+
14
+ ID = self.name.split('::').last
15
+ VALUE = 61
16
+
17
+ DEFINITION = [
18
+ #[DataType, Field Name]
19
+ [DataType::Byte, :'message number'],
20
+ [DataType::Uint32, :'num-responses'],
21
+ ]
22
+
23
+ PER_NUM_RESPONSES_DEFINITION = Hash.new{ |hash, key|
24
+ Array.new(key){ |i|
25
+ [
26
+ #[DataType, Field Name]
27
+ #[DataType::String, :'num-responses' : "> 0"],
28
+ [DataType::String, :"response[#{i+1}]"],
29
+ ]
30
+ }.inject(:+)
31
+ }
32
+
33
+ CONDITIONAL_DEFINITION = {
34
+ # Field Name => {Field Value => Conditional Definition}
35
+ :'num-responses' => PER_NUM_RESPONSES_DEFINITION,
36
+ }
37
+ end
38
+ end
39
+ end
@@ -22,6 +22,8 @@ require 'hrr_rb_ssh/message/050_ssh_msg_userauth_request'
22
22
  require 'hrr_rb_ssh/message/051_ssh_msg_userauth_failure'
23
23
  require 'hrr_rb_ssh/message/052_ssh_msg_userauth_success'
24
24
  require 'hrr_rb_ssh/message/060_ssh_msg_userauth_pk_ok'
25
+ require 'hrr_rb_ssh/message/060_ssh_msg_userauth_info_request'
26
+ require 'hrr_rb_ssh/message/061_ssh_msg_userauth_info_response'
25
27
  require 'hrr_rb_ssh/message/080_ssh_msg_global_request.rb'
26
28
  require 'hrr_rb_ssh/message/081_ssh_msg_request_success.rb'
27
29
  require 'hrr_rb_ssh/message/082_ssh_msg_request_failure.rb'
@@ -2,5 +2,5 @@
2
2
  # vim: et ts=2 sw=2
3
3
 
4
4
  module HrrRbSsh
5
- VERSION = "0.2.0"
5
+ VERSION = "0.2.1"
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hrr_rb_ssh
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - hirura
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-05-19 00:00:00.000000000 Z
11
+ date: 2018-05-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -104,6 +104,10 @@ files:
104
104
  - lib/hrr_rb_ssh/authentication.rb
105
105
  - lib/hrr_rb_ssh/authentication/authenticator.rb
106
106
  - lib/hrr_rb_ssh/authentication/method.rb
107
+ - lib/hrr_rb_ssh/authentication/method/keyboard_interactive.rb
108
+ - lib/hrr_rb_ssh/authentication/method/keyboard_interactive/context.rb
109
+ - lib/hrr_rb_ssh/authentication/method/keyboard_interactive/info_request.rb
110
+ - lib/hrr_rb_ssh/authentication/method/keyboard_interactive/info_response.rb
107
111
  - lib/hrr_rb_ssh/authentication/method/none.rb
108
112
  - lib/hrr_rb_ssh/authentication/method/none/context.rb
109
113
  - lib/hrr_rb_ssh/authentication/method/password.rb
@@ -183,7 +187,9 @@ files:
183
187
  - lib/hrr_rb_ssh/message/050_ssh_msg_userauth_request.rb
184
188
  - lib/hrr_rb_ssh/message/051_ssh_msg_userauth_failure.rb
185
189
  - lib/hrr_rb_ssh/message/052_ssh_msg_userauth_success.rb
190
+ - lib/hrr_rb_ssh/message/060_ssh_msg_userauth_info_request.rb
186
191
  - lib/hrr_rb_ssh/message/060_ssh_msg_userauth_pk_ok.rb
192
+ - lib/hrr_rb_ssh/message/061_ssh_msg_userauth_info_response.rb
187
193
  - lib/hrr_rb_ssh/message/080_ssh_msg_global_request.rb
188
194
  - lib/hrr_rb_ssh/message/081_ssh_msg_request_success.rb
189
195
  - lib/hrr_rb_ssh/message/082_ssh_msg_request_failure.rb