hrr_rb_ssh 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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