net-ssh 7.3.4 → 8.0.0.rc1
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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.github/workflows/ci-with-docker.yml +0 -3
- data/.github/workflows/ci.yml +2 -17
- data/.rubocop.yml +1 -1
- data/.rubocop_todo.yml +1 -0
- data/CHANGES.txt +20 -2
- data/Dockerfile +4 -1
- data/Dockerfile.openssl3 +1 -1
- data/Dockerfile.zlib_ng +1 -1
- data/README.md +10 -13
- data/Rakefile +18 -12
- data/appveyor.yml +4 -16
- data/docker-compose.yml +1 -7
- data/lib/net/ssh/authentication/agent.rb +4 -4
- data/lib/net/ssh/authentication/ed25519.rb +134 -36
- data/lib/net/ssh/authentication/ed25519_loader.rb +14 -9
- data/lib/net/ssh/authentication/key_manager.rb +3 -2
- data/lib/net/ssh/prompt.rb +3 -3
- data/lib/net/ssh/service/forward.rb +3 -3
- data/lib/net/ssh/transport/algorithms.rb +17 -10
- data/lib/net/ssh/transport/chacha20_poly1305_cipher.rb +89 -16
- data/lib/net/ssh/transport/chacha20_poly1305_cipher_loader.rb +20 -1
- data/lib/net/ssh/transport/cipher_factory.rb +23 -1
- data/lib/net/ssh/transport/constants.rb +1 -0
- data/lib/net/ssh/transport/gcm_cipher.rb +27 -6
- data/lib/net/ssh/transport/kex/curve25519_sha256.rb +41 -9
- data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +14 -6
- data/lib/net/ssh/transport/session.rb +22 -2
- data/lib/net/ssh/version.rb +4 -4
- data/lib/net/ssh.rb +7 -1
- data/net-ssh.gemspec +4 -7
- data.tar.gz.sig +0 -0
- metadata +17 -33
- metadata.gz.sig +0 -0
- data/Gemfile.noed25519 +0 -12
- data/Gemfile.norbnacl +0 -12
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: aad28c615382be2934f9a43923ede5e5eed71383f3af54a0440ebf94ad40dc1a
|
|
4
|
+
data.tar.gz: 938c219b1073d44e27de4b5d4c95c01df2903d52f6b737454fb504d878ee6592
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a9663038da70081bafeaa5a9bfabc0241f7c249386fc6f475f09611d697c6db00161624777ec92abe0ae8f301f4ba20b2ba3ea869766ebccd6c11648acf5834b
|
|
7
|
+
data.tar.gz: d8eb01f0e3aad7af012363f211873842c9b80e382d304fb0c3faef045d3eea4ec389ec6aa54be49c600a70bacb566c23b18f0aa0d2d6decf5631169bec01aa59
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -7,7 +7,7 @@ jobs:
|
|
|
7
7
|
runs-on: ubuntu-22.04
|
|
8
8
|
strategy:
|
|
9
9
|
matrix:
|
|
10
|
-
ruby-version: [2.
|
|
10
|
+
ruby-version: [2.7.8, 3.0.6, 3.1.3, 3.2.1, 3.3, 3.4, '4.0']
|
|
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 ${{
|
|
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
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,6 +1,24 @@
|
|
|
1
|
-
===
|
|
1
|
+
=== 8.0.0 rc1
|
|
2
2
|
|
|
3
|
-
*
|
|
3
|
+
* BREAKING: TCP_NODELAY is now set on the connection socket by default, as OpenSSH does; pass `no_delay: false` to keep Nagle's algorithm on [#1017]
|
|
4
|
+
* Ruby 4.0 support: add `logger` as a runtime dependency; Windows Pageant users on Ruby 4.0 need `fiddle` in their Gemfile [#984, #1012]
|
|
5
|
+
* Close the socket when connection setup fails, e.g. on a server version timeout [#1013]
|
|
6
|
+
* Ignore SSH_MSG_EXT_INFO from servers that send it unrequested [#1020]
|
|
7
|
+
* Fix `undefined method 'to_pem'` for ECDSA key_data when an agent is running [#982]
|
|
8
|
+
* Agent forwarding connects to the agent set by `identity_agent`/IdentityAgent [#974]
|
|
9
|
+
* Keyboard-interactive prompts show input when the server asks for it to be echoed [#1014]
|
|
10
|
+
* Raise Net::SSH::Exception on a truncated KEXINIT [#1016]
|
|
11
|
+
* Remote forwarding no longer prints diagnostics to stdout [#953]
|
|
12
|
+
|
|
13
|
+
=== 8.0.0 beta3
|
|
14
|
+
|
|
15
|
+
* 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]
|
|
16
|
+
|
|
17
|
+
=== 8.0.0 beta1
|
|
18
|
+
|
|
19
|
+
* BREAKING: require Ruby 2.7 and the openssl gem >= 3.2 [#1001]
|
|
20
|
+
* Use OpenSSL for Ed25519, X25519 and ChaCha20-Poly1305; the ed25519, x25519 and rbnacl gems are no longer needed [#1001]
|
|
21
|
+
* Support AEAD-encrypted OpenSSH private keys [#1001]
|
|
4
22
|
|
|
5
23
|
=== 7.3.3
|
|
6
24
|
|
data/Dockerfile
CHANGED
|
@@ -3,6 +3,9 @@ FROM ruby:${RUBY_VERSION}
|
|
|
3
3
|
|
|
4
4
|
ARG BUNDLERV=
|
|
5
5
|
|
|
6
|
+
# Ruby 2.7 and 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|' /etc/apt/sources.list; fi
|
|
8
|
+
|
|
6
9
|
RUN apt update && apt install -y openssh-server sudo netcat-openbsd \
|
|
7
10
|
&& useradd --create-home --shell '/bin/bash' --comment 'NetSSH' 'net_ssh_1' \
|
|
8
11
|
&& useradd --create-home --shell '/bin/bash' --comment 'NetSSH' 'net_ssh_2' \
|
|
@@ -26,4 +29,4 @@ RUN gem install bundler ${BUNDLERV} && bundle install
|
|
|
26
29
|
|
|
27
30
|
COPY . $INSTALL_PATH/
|
|
28
31
|
|
|
29
|
-
CMD service ssh start && rake test
|
|
32
|
+
CMD service ssh start && rake test
|
data/Dockerfile.openssl3
CHANGED
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 zlib-ng crypto-policies-scripts && \
|
|
14
|
+
RUN dnf install -y openssl openssl-devel ruby ruby-devel git make gcc redhat-rpm-config 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 |
|
|
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 |
|
|
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. |
|
|
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
|
-
|
|
152
|
-
|
|
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
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
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/Rakefile
CHANGED
|
@@ -9,7 +9,11 @@ require "rake"
|
|
|
9
9
|
require "rake/clean"
|
|
10
10
|
require "bundler/gem_tasks"
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
begin
|
|
13
|
+
require "rdoc/task"
|
|
14
|
+
rescue LoadError
|
|
15
|
+
# rdoc is no longer a default gem as of Ruby 4.0; the rest of the Rakefile works without it.
|
|
16
|
+
end
|
|
13
17
|
|
|
14
18
|
desc "When releasing make sure NET_SSH_BUILDGEM_SIGNED is set"
|
|
15
19
|
task :check_NET_SSH_BUILDGEM_SIGNED do
|
|
@@ -27,17 +31,19 @@ require_relative "lib/net/ssh/version"
|
|
|
27
31
|
version = Net::SSH::Version::CURRENT
|
|
28
32
|
|
|
29
33
|
extra_files = %w[LICENSE.txt THANKS.txt CHANGES.txt]
|
|
30
|
-
RDoc::Task
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
if defined?(RDoc::Task)
|
|
35
|
+
RDoc::Task.new do |rdoc|
|
|
36
|
+
rdoc.rdoc_dir = "rdoc"
|
|
37
|
+
rdoc.title = "#{name} #{version}"
|
|
38
|
+
rdoc.generator = 'hanna' # gem install hanna-nouveau
|
|
39
|
+
rdoc.main = 'README.md'
|
|
40
|
+
rdoc.rdoc_files.include("README*")
|
|
41
|
+
rdoc.rdoc_files.include("bin/*.rb")
|
|
42
|
+
rdoc.rdoc_files.include("lib/**/*.rb")
|
|
43
|
+
extra_files.each { |file|
|
|
44
|
+
rdoc.rdoc_files.include(file) if File.exist?(file)
|
|
45
|
+
}
|
|
46
|
+
end
|
|
41
47
|
end
|
|
42
48
|
|
|
43
49
|
namespace :cert do
|
data/appveyor.yml
CHANGED
|
@@ -4,16 +4,9 @@ skip_tags: true
|
|
|
4
4
|
|
|
5
5
|
environment:
|
|
6
6
|
matrix:
|
|
7
|
-
- ruby_version: "
|
|
8
|
-
- ruby_version: "
|
|
9
|
-
- ruby_version: "
|
|
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
|
|
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
|
@@ -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.
|
|
265
|
-
:string, priv_key.
|
|
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.
|
|
270
|
-
:string, priv_key.key.
|
|
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
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
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
|
|
21
26
|
end
|
|
22
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
|
|
48
|
+
end
|
|
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
|
|
|
@@ -47,7 +79,7 @@ module Net
|
|
|
47
79
|
data = datab64.unpack1("m")
|
|
48
80
|
raise ArgumentError.new("Expected #{MAGIC} at start of decoded private key") unless data.start_with?(MAGIC)
|
|
49
81
|
|
|
50
|
-
buffer = Net::SSH::Buffer.new(data[MAGIC.size + 1..-1])
|
|
82
|
+
buffer = Net::SSH::Buffer.new(data[(MAGIC.size + 1)..-1])
|
|
51
83
|
|
|
52
84
|
ciphername = buffer.read_string
|
|
53
85
|
raise ArgumentError.new("#{ciphername} in private key is not supported") unless
|
|
@@ -63,27 +95,34 @@ module Net
|
|
|
63
95
|
_pubkey = buffer.read_string
|
|
64
96
|
|
|
65
97
|
len = buffer.read_long
|
|
98
|
+
encrypted_private = buffer.read(len)
|
|
66
99
|
|
|
67
100
|
keylen, blocksize, ivlen = CipherFactory.get_lengths(ciphername, iv_len: true)
|
|
68
101
|
raise ArgumentError.new("Private key len:#{len} is not a multiple of #{blocksize}") if
|
|
69
|
-
(
|
|
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
|
|
70
108
|
|
|
71
109
|
if kdfname == 'bcrypt'
|
|
72
110
|
salt = kdfopts.read_string
|
|
73
111
|
rounds = kdfopts.read_long
|
|
74
112
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
key = BCryptPbkdf::key(password, salt, keylen + ivlen, rounds)
|
|
78
|
-
raise DecryptError.new("BCyryptPbkdf failed", encrypted_key: true) unless key
|
|
113
|
+
key = bcrypt_pbkdf_key(password, salt, keylen + ivlen, rounds)
|
|
79
114
|
else
|
|
80
|
-
key =
|
|
115
|
+
key = "\x00" * (keylen + ivlen)
|
|
81
116
|
end
|
|
82
117
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
+
)
|
|
87
126
|
|
|
88
127
|
decoded = Net::SSH::Buffer.new(decoded)
|
|
89
128
|
check1 = decoded.read_long
|
|
@@ -99,15 +138,49 @@ module Net
|
|
|
99
138
|
decoded.read_private_keyblob(type_name)
|
|
100
139
|
end
|
|
101
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
|
|
102
176
|
end
|
|
103
177
|
|
|
104
178
|
class PubKey
|
|
105
179
|
include Net::SSH::Authentication::PubKeyFingerprint
|
|
106
180
|
|
|
107
|
-
attr_reader :verify_key
|
|
108
|
-
|
|
109
181
|
def initialize(data)
|
|
110
|
-
|
|
182
|
+
ED25519.validate_key_bytes!(data, "public key", KEY_BYTES)
|
|
183
|
+
@public_key_bytes = data.dup.force_encoding('BINARY')
|
|
111
184
|
end
|
|
112
185
|
|
|
113
186
|
def self.read_keyblob(buffer)
|
|
@@ -115,7 +188,7 @@ module Net
|
|
|
115
188
|
end
|
|
116
189
|
|
|
117
190
|
def to_blob
|
|
118
|
-
Net::SSH::Buffer.from(:mstring, "ssh-ed25519".dup, :string,
|
|
191
|
+
Net::SSH::Buffer.from(:mstring, "ssh-ed25519".dup, :string, public_key_bytes).to_s
|
|
119
192
|
end
|
|
120
193
|
|
|
121
194
|
def ssh_type
|
|
@@ -127,22 +200,26 @@ module Net
|
|
|
127
200
|
end
|
|
128
201
|
|
|
129
202
|
def ssh_do_verify(sig, data, options = {})
|
|
130
|
-
|
|
203
|
+
ED25519.verify(verify_key, sig, data)
|
|
131
204
|
end
|
|
132
205
|
|
|
133
206
|
def to_pem
|
|
134
207
|
# TODO this is not pem
|
|
135
|
-
ssh_type + [
|
|
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)
|
|
136
217
|
end
|
|
137
218
|
end
|
|
138
219
|
|
|
139
220
|
class PrivKey
|
|
140
221
|
CipherFactory = Net::SSH::Transport::CipherFactory
|
|
141
222
|
|
|
142
|
-
MBEGIN = "-----BEGIN OPENSSH PRIVATE KEY-----\n"
|
|
143
|
-
MEND = "-----END OPENSSH PRIVATE KEY-----\n"
|
|
144
|
-
MAGIC = "openssh-key-v1"
|
|
145
|
-
|
|
146
223
|
attr_reader :sign_key
|
|
147
224
|
|
|
148
225
|
def initialize(buffer)
|
|
@@ -150,8 +227,17 @@ module Net
|
|
|
150
227
|
sk = buffer.read_string
|
|
151
228
|
_comment = buffer.read_string
|
|
152
229
|
|
|
153
|
-
|
|
154
|
-
|
|
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')
|
|
155
241
|
end
|
|
156
242
|
|
|
157
243
|
def to_blob
|
|
@@ -167,11 +253,23 @@ module Net
|
|
|
167
253
|
end
|
|
168
254
|
|
|
169
255
|
def public_key
|
|
170
|
-
PubKey.new(
|
|
256
|
+
PubKey.new(public_key_bytes)
|
|
171
257
|
end
|
|
172
258
|
|
|
173
259
|
def ssh_do_sign(data, sig_alg = nil)
|
|
174
|
-
|
|
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
|
|
175
273
|
end
|
|
176
274
|
|
|
177
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
|
|
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 =
|
|
18
|
-
description
|
|
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.
|
|
23
|
-
|
|
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
|