net-ssh 7.3.6 → 8.0.0.beta3

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: a1fd91f862a832657da3941f813a7a7d2204b2b08cc0082db740d27e9a23e7cc
4
- data.tar.gz: b6f84d4e0ff8391ca7ccaab236c0eda12fdc0d46d7f7751a19d355cbf2b4cd5d
3
+ metadata.gz: ee72c6673cb55fe69cdb90bc49d0b7266b29cb80f7a14794f873e048075cde2b
4
+ data.tar.gz: b9416f51d2b80d5d0bbbcac1b33a4272d5b5add59d81421ca8eaa84db2fd0823
5
5
  SHA512:
6
- metadata.gz: 4b8edaf68052b8b828993179ee6f8a29df1e64ca63e1cad5228d4e58ec50c088b321deaf0ad8a202eb6fe27a9a923be2dbe1dc5569e049634379e64b1d4228d5
7
- data.tar.gz: 6b2f300dab731967559d13d6ac18c756c27ff39222dd2370b1e32b4187475cb0103e5e2683dfbe03624d172756232066ee30dc84bed8bfaa7d032c3a9736dd69
6
+ metadata.gz: 4f1d529b4af06145b892c6ece94d2fcc6a9d2b65b93db633189b71988b8bc03aa64feffdfce3509ce64809378ff542b04f01381419d5a9e11246c7303d258583
7
+ data.tar.gz: 2170524d2a24e1ec8d0c07c3237d140ad00c412c59875f6f178b6f4a2c47ce9d16f8418dee56e0425d8ec8a214f50e0cc96e434d1b6e37998125e1cda9ebb41e
checksums.yaml.gz.sig CHANGED
Binary file
@@ -17,9 +17,6 @@ jobs:
17
17
  - name: Build docker images
18
18
  run: docker compose build
19
19
 
20
- - name: Run ruby 2.6
21
- run: docker compose run ruby-2.6
22
-
23
20
  - name: Run ruby 2.7
24
21
  run: docker compose run ruby-2.7
25
22
 
@@ -7,7 +7,7 @@ jobs:
7
7
  runs-on: ubuntu-22.04
8
8
  strategy:
9
9
  matrix:
10
- ruby-version: [2.6.10, 2.7.7, 3.0.6, 3.1.3, 3.2.1, 3.3, 3.4]
10
+ ruby-version: [2.7.8, 3.0.6, 3.1.3, 3.2.1, 3.3.0, 3.4]
11
11
  steps:
12
12
  - uses: actions/checkout@v3
13
13
 
@@ -39,11 +39,10 @@ jobs:
39
39
  ${{ runner.os }}-pip-v1
40
40
  - name: Bundle install
41
41
  run: |
42
- gem install bundler ${{ (startsWith(matrix.ruby-version, '2.6.') || startsWith(matrix.ruby-version, '2.7.')) && '-v 2.4.22' || '' }}
42
+ gem install bundler ${{ startsWith(matrix.ruby-version, '2.7.') && '-v 2.4.22' || '' }}
43
43
  bundle config set path 'vendor/bundle'
44
44
  bundle config set --local path 'vendor/bundle'
45
45
  bundle install --jobs 4 --retry 3
46
- BUNDLE_GEMFILE=./Gemfile.noed25519 bundle install --jobs 4 --retry 3
47
46
  env:
48
47
  BUNDLE_PATH: vendor/bundle
49
48
 
@@ -79,20 +78,6 @@ jobs:
79
78
  NET_SSH_RUN_INTEGRATION_TESTS: 1
80
79
  CI: 1
81
80
  RUBYOPT: '--enable=frozen-string-literal'
82
- - name: Run tests (without rbnacl)
83
- run: bundle exec rake test
84
- env:
85
- BUNDLE_GEMFILE: ./Gemfile.norbnacl
86
- NET_SSH_RUN_INTEGRATION_TESTS: 1
87
- CI: 1
88
- RUBYOPT: '--enable=frozen-string-literal'
89
- - name: Run Tests (without ed25519)
90
- run: bundle exec rake test
91
- env:
92
- BUNDLE_GEMFILE: ./Gemfile.noed25519
93
- NET_SSH_RUN_INTEGRATION_TESTS: 1
94
- CI: 1
95
- RUBYOPT: '--enable=frozen-string-literal'
96
81
  - name: Run test helper test
97
82
  run: bundle exec rake test_test
98
83
  env:
data/.rubocop.yml CHANGED
@@ -4,7 +4,7 @@ AllCops:
4
4
  - "vendor/**/.*"
5
5
  - "vendor/**/*"
6
6
  NewCops: enable
7
- TargetRubyVersion: 2.6
7
+ TargetRubyVersion: 2.7
8
8
 
9
9
  inherit_from: .rubocop_todo.yml
10
10
 
data/.rubocop_todo.yml CHANGED
@@ -335,6 +335,7 @@ Naming/MemoizedInstanceVariableName:
335
335
  Naming/MethodName:
336
336
  Exclude:
337
337
  - 'lib/net/ssh/authentication/ed25519_loader.rb'
338
+ - 'lib/net/ssh/transport/chacha20_poly1305_cipher_loader.rb'
338
339
  - 'lib/net/ssh/transport/kex/curve25519_sha256_loader.rb'
339
340
  - 'test/authentication/test_agent.rb'
340
341
  - 'test/authentication/test_session.rb'
data/CHANGES.txt CHANGED
@@ -1,19 +1,6 @@
1
- === 7.3.6
1
+ === 8.0.0 beta3
2
2
 
3
- * Fix "Calling Net::SSH::Buffer methods on HostKeyEntries PubKey is deprecated" warnings printed on every connection to a host in known_hosts since 7.3.4 [#1026, #1028]
4
-
5
- === 7.3.5
6
-
7
- * Security: refuse agent channels opened by the server unless agent forwarding was requested (`forward_agent`, ForwardAgent, or `Forward#agent`); any use of port forwarding let a malicious server use the local ssh-agent [GHSA-qpg5-fx6p-8cq3]
8
- * Security: reject host and user names containing shell metacharacters, and non-numeric ports, before substituting them into ProxyCommand/ProxyJump commands, as OpenSSH does since 9.6 [GHSA-3x7f-q7wf-fp73]
9
- * Security: reject OpenSSH private keys claiming more than 2**20 bcrypt rounds, and only run the KDF once a passphrase is given [GHSA-rpq3-v334-f5pm]
10
- * Security: bound the server's identification banner like OpenSSH (8192-byte lines, 1024 lines), and apply `:timeout` to the whole version exchange instead of only its first byte [GHSA-r75w-7fhp-84w8]
11
- * Security: reject packet lengths below 5 or above 256 KB, as OpenSSH does [GHSA-pfqh-p9vh-wx4p]
12
- * Security: treat unexpected packets during the initial key exchange as a protocol error, and bound packets queued during a re-key [GHSA-ggxh-cfwq-5xqc]
13
-
14
- === 7.3.4
15
-
16
- * Verify @cert-authority host certificate constraints — principals (shell-glob, matching OpenSSH), validity window, host type, and critical options — instead of trusting any CA-signed certificate for any host [#1010]
3
+ * Verify @cert-authority host certificate constraints — principals (shell-glob, matching OpenSSH), validity window, host type, and critical options — instead of trusting any CA-signed certificate for any host [#1009]
17
4
 
18
5
  === 7.3.3
19
6
 
data/Dockerfile CHANGED
@@ -3,9 +3,6 @@ FROM ruby:${RUBY_VERSION}
3
3
 
4
4
  ARG BUNDLERV=
5
5
 
6
- # Ruby 2.6 to 3.0 images are built on Debian bullseye, which only archive.debian.org still serves.
7
- RUN if grep -q bullseye /etc/os-release; then sed -i 's|deb.debian.org|archive.debian.org|; s|security.debian.org|archive.debian.org|' /etc/apt/sources.list; fi
8
-
9
6
  RUN apt update && apt install -y openssh-server sudo netcat-openbsd \
10
7
  && useradd --create-home --shell '/bin/bash' --comment 'NetSSH' 'net_ssh_1' \
11
8
  && useradd --create-home --shell '/bin/bash' --comment 'NetSSH' 'net_ssh_2' \
@@ -29,4 +26,4 @@ RUN gem install bundler ${BUNDLERV} && bundle install
29
26
 
30
27
  COPY . $INSTALL_PATH/
31
28
 
32
- CMD service ssh start && rake test && NET_SSH_NO_ED25519=1 rake test
29
+ CMD service ssh start && rake test
data/Dockerfile.openssl3 CHANGED
@@ -2,7 +2,7 @@ FROM ubuntu:22.04
2
2
 
3
3
  ENV INSTALL_PATH="/netssh"
4
4
 
5
- RUN apt update && apt install -y openssl ruby ruby-dev git build-essential
5
+ RUN apt update && apt install -y openssl libssl-dev ruby ruby-dev git build-essential
6
6
 
7
7
  WORKDIR $INSTALL_PATH
8
8
 
data/Dockerfile.zlib_ng CHANGED
@@ -11,7 +11,7 @@ ENV INSTALL_PATH="/netssh"
11
11
  # Ruby/OpenSSL initialises — setting it in the Rakefile is too late.
12
12
  ENV OPENSSL_CONF="/netssh/test/openssl3.conf"
13
13
 
14
- RUN dnf install -y openssl ruby ruby-devel git make gcc redhat-rpm-config zlib-ng crypto-policies-scripts && \
14
+ RUN dnf install -y openssl openssl-devel ruby ruby-devel git make gcc zlib-ng crypto-policies-scripts && \
15
15
  update-crypto-policies --set LEGACY
16
16
 
17
17
  WORKDIR $INSTALL_PATH
data/README.md CHANGED
@@ -40,7 +40,7 @@ Unsecure algoritms will definitely be removed in Net::SSH 8.*.
40
40
  | Name | Support | Details |
41
41
  |----------------------|-----------------------|----------|
42
42
  | ssh-rsa | OK | |
43
- | ssh-ed25519 | OK | Require the gem `ed25519` |
43
+ | ssh-ed25519 | OK | Backed by Ruby OpenSSL raw Ed25519 APIs (`openssl >= 3.2.0`) |
44
44
  | ecdsa-sha2-nistp521 | OK | [using weak elliptic curves](https://safecurves.cr.yp.to/) |
45
45
  | ecdsa-sha2-nistp384 | OK | [using weak elliptic curves](https://safecurves.cr.yp.to/) |
46
46
  | ecdsa-sha2-nistp256 | OK | [using weak elliptic curves](https://safecurves.cr.yp.to/) |
@@ -50,7 +50,7 @@ Unsecure algoritms will definitely be removed in Net::SSH 8.*.
50
50
 
51
51
  | Name | Support | Details |
52
52
  |--------------------------------------|-----------------------|----------|
53
- | curve25519-sha256 | OK | Require the gem `x25519` |
53
+ | curve25519-sha256 | OK | Backed by Ruby OpenSSL raw X25519 APIs (`openssl >= 3.2.0`) |
54
54
  | ecdh-sha2-nistp521 | OK | [using weak elliptic curves](https://safecurves.cr.yp.to/) |
55
55
  | ecdh-sha2-nistp384 | OK | [using weak elliptic curves](https://safecurves.cr.yp.to/) |
56
56
  | ecdh-sha2-nistp256 | OK | [using weak elliptic curves](https://safecurves.cr.yp.to/) |
@@ -64,7 +64,7 @@ Unsecure algoritms will definitely be removed in Net::SSH 8.*.
64
64
  | Name | Support | Details |
65
65
  |--------------------------------------|-----------------------|----------|
66
66
  | aes256-ctr / aes192-ctr / aes128-ctr | OK | |
67
- | chacha20-poly1305@openssh.com | OK. | Requires the gem `rbnacl` |
67
+ | chacha20-poly1305@openssh.com | OK. | Backed by Ruby OpenSSL ChaCha20 and Poly1305 APIs (`openssl >= 3.2.0`) |
68
68
  | aes256-cbc / aes192-cbc / aes128-cbc | Deprecated in 6.0 | unsecure, will be removed in 8.0 |
69
69
  | rijndael-cbc@lysator.liu.se | Deprecated in 6.0 | unsecure, will be removed in 8.0 |
70
70
  | blowfish-ctr blowfish-cbc | Deprecated in 6.0 | unsecure, will be removed in 8.0 |
@@ -148,16 +148,16 @@ See Net::SSH for more documentation, and links to further information.
148
148
 
149
149
  ## REQUIREMENTS:
150
150
 
151
- The only requirement you might be missing is the OpenSSL bindings for Ruby with a version greather than `1.0.1`.
152
- These are built by default on most platforms, but you can verify that they're built and installed on your system by running the following command line:
151
+ Net::SSH requires Ruby 2.7 or newer and the Ruby `openssl` gem version `3.2.0` or newer.
152
+ The `openssl` gem uses the OpenSSL library Ruby is built against. You can verify the loaded gem and library with:
153
153
 
154
154
  ```sh
155
- ruby -ropenssl -e 'puts OpenSSL::OPENSSL_VERSION'
155
+ ruby -ropenssl -e 'puts OpenSSL::VERSION; puts OpenSSL::OPENSSL_VERSION'
156
156
  ```
157
157
 
158
- If that spits out something like `OpenSSL 1.0.1 14 Mar 2012`, then you're set.
159
- If you get an error, then you'll need to see about rebuilding ruby with OpenSSL support,
160
- or (if your platform supports it) installing the OpenSSL bindings separately.
158
+ If `openssl` cannot be required, rebuild Ruby with OpenSSL support or install the Ruby `openssl` gem separately.
159
+
160
+ Ed25519, curve25519-sha256, and chacha20-poly1305@openssh.com support also require an OpenSSL backend with Ed25519, X25519, ChaCha20, and Poly1305 enabled.
161
161
 
162
162
  ## INSTALL:
163
163
 
@@ -188,15 +188,12 @@ gem install net-ssh -P HighSecurity
188
188
  If you don't add the public key, you'll see an error like "Couldn't verify data signature".
189
189
  If you're still having trouble let me know and I'll give you a hand.
190
190
 
191
- For ed25519 public key auth support your bundle file should contain `ed25519`, `bcrypt_pbkdf` dependencies.
191
+ Ed25519 public key auth is backed by Ruby OpenSSL. The `bcrypt_pbkdf` gem is only needed when loading bcrypt-encrypted OpenSSH private keys.
192
192
 
193
193
  ```sh
194
- gem install ed25519
195
194
  gem install bcrypt_pbkdf
196
195
  ```
197
196
 
198
- For curve25519-sha256 kex exchange support your bundle file should contain `x25519` dependency.
199
-
200
197
  ## RUBY SUPPORT
201
198
 
202
199
  * See [net-ssh.gemspec](https://github.com/net-ssh/net-ssh/blob/master/net-ssh.gemspec) for current versions ruby requirements
data/appveyor.yml CHANGED
@@ -4,16 +4,9 @@ skip_tags: true
4
4
 
5
5
  environment:
6
6
  matrix:
7
- - ruby_version: "jruby-9.1.2.0"
8
- - ruby_version: "26-x64"
9
- - ruby_version: "25-x64"
10
- - ruby_version: "24-x64"
11
- - ruby_version: "23"
12
- - ruby_version: "23-x64"
13
-
14
- matrix:
15
- allow_failures:
16
- - ruby_version: "jruby-9.1.2.0"
7
+ - ruby_version: "31-x64"
8
+ - ruby_version: "30-x64"
9
+ - ruby_version: "27-x64"
17
10
 
18
11
  #init:
19
12
  # - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
@@ -27,12 +20,8 @@ platform:
27
20
 
28
21
  install:
29
22
  - SET PATH=C:\Ruby%ruby_version%\bin;%PATH%
30
- - if "%ruby_version%" == "jruby-9.1.2.0" ( cinst javaruntime -i )
31
- - if "%ruby_version%" == "jruby-9.1.2.0" ( cinst jruby --version 9.1.2.0 -i --allow-empty-checksums )
32
- - if "%ruby_version%" == "jruby-9.1.2.0" ( SET "PATH=C:\jruby-9.1.2.0\bin\;%PATH%" )
33
23
  - ruby --version
34
- - gem install bundler --no-document --user-install -v 1.17
35
- - SET BUNDLE_GEMFILE=Gemfile.noed25519
24
+ - gem install bundler --no-document --user-install -v 2.4.22
36
25
  - bundle install --retry=3
37
26
  - cinst freesshd
38
27
  - cinst putty --allow-empty-checksums
@@ -51,7 +40,6 @@ install:
51
40
  }
52
41
 
53
42
  test_script:
54
- - SET BUNDLE_GEMFILE=Gemfile.noed25519
55
43
  - SET NET_SSH_RUN_WIN_INTEGRATION_TESTS=YES
56
44
  - bundle exec rake test
57
45
 
data/docker-compose.yml CHANGED
@@ -13,11 +13,5 @@ services:
13
13
  build:
14
14
  context: .
15
15
  args:
16
- RUBY_VERSION: 2.7
16
+ RUBY_VERSION: 2.7.8
17
17
  BUNDLERV: "-v 2.2.28"
18
- ruby-2.6:
19
- build:
20
- context: .
21
- args:
22
- RUBY_VERSION: 2.6
23
- BUNDLERV: "-v 2.4.22"
@@ -261,13 +261,13 @@ module Net
261
261
  when /^ecdsa\-sha2\-(\w*)-cert-v01@openssh\.com$/
262
262
  Net::SSH::Buffer.from(:string, priv_key.to_blob, :bignum, priv_key.key.private_key).to_s
263
263
  when /^ssh-ed25519$/
264
- Net::SSH::Buffer.from(:string, priv_key.public_key.verify_key.to_bytes,
265
- :string, priv_key.sign_key.keypair).to_s
264
+ Net::SSH::Buffer.from(:string, priv_key.public_key_bytes,
265
+ :string, priv_key.private_key_bytes_for_agent).to_s
266
266
  when /^ssh-ed25519-cert-v01@openssh\.com$/
267
267
  # Unlike the other certificate types, the public key is included after the certifiate.
268
268
  Net::SSH::Buffer.from(:string, priv_key.to_blob,
269
- :string, priv_key.key.public_key.verify_key.to_bytes,
270
- :string, priv_key.key.sign_key.keypair).to_s
269
+ :string, priv_key.key.public_key_bytes,
270
+ :string, priv_key.key.private_key_bytes_for_agent).to_s
271
271
  when /^ssh-rsa$/
272
272
  # `n` and `e` are reversed compared to the ordering in `OpenSSL::PKey::RSA#to_blob`.
273
273
  Net::SSH::Buffer.from(:bignum, priv_key.n, :bignum, priv_key.e, :bignum, priv_key.d,
@@ -1,25 +1,57 @@
1
- gem 'ed25519', '~> 1.2'
2
- gem 'bcrypt_pbkdf', '~> 1.0' unless RUBY_PLATFORM == "java"
3
-
4
- require 'ed25519'
5
-
1
+ require 'openssl'
6
2
  require 'net/ssh/transport/cipher_factory'
7
3
  require 'net/ssh/authentication/pub_key_fingerprint'
8
- require 'bcrypt_pbkdf' unless RUBY_PLATFORM == "java"
9
4
 
10
5
  module Net
11
6
  module SSH
12
7
  module Authentication
13
8
  module ED25519
14
- class SigningKeyFromFile < SimpleDelegator
15
- def initialize(pk, sk)
16
- key = ::Ed25519::SigningKey.from_keypair(sk)
17
- raise ArgumentError, "pk does not match sk" unless pk == key.verify_key.to_bytes
9
+ ALGORITHM = "ED25519"
10
+ KEY_BYTES = 32
11
+ PRIVATE_KEY_BYTES = 64
18
12
 
19
- super(key)
13
+ class UnsupportedError < StandardError; end
14
+
15
+ def self.ensure_supported!
16
+ unless OpenSSL::PKey.respond_to?(:new_raw_public_key) && OpenSSL::PKey.respond_to?(:new_raw_private_key)
17
+ raise UnsupportedError, "OpenSSL::PKey raw public/private key APIs are unavailable"
20
18
  end
19
+
20
+ key = new_private_key("\x00" * KEY_BYTES)
21
+ signature = sign(key, "")
22
+ public_key = new_public_key(key.raw_public_key)
23
+ raise UnsupportedError, "OpenSSL Ed25519 signature verification failed" unless verify(public_key, signature, "")
24
+ rescue OpenSSL::PKey::PKeyError => e
25
+ raise UnsupportedError, e.message
26
+ end
27
+
28
+ def self.new_public_key(key)
29
+ validate_key_bytes!(key, "public key", KEY_BYTES)
30
+ OpenSSL::PKey.new_raw_public_key(ALGORITHM, binary_string(key))
31
+ end
32
+
33
+ def self.new_private_key(key)
34
+ validate_key_bytes!(key, "private key", KEY_BYTES)
35
+ OpenSSL::PKey.new_raw_private_key(ALGORITHM, binary_string(key))
36
+ end
37
+
38
+ def self.sign(key, data)
39
+ key.sign(nil, data)
40
+ end
41
+
42
+ def self.verify(key, signature, data)
43
+ key.verify(nil, signature, data)
44
+ end
45
+
46
+ def self.validate_key_bytes!(key, label, expected_bytes)
47
+ raise ArgumentError, "invalid Ed25519 #{label}" unless key.respond_to?(:bytesize) && key.bytesize == expected_bytes
21
48
  end
22
49
 
50
+ def self.binary_string(string)
51
+ string.dup.force_encoding('BINARY')
52
+ end
53
+ private_class_method :binary_string
54
+
23
55
  class OpenSSHPrivateKeyLoader
24
56
  CipherFactory = Net::SSH::Transport::CipherFactory
25
57
 
@@ -27,9 +59,6 @@ module Net
27
59
  MEND = "-----END OPENSSH PRIVATE KEY-----"
28
60
  MAGIC = "openssh-key-v1"
29
61
 
30
- # Same limit as OpenSSH; bcrypt_pbkdf with 2**32 rounds would run for months (GHSA-rpq3-v334-f5pm).
31
- MAX_KDF_ROUNDS = 1 << 20
32
-
33
62
  class DecryptError < ArgumentError
34
63
  def initialize(message, encrypted_key: false)
35
64
  super(message)
@@ -41,11 +70,6 @@ module Net
41
70
  end
42
71
  end
43
72
 
44
- def self.check_bcrypt_kdf!(rounds, password)
45
- raise ArgumentError.new("Private key bcrypt rounds #{rounds} exceeds #{MAX_KDF_ROUNDS}") if rounds > MAX_KDF_ROUNDS
46
- raise DecryptError.new("Passphrase required for encrypted private key", encrypted_key: true) if password.nil? || password.empty?
47
- end
48
-
49
73
  def self.read(datafull, password)
50
74
  datafull = datafull.strip
51
75
  raise ArgumentError.new("Expected #{MBEGIN} at start of private key") unless datafull.start_with?(MBEGIN)
@@ -55,7 +79,7 @@ module Net
55
79
  data = datab64.unpack1("m")
56
80
  raise ArgumentError.new("Expected #{MAGIC} at start of decoded private key") unless data.start_with?(MAGIC)
57
81
 
58
- buffer = Net::SSH::Buffer.new(data[MAGIC.size + 1..-1])
82
+ buffer = Net::SSH::Buffer.new(data[(MAGIC.size + 1)..-1])
59
83
 
60
84
  ciphername = buffer.read_string
61
85
  raise ArgumentError.new("#{ciphername} in private key is not supported") unless
@@ -71,29 +95,34 @@ module Net
71
95
  _pubkey = buffer.read_string
72
96
 
73
97
  len = buffer.read_long
98
+ encrypted_private = buffer.read(len)
74
99
 
75
100
  keylen, blocksize, ivlen = CipherFactory.get_lengths(ciphername, iv_len: true)
76
101
  raise ArgumentError.new("Private key len:#{len} is not a multiple of #{blocksize}") if
77
- ((len < blocksize) || ((blocksize > 0) && (len % blocksize) != 0))
102
+ (len < blocksize) || ((blocksize > 0) && (len % blocksize) != 0)
103
+
104
+ raise ArgumentError.new("Private key len:#{len} exceeds available data") unless encrypted_private.bytesize == len
105
+
106
+ authlen = CipherFactory.auth_length(ciphername)
107
+ auth_tag = authlen > 0 ? buffer.read(authlen) : nil
78
108
 
79
109
  if kdfname == 'bcrypt'
80
110
  salt = kdfopts.read_string
81
111
  rounds = kdfopts.read_long
82
112
 
83
- raise "BCryptPbkdf is not implemented for jruby" if RUBY_PLATFORM == "java"
84
-
85
- check_bcrypt_kdf!(rounds, password)
86
-
87
- key = BCryptPbkdf::key(password, salt, keylen + ivlen, rounds)
88
- raise DecryptError.new("BCyryptPbkdf failed", encrypted_key: true) unless key
113
+ key = bcrypt_pbkdf_key(password, salt, keylen + ivlen, rounds)
89
114
  else
90
- key = '\x00' * (keylen + ivlen)
115
+ key = "\x00" * (keylen + ivlen)
91
116
  end
92
117
 
93
- cipher = CipherFactory.get(ciphername, key: key[0...keylen], iv: key[keylen...keylen + ivlen], decrypt: true)
94
-
95
- decoded = cipher.update(buffer.remainder_as_buffer.to_s)
96
- decoded << cipher.final
118
+ decoded = decrypt_private_key(
119
+ ciphername,
120
+ encrypted_private,
121
+ auth_tag,
122
+ key: key[0...keylen],
123
+ iv: key[keylen...(keylen + ivlen)],
124
+ encrypted_key: kdfname == 'bcrypt'
125
+ )
97
126
 
98
127
  decoded = Net::SSH::Buffer.new(decoded)
99
128
  check1 = decoded.read_long
@@ -109,15 +138,49 @@ module Net
109
138
  decoded.read_private_keyblob(type_name)
110
139
  end
111
140
  end
141
+
142
+ def self.bcrypt_pbkdf_key(password, salt, length, rounds)
143
+ begin
144
+ require_bcrypt_pbkdf
145
+ rescue LoadError
146
+ raise DecryptError.new("bcrypt_pbkdf is required to decrypt bcrypt-encrypted OpenSSH private keys")
147
+ end
148
+
149
+ key = BCryptPbkdf::key(password, salt, length, rounds)
150
+ raise DecryptError.new("BCryptPbkdf failed", encrypted_key: true) unless key
151
+
152
+ key
153
+ end
154
+
155
+ def self.require_bcrypt_pbkdf
156
+ require 'bcrypt_pbkdf'
157
+ end
158
+
159
+ def self.decrypt_private_key(ciphername, encrypted_private, auth_tag, options)
160
+ if auth_tag
161
+ return CipherFactory.decrypt_private_key(
162
+ ciphername,
163
+ encrypted_private,
164
+ auth_tag,
165
+ options[:key],
166
+ options[:iv]
167
+ )
168
+ end
169
+
170
+ cipher = CipherFactory.get(ciphername, key: options[:key], iv: options[:iv], decrypt: true)
171
+ decoded = cipher.update(encrypted_private)
172
+ decoded << cipher.final
173
+ rescue Net::SSH::Exception, OpenSSL::Cipher::CipherError
174
+ raise DecryptError.new("Decrypt failed on private key", encrypted_key: options[:encrypted_key])
175
+ end
112
176
  end
113
177
 
114
178
  class PubKey
115
179
  include Net::SSH::Authentication::PubKeyFingerprint
116
180
 
117
- attr_reader :verify_key
118
-
119
181
  def initialize(data)
120
- @verify_key = ::Ed25519::VerifyKey.new(data)
182
+ ED25519.validate_key_bytes!(data, "public key", KEY_BYTES)
183
+ @public_key_bytes = data.dup.force_encoding('BINARY')
121
184
  end
122
185
 
123
186
  def self.read_keyblob(buffer)
@@ -125,7 +188,7 @@ module Net
125
188
  end
126
189
 
127
190
  def to_blob
128
- Net::SSH::Buffer.from(:mstring, "ssh-ed25519".dup, :string, @verify_key.to_bytes).to_s
191
+ Net::SSH::Buffer.from(:mstring, "ssh-ed25519".dup, :string, public_key_bytes).to_s
129
192
  end
130
193
 
131
194
  def ssh_type
@@ -137,22 +200,26 @@ module Net
137
200
  end
138
201
 
139
202
  def ssh_do_verify(sig, data, options = {})
140
- @verify_key.verify(sig, data)
203
+ ED25519.verify(verify_key, sig, data)
141
204
  end
142
205
 
143
206
  def to_pem
144
207
  # TODO this is not pem
145
- ssh_type + [@verify_key.to_bytes].pack("m")
208
+ ssh_type + [public_key_bytes].pack("m")
209
+ end
210
+
211
+ def public_key_bytes
212
+ @public_key_bytes.dup
213
+ end
214
+
215
+ def verify_key
216
+ @verify_key ||= ED25519.new_public_key(@public_key_bytes)
146
217
  end
147
218
  end
148
219
 
149
220
  class PrivKey
150
221
  CipherFactory = Net::SSH::Transport::CipherFactory
151
222
 
152
- MBEGIN = "-----BEGIN OPENSSH PRIVATE KEY-----\n"
153
- MEND = "-----END OPENSSH PRIVATE KEY-----\n"
154
- MAGIC = "openssh-key-v1"
155
-
156
223
  attr_reader :sign_key
157
224
 
158
225
  def initialize(buffer)
@@ -160,8 +227,17 @@ module Net
160
227
  sk = buffer.read_string
161
228
  _comment = buffer.read_string
162
229
 
163
- @pk = pk
164
- @sign_key = SigningKeyFromFile.new(pk, sk)
230
+ ED25519.validate_key_bytes!(pk, "public key", KEY_BYTES)
231
+ ED25519.validate_key_bytes!(sk, "private key field", PRIVATE_KEY_BYTES)
232
+
233
+ private_key_bytes = sk[0, KEY_BYTES]
234
+ raise ArgumentError, "Ed25519 private key does not include matching public key" unless sk[KEY_BYTES, KEY_BYTES] == pk
235
+
236
+ @sign_key = ED25519.new_private_key(private_key_bytes)
237
+ raise ArgumentError, "Ed25519 public key does not match private key" unless @sign_key.raw_public_key == pk
238
+
239
+ @public_key_bytes = pk.dup.force_encoding('BINARY')
240
+ @private_key_bytes = private_key_bytes.dup.force_encoding('BINARY')
165
241
  end
166
242
 
167
243
  def to_blob
@@ -177,11 +253,23 @@ module Net
177
253
  end
178
254
 
179
255
  def public_key
180
- PubKey.new(@pk)
256
+ PubKey.new(public_key_bytes)
181
257
  end
182
258
 
183
259
  def ssh_do_sign(data, sig_alg = nil)
184
- @sign_key.sign(data)
260
+ ED25519.sign(@sign_key, data)
261
+ end
262
+
263
+ def public_key_bytes
264
+ @public_key_bytes.dup
265
+ end
266
+
267
+ def private_key_bytes
268
+ @private_key_bytes.dup
269
+ end
270
+
271
+ def private_key_bytes_for_agent
272
+ private_key_bytes + public_key_bytes
185
273
  end
186
274
 
187
275
  def self.read(data, password)
@@ -1,29 +1,34 @@
1
1
  module Net
2
2
  module SSH
3
3
  module Authentication
4
- # Loads ED25519 support which requires optinal dependecies like
5
- # ed25519, bcrypt_pbkdf
4
+ # Loads ED25519 support backed by Ruby OpenSSL raw key APIs.
6
5
  module ED25519Loader
7
6
  begin
8
7
  require 'net/ssh/authentication/ed25519'
8
+ Net::SSH::Authentication::ED25519.ensure_supported!
9
9
  LOADED = true
10
10
  ERROR = nil
11
11
  rescue LoadError => e
12
12
  ERROR = e
13
13
  LOADED = false
14
+ rescue StandardError => e
15
+ if defined?(Net::SSH::Authentication::ED25519::UnsupportedError) &&
16
+ e.is_a?(Net::SSH::Authentication::ED25519::UnsupportedError)
17
+ ERROR = e
18
+ LOADED = false
19
+ else
20
+ raise
21
+ end
14
22
  end
15
23
 
16
24
  def self.raiseUnlessLoaded(message)
17
- description = ERROR.is_a?(LoadError) ? dependenciesRequiredForED25519 : +''
18
- description << "#{ERROR.class} : \"#{ERROR.message}\"\n" if ERROR
25
+ description = LOADED ? '' : ed25519SupportRequired
26
+ description += "#{ERROR.class} : \"#{ERROR.message}\"\n" if ERROR
19
27
  raise NotImplementedError, "#{message}\n#{description}" unless LOADED
20
28
  end
21
29
 
22
- def self.dependenciesRequiredForED25519
23
- result = +"net-ssh requires the following gems for ed25519 support:\n"
24
- result << " * ed25519 (>= 1.2, < 2.0)\n"
25
- result << " * bcrypt_pbkdf (>= 1.0, < 2.0)\n" unless RUBY_PLATFORM == "java"
26
- result << "See https://github.com/net-ssh/net-ssh/issues/565 for more information\n"
30
+ def self.ed25519SupportRequired
31
+ "net-ssh requires openssl >= 3.2.0 and a crypto backend with Ed25519 enabled for ed25519 support.\n"
27
32
  end
28
33
  end
29
34
  end
@@ -55,7 +55,7 @@ module Net
55
55
  prompter = nil
56
56
  result =
57
57
  begin
58
- key_type.read(data, passphrase)
58
+ key_type.read(data, passphrase || 'invalid')
59
59
  rescue *key_type.error_classes => e
60
60
  encrypted_key = !!key_type.encrypted_key?(data, e) if encrypted_key.nil?
61
61
  if encrypted_key && ask_passphrase
@@ -138,9 +138,8 @@ module Net
138
138
  raise Exception, "TODO: subclasses should implement"
139
139
  end
140
140
 
141
- # A nil passphrase would make OpenSSL prompt on the terminal itself.
142
141
  def self.read(key_data, passphrase)
143
- open_ssl_class.new(key_data, passphrase || 'invalid')
142
+ open_ssl_class.new(key_data, passphrase)
144
143
  end
145
144
 
146
145
  def self.encrypted_key?(key_data, error)
@@ -150,7 +149,7 @@ module Net
150
149
 
151
150
  class OpenSSLPKeyType < OpenSSLKeyTypeBase
152
151
  def self.read(key_data, passphrase)
153
- open_ssl_class.read(key_data, passphrase || 'invalid')
152
+ open_ssl_class.read(key_data, passphrase)
154
153
  end
155
154
 
156
155
  def self.open_ssl_class