openssl-ssh 0.1.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
- SHA1:
3
- metadata.gz: 201e86275b09b7955f5e65c8e20b77d3b824be8e
4
- data.tar.gz: 4d04006494ec4c1f37f32647d0b19adb7a0484c8
2
+ SHA256:
3
+ metadata.gz: 37a68dc478799667fdf5e6197816d68270485d49cdb3b5a55d4eafd5a6f26fe2
4
+ data.tar.gz: a735beabe82287963121cfd4e8aae2791072c7e49eb4ee5729746888d3d4900c
5
5
  SHA512:
6
- metadata.gz: f6a38328c4d02ce9bc2a9345c8b7526a49ca142acd28fe44dceb3ee957f5424607253917a0f61133cd328ace34b611e280f3b5603a5edc255d42dee276a85edc
7
- data.tar.gz: 2f835b87e85e520d6c9456f5c1e8e439e48c9e0e6f2788aef58fad9ec539e674058c11521c113891c9713ca00fbc27f5cadbd560d7b3f5950e7d46b0f146da72
6
+ metadata.gz: 7bea96bc4ed7eb74f70f4954ce92f56e7fae212a0b5a1a1eb0cad360a01df9102699335e461c2159699f414949f8680a458b5883335ab8833e38a77e88f2279f
7
+ data.tar.gz: 0fd6c0455b634116d8d82bf1f1af28db706c4325c24cf43d8a5b0482c567526bd941292f4693852a8f175b70514415ae9866aef94acfc0b5215db9b3dd36f877
@@ -0,0 +1,44 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [master, main]
6
+ pull_request:
7
+ branches: [master, main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ ruby-version: ['3.0', '3.1', '3.2', '3.3']
16
+
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+
20
+ - name: Set up Ruby ${{ matrix.ruby-version }}
21
+ uses: ruby/setup-ruby@v1
22
+ with:
23
+ ruby-version: ${{ matrix.ruby-version }}
24
+ bundler-cache: true
25
+
26
+ - name: Run tests
27
+ run: bundle exec rspec
28
+
29
+ lint:
30
+ runs-on: ubuntu-latest
31
+ steps:
32
+ - uses: actions/checkout@v4
33
+
34
+ - name: Set up Ruby
35
+ uses: ruby/setup-ruby@v1
36
+ with:
37
+ ruby-version: '3.3'
38
+ bundler-cache: true
39
+
40
+ - name: Run Standard RB
41
+ run: bundle exec standardrb --no-fix
42
+
43
+ - name: Check gem builds
44
+ run: gem build openssl-ssh.gemspec
@@ -0,0 +1,52 @@
1
+ name: Publish Gem
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+
8
+ permissions:
9
+ contents: write
10
+ id-token: write
11
+
12
+ jobs:
13
+ publish:
14
+ runs-on: ubuntu-latest
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Set up Ruby
20
+ uses: ruby/setup-ruby@v1
21
+ with:
22
+ ruby-version: '3.3'
23
+
24
+ - name: Extract version from tag
25
+ id: version
26
+ run: |
27
+ TAG=${GITHUB_REF#refs/tags/v}
28
+ echo "version=$TAG" >> $GITHUB_OUTPUT
29
+ if [[ "$TAG" =~ -pre[0-9]*$ ]] || [[ "$TAG" =~ -alpha[0-9]*$ ]] || [[ "$TAG" =~ -beta[0-9]*$ ]] || [[ "$TAG" =~ -rc[0-9]*$ ]] || [[ "$TAG" =~ -a[0-9]*$ ]]; then
30
+ echo "prerelease=true" >> $GITHUB_OUTPUT
31
+ else
32
+ echo "prerelease=false" >> $GITHUB_OUTPUT
33
+ fi
34
+
35
+ - name: Build gem
36
+ id: build
37
+ run: |
38
+ gem build *.gemspec
39
+ echo "gem_file=$(ls *.gem)" >> $GITHUB_OUTPUT
40
+
41
+ - name: Configure RubyGems credentials
42
+ uses: rubygems/configure-rubygems-credentials@v1.0.0
43
+
44
+ - name: Publish to RubyGems
45
+ run: gem push ${{ steps.build.outputs.gem_file }}
46
+
47
+ - name: Create GitHub Release
48
+ uses: softprops/action-gh-release@v2
49
+ with:
50
+ files: ${{ steps.build.outputs.gem_file }}
51
+ prerelease: ${{ steps.version.outputs.prerelease }}
52
+ generate_release_notes: true
data/.gitignore CHANGED
@@ -2,7 +2,6 @@
2
2
  /.yardoc
3
3
  /_yardoc/
4
4
  /coverage/
5
- /doc/
6
5
  /pkg/
7
6
  /spec/reports/
8
7
  /tmp/
data/.standard.yml ADDED
@@ -0,0 +1,8 @@
1
+ # Standard RB configuration
2
+ # https://github.com/standardrb/standard
3
+
4
+ ruby_version: 3.0
5
+
6
+ ignore:
7
+ - "vendor/**/*"
8
+ - "coverage/**/*"
data/.tool-versions ADDED
@@ -0,0 +1 @@
1
+ ruby 3.3.8
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source 'https://rubygems.org'
1
+ source "https://rubygems.org"
2
2
 
3
3
  git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
4
4
 
data/Gemfile.lock CHANGED
@@ -1,43 +1,93 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- openssl-ssh (0.1.0)
4
+ openssl-ssh (0.2.1)
5
+ base64
5
6
 
6
7
  GEM
7
8
  remote: https://rubygems.org/
8
9
  specs:
9
- diff-lcs (1.3)
10
- docile (1.3.1)
11
- json (2.1.0)
12
- rake (12.3.1)
13
- rspec (3.8.0)
14
- rspec-core (~> 3.8.0)
15
- rspec-expectations (~> 3.8.0)
16
- rspec-mocks (~> 3.8.0)
17
- rspec-core (3.8.0)
18
- rspec-support (~> 3.8.0)
19
- rspec-expectations (3.8.2)
10
+ ast (2.4.3)
11
+ base64 (0.3.0)
12
+ diff-lcs (1.6.2)
13
+ docile (1.4.1)
14
+ json (2.18.1)
15
+ language_server-protocol (3.17.0.5)
16
+ lint_roller (1.1.0)
17
+ parallel (1.27.0)
18
+ parser (3.3.10.1)
19
+ ast (~> 2.4.1)
20
+ racc
21
+ prism (1.9.0)
22
+ racc (1.8.1)
23
+ rainbow (3.1.1)
24
+ rake (13.3.1)
25
+ regexp_parser (2.11.3)
26
+ rspec (3.13.2)
27
+ rspec-core (~> 3.13.0)
28
+ rspec-expectations (~> 3.13.0)
29
+ rspec-mocks (~> 3.13.0)
30
+ rspec-core (3.13.6)
31
+ rspec-support (~> 3.13.0)
32
+ rspec-expectations (3.13.5)
20
33
  diff-lcs (>= 1.2.0, < 2.0)
21
- rspec-support (~> 3.8.0)
22
- rspec-mocks (3.8.0)
34
+ rspec-support (~> 3.13.0)
35
+ rspec-mocks (3.13.7)
23
36
  diff-lcs (>= 1.2.0, < 2.0)
24
- rspec-support (~> 3.8.0)
25
- rspec-support (3.8.0)
26
- simplecov (0.16.1)
37
+ rspec-support (~> 3.13.0)
38
+ rspec-support (3.13.7)
39
+ rubocop (1.82.1)
40
+ json (~> 2.3)
41
+ language_server-protocol (~> 3.17.0.2)
42
+ lint_roller (~> 1.1.0)
43
+ parallel (~> 1.10)
44
+ parser (>= 3.3.0.2)
45
+ rainbow (>= 2.2.2, < 4.0)
46
+ regexp_parser (>= 2.9.3, < 3.0)
47
+ rubocop-ast (>= 1.48.0, < 2.0)
48
+ ruby-progressbar (~> 1.7)
49
+ unicode-display_width (>= 2.4.0, < 4.0)
50
+ rubocop-ast (1.49.0)
51
+ parser (>= 3.3.7.2)
52
+ prism (~> 1.7)
53
+ rubocop-performance (1.26.1)
54
+ lint_roller (~> 1.1)
55
+ rubocop (>= 1.75.0, < 2.0)
56
+ rubocop-ast (>= 1.47.1, < 2.0)
57
+ ruby-progressbar (1.13.0)
58
+ simplecov (0.22.0)
27
59
  docile (~> 1.1)
28
- json (>= 1.8, < 3)
29
- simplecov-html (~> 0.10.0)
30
- simplecov-html (0.10.2)
60
+ simplecov-html (~> 0.11)
61
+ simplecov_json_formatter (~> 0.1)
62
+ simplecov-html (0.13.2)
63
+ simplecov_json_formatter (0.1.4)
64
+ standard (1.53.0)
65
+ language_server-protocol (~> 3.17.0.2)
66
+ lint_roller (~> 1.0)
67
+ rubocop (~> 1.82.0)
68
+ standard-custom (~> 1.0.0)
69
+ standard-performance (~> 1.8)
70
+ standard-custom (1.0.2)
71
+ lint_roller (~> 1.0)
72
+ rubocop (~> 1.50)
73
+ standard-performance (1.9.0)
74
+ lint_roller (~> 1.1)
75
+ rubocop-performance (~> 1.26.0)
76
+ unicode-display_width (3.2.0)
77
+ unicode-emoji (~> 4.1)
78
+ unicode-emoji (4.2.0)
31
79
 
32
80
  PLATFORMS
81
+ arm64-darwin-25
33
82
  ruby
34
83
 
35
84
  DEPENDENCIES
36
- bundler (~> 1.16)
85
+ bundler (~> 2.5)
37
86
  openssl-ssh!
38
- rake (~> 12.3)
39
- rspec (~> 3.8)
40
- simplecov (~> 0.16)
87
+ rake (~> 13.2)
88
+ rspec (~> 3.13)
89
+ simplecov (~> 0.22)
90
+ standard (~> 1.53)
41
91
 
42
92
  BUNDLED WITH
43
- 1.16.6
93
+ 2.7.2
data/README.md CHANGED
@@ -1,7 +1,9 @@
1
+ # ![OpenSSL](https://raw.githubusercontent.com/aladac/openssl-ssh/master/doc/openssl.png) + ![OpenSSH](https://raw.githubusercontent.com/aladac/openssl-ssh/master/doc/openssh.gif) 4 ![Ruby](https://raw.githubusercontent.com/aladac/openssl-ssh/master/doc/ruby.png)
2
+
1
3
  [![Maintainability](https://api.codeclimate.com/v1/badges/76e285ab4467fa52bc70/maintainability)](https://codeclimate.com/github/aladac/openssl-ssh/maintainability)
2
4
  [![Test Coverage](https://api.codeclimate.com/v1/badges/76e285ab4467fa52bc70/test_coverage)](https://codeclimate.com/github/aladac/openssl-ssh/test_coverage)
3
5
 
4
- # OpenSSL::SSH
6
+ # OpenSSL::PKey::SSH
5
7
 
6
8
  This gem introduces `OpenSSL::PKey::SSH` class with the ability to parse **OpenSSH** format public keys and return a correct `OpenSSL::PKey` type object. This is a convenience class used to parse the specific format of the **OpenSSH** public key.
7
9
 
data/Rakefile CHANGED
@@ -1,6 +1,9 @@
1
- require 'bundler/gem_tasks'
2
- require 'rspec/core/rake_task'
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+ require "standard/rake"
3
6
 
4
7
  RSpec::Core::RakeTask.new(:spec)
5
8
 
6
- task default: :spec
9
+ task default: %i[spec standard]
data/TODO.md ADDED
@@ -0,0 +1,56 @@
1
+ # OpenSSL-SSH - Modernization for Ruby 3.x / OpenSSL 3.x
2
+
3
+ Parse OpenSSH format public keys (`ssh-rsa AAAA...`) and return `OpenSSL::PKey` objects.
4
+
5
+ ## Completed
6
+
7
+ ### Phase 1: Fix Core
8
+ - [x] Replace property assignment with ASN.1 DER construction
9
+ - [x] Update `build_rsa` method to build ASN.1 sequence
10
+ - [x] Update `build_dsa` method to use SubjectPublicKeyInfo format
11
+ - [x] Test with Ruby 3.x / OpenSSL 3.x
12
+ - [x] Update specs
13
+
14
+ ### Phase 2: Add Key Types
15
+ - [x] Ed25519 support (default for modern OpenSSH)
16
+ ```ruby
17
+ OpenSSL::PKey.new_raw_public_key('ED25519', raw_key_bytes)
18
+ ```
19
+ - [x] ECDSA support (ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, ecdsa-sha2-nistp521)
20
+ - [x] Keep RSA and DSA support
21
+
22
+ ### Phase 3: Modernize
23
+ - [x] Update gemspec dependencies
24
+ - bundler ~> 2.0
25
+ - rake ~> 13.0
26
+ - rspec ~> 3.12
27
+ - simplecov ~> 0.22
28
+ - base64 (runtime dependency for Ruby 3.4+)
29
+ - [x] Add required Ruby version (>= 3.0)
30
+ - [x] Add GitHub Actions workflow
31
+ - [x] Update metadata URIs
32
+
33
+ ## Future Improvements
34
+
35
+ ### Phase 4: OpenSSH Private Key Format
36
+ - [ ] Support OpenSSH private key format (new format, not just PEM)
37
+ - Ed25519 and ECDSA private keys use new format by default
38
+ - Requires custom parser for `-----BEGIN OPENSSH PRIVATE KEY-----`
39
+ - [ ] Add fingerprint generation (MD5, SHA256)
40
+ - [ ] Add key comment extraction
41
+ - [ ] Better error messages
42
+
43
+ ## Supported Key Types
44
+
45
+ | Type | Public Key | Private Key (PEM) | Private Key (OpenSSH) |
46
+ |------|------------|-------------------|----------------------|
47
+ | RSA | ✅ | ✅ | ❌ |
48
+ | DSA | ✅ | ✅ | ❌ |
49
+ | Ed25519 | ✅ | N/A | ❌ (pending) |
50
+ | ECDSA | ✅ | ✅ | ❌ (pending) |
51
+
52
+ ## References
53
+
54
+ - [OpenSSL::PKey docs](https://ruby-doc.org/stdlib/libdoc/openssl/rdoc/OpenSSL/PKey.html)
55
+ - [RFC 4253 - SSH Transport](https://tools.ietf.org/html/rfc4253#section-6.6) - Key format
56
+ - [RFC 8709 - Ed25519 for SSH](https://tools.ietf.org/html/rfc8709)
data/doc/openssh.gif ADDED
Binary file
data/doc/openssl.png ADDED
Binary file
data/doc/ruby.png ADDED
Binary file
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module OpenSSL
2
4
  module SSH
3
- VERSION = '0.1.0'.freeze
5
+ VERSION = "0.2.1"
4
6
  end
5
7
  end
data/lib/openssl/ssh.rb CHANGED
@@ -1,12 +1,26 @@
1
- require 'openssl/ssh/version'
2
- require 'base64'
3
- require 'openssl'
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl/ssh/version"
4
+ require "base64"
5
+ require "openssl"
4
6
 
5
7
  module OpenSSL
6
8
  module PKey
7
9
  class SSH
8
- RSA_COMPONENTS = ['ssh-rsa', :e, :n].freeze
9
- DSA_COMPONENTS = ['ssh-dss', :p, :q, :g, :pub_key].freeze
10
+ SUPPORTED_TYPES = {
11
+ "ssh-rsa" => :rsa,
12
+ "ssh-dss" => :dsa,
13
+ "ssh-ed25519" => :ed25519,
14
+ "ecdsa-sha2-nistp256" => :ecdsa,
15
+ "ecdsa-sha2-nistp384" => :ecdsa,
16
+ "ecdsa-sha2-nistp521" => :ecdsa
17
+ }.freeze
18
+
19
+ ECDSA_CURVES = {
20
+ "ecdsa-sha2-nistp256" => "prime256v1",
21
+ "ecdsa-sha2-nistp384" => "secp384r1",
22
+ "ecdsa-sha2-nistp521" => "secp521r1"
23
+ }.freeze
10
24
 
11
25
  def self.new(key, password = nil)
12
26
  forward_private_key(key, password) || parse_public_ssh_key(key)
@@ -14,63 +28,152 @@ module OpenSSL
14
28
 
15
29
  def self.forward_private_key(key, password)
16
30
  case key
17
- when /BEGIN RSA PRIVATE KEY/
31
+ when /BEGIN RSA PRIVATE KEY/, /BEGIN PRIVATE KEY/
18
32
  OpenSSL::PKey::RSA.new(key, password)
19
33
  when /BEGIN DSA PRIVATE KEY/
20
34
  OpenSSL::PKey::DSA.new(key, password)
35
+ when /BEGIN EC PRIVATE KEY/
36
+ OpenSSL::PKey::EC.new(key, password)
37
+ when /BEGIN OPENSSH PRIVATE KEY/
38
+ parse_openssh_private_key(key, password)
21
39
  end
22
40
  end
23
41
 
42
+ def self.parse_openssh_private_key(key, password)
43
+ # OpenSSH new format private keys - delegate to OpenSSL if supported
44
+ OpenSSL::PKey.read(key, password)
45
+ rescue OpenSSL::PKey::PKeyError
46
+ raise "OpenSSH private key format not supported by your OpenSSL version"
47
+ end
48
+
24
49
  def self.parse_public_ssh_key(key)
25
- if key && key.is_a?(String) && key.match?(/^ssh-/)
26
- key = key.split[1]
27
- key = decode_pubkey(key)
50
+ return key unless key.is_a?(String) && key.match?(/^(ssh-|ecdsa-)/)
51
+
52
+ parts = key.strip.split(" ", 3)
53
+ type = parts[0]
54
+ data = parts[1]
55
+ # comment = parts[2] # Available if needed
56
+
57
+ raise "Unsupported key type: #{type}" unless SUPPORTED_TYPES.key?(type)
58
+
59
+ decoded = Base64.decode64(data)
60
+ components = unpack_pubkey_components(decoded)
61
+
62
+ case SUPPORTED_TYPES[type]
63
+ when :rsa then build_rsa(components)
64
+ when :dsa then build_dsa(components)
65
+ when :ed25519 then build_ed25519(components)
66
+ when :ecdsa then build_ecdsa(components, type)
28
67
  end
29
- key
30
68
  end
31
69
 
32
- def self.decode_pubkey(string)
33
- components = unpack_pubkey_components Base64.decode64(string)
34
- raise "Unsupported key type #{components.first}" unless components.first.match?(/#{RSA_COMPONENTS.first}|#{DSA_COMPONENTS.first}/)
70
+ def self.build_rsa(components)
71
+ _type, e_bytes, n_bytes = components
72
+ e = bytes_to_bn(e_bytes)
73
+ n = bytes_to_bn(n_bytes)
35
74
 
36
- ops, key = process_components(components)
37
- process_ops(key, ops)
75
+ # RSA public key ASN.1 structure:
76
+ # RSAPublicKey ::= SEQUENCE {
77
+ # modulus INTEGER, -- n
78
+ # publicExponent INTEGER -- e
79
+ # }
80
+ asn1 = OpenSSL::ASN1::Sequence.new([
81
+ OpenSSL::ASN1::Integer.new(n),
82
+ OpenSSL::ASN1::Integer.new(e)
83
+ ])
84
+ OpenSSL::PKey::RSA.new(asn1.to_der)
38
85
  end
39
86
 
40
- def self.key_type_components(components)
41
- (components.first.match?(RSA_COMPONENTS.first) ? RSA_COMPONENTS : DSA_COMPONENTS).zip(components)
42
- end
87
+ def self.build_dsa(components)
88
+ _type, p_bytes, q_bytes, g_bytes, pub_key_bytes = components
89
+ p = bytes_to_bn(p_bytes)
90
+ q = bytes_to_bn(q_bytes)
91
+ g = bytes_to_bn(g_bytes)
92
+ pub_key = bytes_to_bn(pub_key_bytes)
43
93
 
44
- def self.key_type_object(components)
45
- components.first.match?(RSA_COMPONENTS.first) ? OpenSSL::PKey::RSA.new : OpenSSL::PKey::DSA.new
46
- end
94
+ # DSA public key requires SubjectPublicKeyInfo format:
95
+ # DSAParameters ::= SEQUENCE {
96
+ # p INTEGER,
97
+ # q INTEGER,
98
+ # g INTEGER
99
+ # }
100
+ # DSAPublicKey ::= INTEGER -- public key y
101
+ #
102
+ # SubjectPublicKeyInfo ::= SEQUENCE {
103
+ # algorithm AlgorithmIdentifier,
104
+ # subjectPublicKey BIT STRING
105
+ # }
106
+ dsa_params = OpenSSL::ASN1::Sequence.new([
107
+ OpenSSL::ASN1::Integer.new(p),
108
+ OpenSSL::ASN1::Integer.new(q),
109
+ OpenSSL::ASN1::Integer.new(g)
110
+ ])
111
+
112
+ # OID for DSA: 1.2.840.10040.4.1
113
+ algo_id = OpenSSL::ASN1::Sequence.new([
114
+ OpenSSL::ASN1::ObjectId.new("DSA"),
115
+ dsa_params
116
+ ])
47
117
 
48
- def self.process_components(components)
49
- [key_type_components(components), key_type_object(components)]
118
+ pub_key_asn1 = OpenSSL::ASN1::Integer.new(pub_key)
119
+ pub_key_bitstring = OpenSSL::ASN1::BitString.new(pub_key_asn1.to_der)
120
+
121
+ spki = OpenSSL::ASN1::Sequence.new([
122
+ algo_id,
123
+ pub_key_bitstring
124
+ ])
125
+
126
+ OpenSSL::PKey::DSA.new(spki.to_der)
50
127
  end
51
128
 
52
- def self.process_ops(key, ops)
53
- ops.each do |o|
54
- next unless o.first.is_a? Symbol
129
+ def self.build_ed25519(components)
130
+ _type, raw_key = components
55
131
 
56
- key.send "#{o.first}=", decode_mpi(o.last)
132
+ # Ed25519 requires Ruby 3.0+ with OpenSSL 1.1.1+
133
+ unless OpenSSL::PKey.respond_to?(:new_raw_public_key)
134
+ raise "Ed25519 requires Ruby 3.0+ with OpenSSL 1.1.1+"
57
135
  end
58
- key
136
+
137
+ OpenSSL::PKey.new_raw_public_key("ED25519", raw_key)
138
+ end
139
+
140
+ def self.build_ecdsa(components, key_type)
141
+ _type, _, point_data = components
142
+ curve = ECDSA_CURVES[key_type]
143
+
144
+ # ECDSA public key in SubjectPublicKeyInfo format
145
+ # The point_data is already in uncompressed point format (04 || x || y)
146
+
147
+ # OID for EC: 1.2.840.10045.2.1
148
+ # Named curve OIDs
149
+ algo_id = OpenSSL::ASN1::Sequence.new([
150
+ OpenSSL::ASN1::ObjectId.new("id-ecPublicKey"),
151
+ OpenSSL::ASN1::ObjectId.new(curve)
152
+ ])
153
+
154
+ pub_key_bitstring = OpenSSL::ASN1::BitString.new(point_data)
155
+
156
+ spki = OpenSSL::ASN1::Sequence.new([
157
+ algo_id,
158
+ pub_key_bitstring
159
+ ])
160
+
161
+ OpenSSL::PKey::EC.new(spki.to_der)
59
162
  end
60
163
 
61
164
  def self.unpack_pubkey_components(str)
62
- cs = []
165
+ components = []
63
166
  i = 0
64
167
  while i < str.length
65
- len = str[i, 4].unpack1('N')
66
- cs << str[i + 4, len]
168
+ len = str[i, 4].unpack1("N")
169
+ components << str[i + 4, len]
67
170
  i += 4 + len
68
171
  end
69
- cs
172
+ components
70
173
  end
71
174
 
72
- def self.decode_mpi(mpi_str)
73
- mpi_str.unpack('C*').inject(0) { |a, e| (a << 8) | e }
175
+ def self.bytes_to_bn(bytes)
176
+ OpenSSL::BN.new(bytes, 2)
74
177
  end
75
178
  end
76
179
  end
data/openssl-ssh.gemspec CHANGED
@@ -1,42 +1,40 @@
1
- lib = File.expand_path('lib', __dir__)
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path("lib", __dir__)
2
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
- require 'openssl/ssh/version'
5
+ require "openssl/ssh/version"
4
6
 
5
7
  Gem::Specification.new do |spec|
6
- spec.name = 'openssl-ssh'
7
- spec.version = OpenSSL::SSH::VERSION
8
- spec.authors = ['Adam Ladachowski']
9
- spec.email = ['adam.ladachowski@gmail.com']
10
-
11
- spec.summary = 'Handling for OpenSSH public keys'
12
- spec.description = 'This gem adds an OpenSSL::PKey::SSH class with the ability to parse OpenSSH and resturn a correct OpenSSL::PKey type class'
13
- spec.homepage = 'https://github.com/aladac/openssl-ssh'
14
- spec.license = 'MIT'
15
-
16
- # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
17
- # to allow pushing to a single host or delete this section to allow pushing to any host.
18
- if spec.respond_to?(:metadata)
19
- # spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
20
-
21
- spec.metadata['homepage_uri'] = spec.homepage
22
- spec.metadata['source_code_uri'] = "TODO: Put your gem's public repo URL here."
23
- spec.metadata['changelog_uri'] = "TODO: Put your gem's CHANGELOG.md URL here."
24
- else
25
- raise 'RubyGems 2.0 or newer is required to protect against ' \
26
- 'public gem pushes.'
27
- end
8
+ spec.name = "openssl-ssh"
9
+ spec.version = OpenSSL::SSH::VERSION
10
+ spec.authors = ["Adam Ladachowski"]
11
+ spec.email = ["adam.ladachowski@gmail.com"]
12
+
13
+ spec.summary = "Handling for OpenSSH public keys"
14
+ spec.description = "Parse OpenSSH format public keys (ssh-rsa, ssh-dss, ssh-ed25519, ecdsa-sha2-*) and return OpenSSL::PKey objects"
15
+ spec.homepage = "https://github.com/aladac/openssl-ssh"
16
+ spec.license = "MIT"
17
+
18
+ spec.required_ruby_version = ">= 3.0"
19
+
20
+ spec.metadata["homepage_uri"] = spec.homepage
21
+ spec.metadata["source_code_uri"] = "https://github.com/aladac/openssl-ssh"
22
+ spec.metadata["changelog_uri"] = "https://github.com/aladac/openssl-ssh/blob/master/CHANGELOG.md"
23
+ spec.metadata["rubygems_mfa_required"] = "true"
28
24
 
29
- # Specify which files should be added to the gem when it is released.
30
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
31
25
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
32
26
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
33
27
  end
34
- spec.bindir = 'exe'
35
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
36
- spec.require_paths = ['lib']
37
-
38
- spec.add_development_dependency 'bundler', '~> 1.16'
39
- spec.add_development_dependency 'rake', '~> 12.3'
40
- spec.add_development_dependency 'rspec', '~> 3.8'
41
- spec.add_development_dependency 'simplecov', '~> 0.16'
28
+ spec.bindir = "exe"
29
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ["lib"]
31
+
32
+ # Runtime dependencies (base64 extracted from stdlib in Ruby 3.4)
33
+ spec.add_dependency "base64"
34
+
35
+ spec.add_development_dependency "bundler", "~> 2.5"
36
+ spec.add_development_dependency "rake", "~> 13.2"
37
+ spec.add_development_dependency "rspec", "~> 3.13"
38
+ spec.add_development_dependency "simplecov", "~> 0.22"
39
+ spec.add_development_dependency "standard", "~> 1.53"
42
40
  end
data/openssl-ssh.md ADDED
@@ -0,0 +1,30 @@
1
+ # OpenSSL-SSH - Ruby Gem Modernization
2
+
3
+ **Repo**: https://github.com/aladac/openssl-ssh
4
+ **Detailed TODO**: See [TODO.md in repo](https://github.com/aladac/openssl-ssh/blob/master/TODO.md)
5
+
6
+ ## Summary
7
+ Ruby gem to parse OpenSSH format keys (`ssh-rsa AAAA...`) into `OpenSSL::PKey` objects.
8
+
9
+ ## Problem
10
+ Current code is **broken** on Ruby 2.4+ / OpenSSL 2.0+. Uses property assignment on immutable objects:
11
+ ```ruby
12
+ key.n = value # ❌ Fails - OpenSSL::PKey is immutable
13
+ ```
14
+
15
+ ## Fix
16
+ Use ASN.1 DER construction:
17
+ ```ruby
18
+ asn1 = OpenSSL::ASN1::Sequence.new([
19
+ OpenSSL::ASN1::Integer.new(n),
20
+ OpenSSL::ASN1::Integer.new(e)
21
+ ])
22
+ key = OpenSSL::PKey::RSA.new(asn1.to_der) # ✅ Works
23
+ ```
24
+
25
+ ## Quick Tasks
26
+ - [ ] Replace property assignment with ASN.1 DER approach
27
+ - [ ] Add Ed25519 support (modern default)
28
+ - [ ] Add ECDSA support
29
+ - [ ] Update gemspec for Ruby 3.x
30
+ - [ ] Add GitHub Actions CI
metadata CHANGED
@@ -1,100 +1,137 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openssl-ssh
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam Ladachowski
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-11-16 00:00:00.000000000 Z
11
+ date: 2026-02-03 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: base64
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: bundler
15
29
  requirement: !ruby/object:Gem::Requirement
16
30
  requirements:
17
31
  - - "~>"
18
32
  - !ruby/object:Gem::Version
19
- version: '1.16'
33
+ version: '2.5'
20
34
  type: :development
21
35
  prerelease: false
22
36
  version_requirements: !ruby/object:Gem::Requirement
23
37
  requirements:
24
38
  - - "~>"
25
39
  - !ruby/object:Gem::Version
26
- version: '1.16'
40
+ version: '2.5'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: rake
29
43
  requirement: !ruby/object:Gem::Requirement
30
44
  requirements:
31
45
  - - "~>"
32
46
  - !ruby/object:Gem::Version
33
- version: '12.3'
47
+ version: '13.2'
34
48
  type: :development
35
49
  prerelease: false
36
50
  version_requirements: !ruby/object:Gem::Requirement
37
51
  requirements:
38
52
  - - "~>"
39
53
  - !ruby/object:Gem::Version
40
- version: '12.3'
54
+ version: '13.2'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: rspec
43
57
  requirement: !ruby/object:Gem::Requirement
44
58
  requirements:
45
59
  - - "~>"
46
60
  - !ruby/object:Gem::Version
47
- version: '3.8'
61
+ version: '3.13'
48
62
  type: :development
49
63
  prerelease: false
50
64
  version_requirements: !ruby/object:Gem::Requirement
51
65
  requirements:
52
66
  - - "~>"
53
67
  - !ruby/object:Gem::Version
54
- version: '3.8'
68
+ version: '3.13'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: simplecov
57
71
  requirement: !ruby/object:Gem::Requirement
58
72
  requirements:
59
73
  - - "~>"
60
74
  - !ruby/object:Gem::Version
61
- version: '0.16'
75
+ version: '0.22'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.22'
83
+ - !ruby/object:Gem::Dependency
84
+ name: standard
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.53'
62
90
  type: :development
63
91
  prerelease: false
64
92
  version_requirements: !ruby/object:Gem::Requirement
65
93
  requirements:
66
94
  - - "~>"
67
95
  - !ruby/object:Gem::Version
68
- version: '0.16'
69
- description: This gem adds an OpenSSL::PKey::SSH class with the ability to parse OpenSSH
70
- and resturn a correct OpenSSL::PKey type class
96
+ version: '1.53'
97
+ description: Parse OpenSSH format public keys (ssh-rsa, ssh-dss, ssh-ed25519, ecdsa-sha2-*)
98
+ and return OpenSSL::PKey objects
71
99
  email:
72
100
  - adam.ladachowski@gmail.com
73
101
  executables: []
74
102
  extensions: []
75
103
  extra_rdoc_files: []
76
104
  files:
105
+ - ".github/workflows/ci.yml"
106
+ - ".github/workflows/publish.yml"
77
107
  - ".gitignore"
78
108
  - ".rspec"
79
- - ".travis.yml"
109
+ - ".standard.yml"
110
+ - ".tool-versions"
80
111
  - Gemfile
81
112
  - Gemfile.lock
82
113
  - LICENSE.txt
83
114
  - README.md
84
115
  - Rakefile
116
+ - TODO.md
85
117
  - bin/console
86
118
  - bin/setup
119
+ - doc/openssh.gif
120
+ - doc/openssl.png
121
+ - doc/ruby.png
87
122
  - lib/openssl/ssh.rb
88
123
  - lib/openssl/ssh/version.rb
89
124
  - openssl-ssh.gemspec
125
+ - openssl-ssh.md
90
126
  homepage: https://github.com/aladac/openssl-ssh
91
127
  licenses:
92
128
  - MIT
93
129
  metadata:
94
130
  homepage_uri: https://github.com/aladac/openssl-ssh
95
- source_code_uri: 'TODO: Put your gem''s public repo URL here.'
96
- changelog_uri: 'TODO: Put your gem''s CHANGELOG.md URL here.'
97
- post_install_message:
131
+ source_code_uri: https://github.com/aladac/openssl-ssh
132
+ changelog_uri: https://github.com/aladac/openssl-ssh/blob/master/CHANGELOG.md
133
+ rubygems_mfa_required: 'true'
134
+ post_install_message:
98
135
  rdoc_options: []
99
136
  require_paths:
100
137
  - lib
@@ -102,16 +139,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
102
139
  requirements:
103
140
  - - ">="
104
141
  - !ruby/object:Gem::Version
105
- version: '0'
142
+ version: '3.0'
106
143
  required_rubygems_version: !ruby/object:Gem::Requirement
107
144
  requirements:
108
145
  - - ">="
109
146
  - !ruby/object:Gem::Version
110
147
  version: '0'
111
148
  requirements: []
112
- rubyforge_project:
113
- rubygems_version: 2.6.14.1
114
- signing_key:
149
+ rubygems_version: 3.5.22
150
+ signing_key:
115
151
  specification_version: 4
116
152
  summary: Handling for OpenSSH public keys
117
153
  test_files: []
data/.travis.yml DELETED
@@ -1,7 +0,0 @@
1
- ---
2
- sudo: false
3
- language: ruby
4
- cache: bundler
5
- rvm:
6
- - 2.4.4
7
- before_install: gem install bundler -v 1.16.6