putty-key 1.0.0

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.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +3 -0
  3. data.tar.gz.sig +1 -0
  4. data/.yardopts +7 -0
  5. data/CHANGES.md +4 -0
  6. data/Gemfile +14 -0
  7. data/LICENSE +19 -0
  8. data/README.md +137 -0
  9. data/Rakefile +110 -0
  10. data/lib/putty/key.rb +25 -0
  11. data/lib/putty/key/error.rb +26 -0
  12. data/lib/putty/key/openssl.rb +182 -0
  13. data/lib/putty/key/ppk.rb +374 -0
  14. data/lib/putty/key/util.rb +128 -0
  15. data/lib/putty/key/version.rb +6 -0
  16. data/putty-key.gemspec +29 -0
  17. data/test/fixtures/dss-1024-encrypted.ppk +17 -0
  18. data/test/fixtures/dss-1024.pem +12 -0
  19. data/test/fixtures/dss-1024.ppk +17 -0
  20. data/test/fixtures/ecdsa-secp256k1.pem +5 -0
  21. data/test/fixtures/ecdsa-sha2-nistp256-encrypted.ppk +10 -0
  22. data/test/fixtures/ecdsa-sha2-nistp256.pem +5 -0
  23. data/test/fixtures/ecdsa-sha2-nistp256.ppk +10 -0
  24. data/test/fixtures/ecdsa-sha2-nistp384-encrypted.ppk +11 -0
  25. data/test/fixtures/ecdsa-sha2-nistp384.pem +6 -0
  26. data/test/fixtures/ecdsa-sha2-nistp384.ppk +11 -0
  27. data/test/fixtures/ecdsa-sha2-nistp521-encrypted.ppk +12 -0
  28. data/test/fixtures/ecdsa-sha2-nistp521.pem +7 -0
  29. data/test/fixtures/ecdsa-sha2-nistp521.ppk +12 -0
  30. data/test/fixtures/rsa-2048-encrypted.ppk +26 -0
  31. data/test/fixtures/rsa-2048.pem +27 -0
  32. data/test/fixtures/rsa-2048.ppk +26 -0
  33. data/test/fixtures/test-blank-comment.ppk +11 -0
  34. data/test/fixtures/test-encrypted.ppk +11 -0
  35. data/test/fixtures/test-invalid-blob-lines.ppk +11 -0
  36. data/test/fixtures/test-invalid-encryption-type.ppk +11 -0
  37. data/test/fixtures/test-invalid-format-1.ppk +11 -0
  38. data/test/fixtures/test-invalid-format-3.ppk +11 -0
  39. data/test/fixtures/test-invalid-private-mac.ppk +11 -0
  40. data/test/fixtures/test-truncated.ppk +10 -0
  41. data/test/fixtures/test-unix-line-endings.ppk +11 -0
  42. data/test/fixtures/test.ppk +11 -0
  43. data/test/openssl_test.rb +252 -0
  44. data/test/ppk_test.rb +247 -0
  45. data/test/test_helper.rb +81 -0
  46. data/test/util_test.rb +180 -0
  47. data/test/version_test.rb +7 -0
  48. metadata +124 -0
  49. metadata.gz.sig +0 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2f25fb48a4e37551a2660d1d4fd3147d3e3170ac
4
+ data.tar.gz: 8e4bd0d38dbf9a5a56e1e10fdf7db11cd2f7a055
5
+ SHA512:
6
+ metadata.gz: 54e123b54417b3b2dcb280c6fccb054ae7f4a1d2bb4af8924c33f12886cdea5dcf3d998dbe48c435840578ebed83f471742b45fea808334ebd0010047b1f45bd
7
+ data.tar.gz: cd8f021629d480f19e4c3b7f628b1b602c318e69ce1316b6619dd6f94b42fc3dd969d2a53b8cbc4e6df1cc655a54dc83512cc74499804fa20fc3cbc26eb0e33b
checksums.yaml.gz.sig ADDED
@@ -0,0 +1,3 @@
1
+ 1�񨯼]�n]�W��E����:Q��؃�#� ��5��.�|T�ju���ͱ�gh�#x�?�;���l�y:C�{8��B��hg��@�����Yba��
2
+ �����)��������B�ɵ
3
+ ��R��#��,/��)���-CW��<5A�c�E��+qtN���CV��a����(�S�`��JG�����[�.��/G?���jf��<+^#�d��K�xb�}'>g^r#�2�'׶��;�X&�7˼OG�Z��5 �
data.tar.gz.sig ADDED
@@ -0,0 +1 @@
1
+ MI�,�+�^������i�+-� X�6�����&9���W�1=�@�W�V �a��q3 #R���+?3�ɐ�A�o�y �Zۄ�毡 ��(��lvq'fզ��C�y�ĉL��*&{�����Ic���O��M���{{ǥ��>z\}�„,��?R '��"�y[��`kf
data/.yardopts ADDED
@@ -0,0 +1,7 @@
1
+ --markup=markdown
2
+ --no-private
3
+ lib/**/*.rb
4
+ -
5
+ CHANGES.md
6
+ LICENSE
7
+ README.md
data/CHANGES.md ADDED
@@ -0,0 +1,4 @@
1
+ Version 1.0.0 - 2-Apr-2016
2
+ --------------------------
3
+
4
+ * First release.
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem 'rake', '~> 10.5'
7
+ gem 'git', '~> 1.2', require: false
8
+ end
9
+
10
+ group :test do
11
+ gem 'minitest', '~> 5.8'
12
+ gem 'simplecov', '~> 0.11', require: false
13
+ gem 'coveralls', '~> 0.8', require: false
14
+ end
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2016 Philip Ross
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7
+ of the Software, and to permit persons to whom the Software is furnished to do
8
+ so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,137 @@
1
+ # PuTTY::Key #
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/putty-key.svg)](http://badge.fury.io/rb/putty-key) [![Build Status](https://travis-ci.org/philr/putty-key.svg?branch=master)](https://travis-ci.org/philr/putty-key) [![Coverage Status](https://coveralls.io/repos/philr/putty-key/badge.svg?branch=master)](https://coveralls.io/r/philr/putty-key?branch=master)
4
+
5
+ PuTTY::Key is a pure-Ruby implementation of the PuTTY private key (ppk) format,
6
+ handling reading and writing .ppk files. It includes a refinement to Ruby's
7
+ OpenSSL library to add support for converting DSA, EC and RSA private keys to
8
+ and from PuTTY private key files. This allows OpenSSH ecdsa, ssh-dss and ssh-rsa
9
+ private keys to be converted to and from PuTTY's private key format.
10
+
11
+
12
+ ## Installation ##
13
+
14
+ To install the PuTTY::Key gem, run the following command:
15
+
16
+ ```bash
17
+ gem install putty-key
18
+ ```
19
+
20
+ To add PuTTY::Key as a Bundler dependency, add the following line to your
21
+ `Gemfile`:
22
+
23
+ ```ruby
24
+ gem 'putty-key'
25
+ ```
26
+
27
+ ## Compatibility ##
28
+
29
+ PuTTY::Key is compatible with Ruby MRI 2.1.0+ and Rubinius 2.5.4+ (provided the
30
+ OpenSSL standard library is available).
31
+
32
+ JRuby will be supported (DSA/DSS and RSA keys only) once jruby-openssl pull
33
+ requests [#82](https://github.com/jruby/jruby-openssl/pull/82) and
34
+ [#83](https://github.com/jruby/jruby-openssl/pull/83) have been released.
35
+
36
+
37
+ ## Usage ##
38
+
39
+ To use PuTTY::Key, it must first be loaded with:
40
+
41
+ ```ruby
42
+ require 'putty/key'
43
+ ```
44
+
45
+ The included [refinement](http://ruby-doc.org/core-2.3.0/doc/syntax/refinements_rdoc.html)
46
+ to Ruby's OpenSSL library can then either be activated in the lexical scope
47
+ (file, class or module) where it will be used with:
48
+
49
+ ```ruby
50
+ using PuTTY::Key
51
+ ```
52
+
53
+ or installed globally by calling:
54
+
55
+ ```ruby
56
+ PuTTY::Key.global_install
57
+ ```
58
+
59
+ Note that Rubinius (as of version 3.22) does not support refinements, so the
60
+ global installation approach is required.
61
+
62
+ JRuby (as of version 9.0.5.0) includes support for refinements, but there are
63
+ still outstanding issues. The global installation approach is preferable on
64
+ JRuby.
65
+
66
+ The following sections give examples of how PuTTY::Key can be used.
67
+
68
+
69
+ ### Converting a .pem formatted key file to an unencrypted .ppk file ###
70
+
71
+ ```ruby
72
+ require 'openssl'
73
+ require 'putty/key'
74
+ using PuTTY::Key # or PuTTY::Key.global_install
75
+
76
+ pem = File.read('key.pem', mode: 'rb')
77
+ pkey = OpenSSL::PKey.read(pem)
78
+ ppk = pkey.to_ppk
79
+ ppk.comment = 'Optional comment'
80
+ ppk.save('key.ppk')
81
+ ```
82
+
83
+
84
+ ### Generating a new RSA key and saving it as an encrypted .ppk file ###
85
+
86
+ ```ruby
87
+ require 'openssl'
88
+ require 'putty/key'
89
+ using PuTTY::Key # or PuTTY::Key.global_install
90
+
91
+ rsa = OpenSSL::PKey::RSA.generate(2048)
92
+ ppk = rsa.to_ppk
93
+ ppk.comment = 'RSA 2048'
94
+ ppk.save('rsa.ppk', 'Passphrase for encryption')
95
+ ```
96
+
97
+
98
+ ### Converting an unencrypted .ppk file to .pem format ###
99
+
100
+ ```ruby
101
+ require 'openssl'
102
+ require 'putty/key'
103
+ using PuTTY::Key # or PuTTY::Key.global_install
104
+
105
+ ppk = PuTTY::Key::PPK.new('key.ppk')
106
+ pkey = OpenSSL::PKey.from_ppk(ppk)
107
+ pem = pkey.to_pem
108
+ File.write('key.pem', pem, mode: 'wb')
109
+ ```
110
+
111
+
112
+ ### Decrypting a .ppk file and re-saving it without encryption ###
113
+
114
+ ```ruby
115
+ require 'putty/key'
116
+
117
+ ppk = PuTTY::Key::PPK.new('rsa.ppk', 'Passphrase for encryption')
118
+ ppk.save('rsa-plain.ppk')
119
+ ```
120
+
121
+
122
+ ## API Documentation ##
123
+
124
+ API documentation for PuTTY::Key is available on
125
+ [RubyDoc.info](http://www.rubydoc.info/gems/putty-key).
126
+
127
+
128
+ ## License ##
129
+
130
+ PuTTY::Key is distributed under the terms of the MIT license. A copy of this
131
+ license can be found in the included LICENSE file.
132
+
133
+
134
+ ## GitHub Project ##
135
+
136
+ Source code, release information and the issue tracker can be found on the
137
+ [PuTTY::Key GitHub project page](https://github.com/philr/putty-key).
data/Rakefile ADDED
@@ -0,0 +1,110 @@
1
+ require 'fileutils'
2
+ require 'rubygems'
3
+ require 'rubygems/package_task'
4
+ require 'rake/testtask'
5
+
6
+ BASE_DIR = File.expand_path(File.dirname(__FILE__))
7
+
8
+ task :default => :test
9
+
10
+ spec = eval(File.read('putty-key.gemspec'))
11
+
12
+ # Attempt to find the private key and return a spec with added options for
13
+ # signing the gem if found.
14
+ def add_signing_key(spec)
15
+ private_key_path = File.expand_path(File.join(BASE_DIR, '..', 'key', 'gem-private_key.pem'))
16
+
17
+ if File.exist?(private_key_path)
18
+ spec = spec.clone
19
+ spec.signing_key = private_key_path
20
+ spec.cert_chain = [File.join(BASE_DIR, 'gem-public_cert.pem')]
21
+ else
22
+ puts 'WARNING: Private key not found. Not signing gem file.'
23
+ end
24
+
25
+ spec
26
+ end
27
+
28
+ package_task = Gem::PackageTask.new(add_signing_key(spec)) do
29
+ end
30
+
31
+ # Ensure files are world-readable before packaging.
32
+ Rake::Task[package_task.package_dir_path].enhance do
33
+ recurse_chmod(package_task.package_dir_path)
34
+ end
35
+
36
+ def recurse_chmod(dir)
37
+ File.chmod(0755, dir)
38
+
39
+ Dir.entries(dir).each do |entry|
40
+ if entry != '.' && entry != '..'
41
+ path = File.join(dir, entry)
42
+ if File.directory?(path)
43
+ recurse_chmod(path)
44
+ else
45
+ File.chmod(0644, path)
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ desc 'Create a tag for the current version'
52
+ task :tag do
53
+ require 'git'
54
+ g = Git.init(BASE_DIR)
55
+ g.add_tag("v#{spec.version}", annotate: true, message: "Tagging v#{spec.version}")
56
+ end
57
+
58
+ def define_test_task(type, test_coverage)
59
+ type = type.to_s
60
+
61
+ env_task = "test:env:#{type}"
62
+ Rake::Task::define_task(env_task) do
63
+ ENV['TEST_COVERAGE'] = test_coverage ? '1' : '0'
64
+ ENV['TEST_TYPE'] = type
65
+ end
66
+
67
+ test_task = "test:#{type}"
68
+ Rake::TestTask.new(test_task) do |t|
69
+ t.libs = [File.join(BASE_DIR, 'test')]
70
+ t.pattern = File.join(BASE_DIR, 'test', '**', '*_test.rb')
71
+ t.warning = true
72
+ end
73
+
74
+ Rake::Task[test_task].enhance([env_task])
75
+ end
76
+
77
+ # JRuby 9.0.5.0 doesn't handle refinements correctly.
78
+ if RUBY_ENGINE == 'jruby'
79
+ # Don't run coverage tests on JRuby due to inaccurate results.
80
+ TEST_COVERAGE = false
81
+
82
+ task 'test:refinement' do
83
+ puts 'Skipping refinement tests on JRuby'
84
+ end
85
+ elsif !respond_to?(:using, true)
86
+ # Don't run coverage tests on platforms that don't support refinements, since
87
+ # it won't be possible to get complete coverage.
88
+ TEST_COVERAGE = false
89
+
90
+ task 'test:refinement' do
91
+ puts "Skipping refinement tests because #{RUBY_DESCRIPTION} lacks support for refinements"
92
+ end
93
+ else
94
+ TEST_COVERAGE = true
95
+ define_test_task(:refinement, TEST_COVERAGE)
96
+ end
97
+
98
+ define_test_task(:global, TEST_COVERAGE)
99
+
100
+ require 'coveralls/rake/task'
101
+ Coveralls::RakeTask.new
102
+
103
+ desc 'Remove coverage results'
104
+ task :clean_coverage do
105
+ FileUtils.rm_f(File.join(BASE_DIR, 'coverage', '.resultset.json'))
106
+ end
107
+
108
+ desc 'Run tests using the refinement, then with the global install'
109
+ task :test => [:clean_coverage, 'test:refinement', 'test:global'] + (TEST_COVERAGE ? ['coveralls:push'] : []) do
110
+ end
data/lib/putty/key.rb ADDED
@@ -0,0 +1,25 @@
1
+ module PuTTY
2
+ # PuTTY::Key is a pure-Ruby implementation of the PuTTY private key (ppk)
3
+ # format, handling reading and writing .ppk files. It includes a refinement to
4
+ # Ruby's OpenSSL library to add support for converting DSA, EC and RSA private
5
+ # keys to and from PuTTY private key files. This allows OpenSSH ecdsa, ssh-dss
6
+ # and ssh-rsa private keys to be converted to and from PuTTY's private key
7
+ # format.
8
+ module Key
9
+
10
+ # Makes the refinements available in PuTTY::Key available globally. After
11
+ # calling {global_install}, it is no longer necessary to include
12
+ # `using PuTTY::Key` when using the `to_ppk` and `from_ppk` methods added to
13
+ # `OpenSSL::PKey`.
14
+ def self.global_install
15
+ ::PuTTY::Key::OpenSSL.global_install
16
+ end
17
+ end
18
+ end
19
+
20
+ require 'putty/key/version'
21
+ require 'putty/key/error'
22
+ require 'putty/key/util'
23
+ require 'putty/key/ppk'
24
+ require 'putty/key/openssl'
25
+
@@ -0,0 +1,26 @@
1
+ module PuTTY
2
+ module Key
3
+ # Base class for all the error classes included in PuTTY::Key.
4
+ class Error < StandardError
5
+ end
6
+
7
+ # Indicates that an error was encountered in a .ppk file that is being
8
+ # read or converted to another format.
9
+ class FormatError < Error
10
+ end
11
+
12
+ # Indicates that an operation cannot be performed with the current state
13
+ # of the receiver.
14
+ class InvalidStateError < Error
15
+ end
16
+
17
+ # Indicates that the specified elliptic curve is not supported.
18
+ class UnsupportedCurveError < Error
19
+ end
20
+
21
+ # Indicates that a nil value has been encountered.
22
+ class NilValueError < Error
23
+ end
24
+ private_constant :NilValueError
25
+ end
26
+ end
@@ -0,0 +1,182 @@
1
+ require 'openssl'
2
+
3
+ module PuTTY
4
+ module Key
5
+ module OpenSSL
6
+ # {OpenSSL::PKey} classes to be refined.
7
+ PKEY_CLASSES = Hash[%i(DSA EC RSA).map {|c| [c, ::OpenSSL::PKey.const_get(c)] rescue nil }.compact]
8
+ private_constant :PKEY_CLASSES
9
+
10
+ # Mapping from SSH curve names to their equivalent OpenSSL names.
11
+ OPENSSL_CURVES = {
12
+ 'nistp256' => 'prime256v1',
13
+ 'nistp384' => 'secp384r1',
14
+ 'nistp521' => 'secp521r1'
15
+ }
16
+ private_constant :OPENSSL_CURVES
17
+
18
+ # Mapping from OpenSSL curve names to their equivalent SSH names.
19
+ SSH_CURVES = OPENSSL_CURVES.invert
20
+ private_constant :SSH_CURVES
21
+
22
+ # The {ClassMethods} module is used to extend `OpenSSL::PKey` when
23
+ # using the PuTTY::Key refinement or calling {PuTTY::Key.global_install}.
24
+ # This adds a `from_ppk` class method to `OpenSSL::PKey`.
25
+ #
26
+ module ClassMethods
27
+ # Creates a new `OpenSSL::PKey` from a PuTTY private key (instance of
28
+ # {PPK}).
29
+ #
30
+ # This method is called using `OpenSSL::PKey.from_ppk(ppk)`.
31
+ #
32
+ # PuTTY keys using the algorithms `ssh-dss`, `ssh-rsa`,
33
+ # `ecdsa-sha2-nistp256`, `ecdsa-sha2-nistp384` and `ecdsa-sha2-nistp521`
34
+ # are supported.
35
+ #
36
+ # @return [Object] An instance of either `OpenSSL::PKey::DSA`,
37
+ # `OpenSSL::PKey::RSA` or `OpenSSL::PKey::EC` depending on the
38
+ # algorithm of `ppk`.
39
+ #
40
+ # @raise [ArgumentError] If `ppk` is `nil`.
41
+ # @raise [ArgumentError] If the algorithm of `ppk` is not supported.
42
+ def from_ppk(ppk)
43
+ raise ArgumentError, 'ppk must not be nil' unless ppk
44
+
45
+ case ppk.algorithm
46
+ when 'ssh-dss'
47
+ ::OpenSSL::PKey::DSA.new.tap do |pkey|
48
+ _, pkey.p, pkey.q, pkey.g, pkey.pub_key = Util.ssh_unpack(ppk.public_blob, :string, :mpint, :mpint, :mpint, :mpint)
49
+ pkey.priv_key = Util.ssh_unpack(ppk.private_blob, :mpint).first
50
+ end
51
+ when 'ssh-rsa'
52
+ ::OpenSSL::PKey::RSA.new.tap do |pkey|
53
+ _, pkey.e, pkey.n = Util.ssh_unpack(ppk.public_blob, :string, :mpint, :mpint)
54
+ pkey.d, pkey.p, pkey.q, pkey.iqmp = Util.ssh_unpack(ppk.private_blob, :mpint, :mpint, :mpint, :mpint)
55
+ pkey.dmp1 = pkey.d % (pkey.p - 1)
56
+ pkey.dmq1 = pkey.d % (pkey.q - 1)
57
+ end
58
+ when /\Aecdsa-sha2-(nistp(?:256|384|521))\z/
59
+ curve = OPENSSL_CURVES[$1]
60
+
61
+ # jruby-openssl doesn't include an EC class (version 0.9.16)
62
+ ec_class = (::OpenSSL::PKey::EC rescue raise ArgumentError, "Unsupported algorithm: #{ppk.algorithm}")
63
+
64
+ ec_class.new(curve).tap do |pkey|
65
+ _, _, point = Util.ssh_unpack(ppk.public_blob, :string, :string, :mpint)
66
+ pkey.public_key = ::OpenSSL::PKey::EC::Point.new(pkey.group, point)
67
+ pkey.private_key = Util.ssh_unpack(ppk.private_blob, :mpint).first
68
+ end
69
+ else
70
+ raise ArgumentError, "Unsupported algorithm: #{ppk.algorithm}"
71
+ end
72
+ end
73
+ end
74
+
75
+ # The {DSA} module is included into `OpenSSL::PKey::DSA` when using the
76
+ # PuTTY::Key refinement or calling {PuTTY::Key.global_install}. This adds
77
+ # a `to_ppk` instance method to `OpenSSL::PKey::DSA`.
78
+ module DSA
79
+ # Returns a new {PPK} instance that is equivalent to this key.
80
+ #
81
+ # `to_ppk` can be called on instances of `OpenSSL::PKey::DSA`.
82
+ #
83
+ # @return [PPK] A new instance of {PPK} that is equivalent to this key.
84
+ #
85
+ # @raise [InvalidStateError] If the key has not been initialized.
86
+ def to_ppk
87
+ PPK.new.tap do |ppk|
88
+ ppk.algorithm = 'ssh-dss'
89
+ begin
90
+ ppk.public_blob = Util.ssh_pack('ssh-dss', p, q, g, pub_key)
91
+ ppk.private_blob = Util.ssh_pack(priv_key)
92
+ rescue NilValueError
93
+ raise InvalidStateError, 'The key has not been fully initialized (the p, q, g, pub_key and priv_key parameters must all be assigned)'
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ # The {EC} module is included into `OpenSSL::PKey::EC` when using the
100
+ # PuTTY::Key refinement or calling {PuTTY::Key.global_install}. This adds
101
+ # a `to_ppk` instance method to `OpenSSL::PKey::EC`.
102
+ module EC
103
+ # Returns a new {PPK} instance that is equivalent to this key.
104
+ #
105
+ # `to_ppk` can be called on instances of `OpenSSL::PKey::EC`.
106
+ #
107
+ # @return [PPK] A new instance of {PPK} that is equivalent to this key.
108
+ #
109
+ # @raise [InvalidStateError] If the key has not been initialized.
110
+ # @raise [UnsupportedCurveError] If the key uses a curve that is not
111
+ # supported by PuTTY.
112
+ def to_ppk
113
+ curve = group && group.curve_name
114
+ raise InvalidStateError, 'The key has not been fully initialized (a curve name must be assigned)' unless curve
115
+ ssh_curve = SSH_CURVES[curve]
116
+ raise UnsupportedCurveError, "The curve '#{curve}' is not supported" unless ssh_curve
117
+
118
+ PPK.new.tap do |ppk|
119
+ ppk.algorithm = "ecdsa-sha2-#{ssh_curve}"
120
+ begin
121
+ ppk.public_blob = Util.ssh_pack(ppk.algorithm, ssh_curve, public_key && public_key.to_bn)
122
+ ppk.private_blob = Util.ssh_pack(private_key)
123
+ rescue NilValueError
124
+ raise InvalidStateError, 'The key has not been fully initialized (public_key and private_key must both be assigned)'
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ # The {RSA} module is included into `OpenSSL::PKey::RSA` when using the
131
+ # PuTTY::Key refinement or calling {PuTTY::Key.global_install}. This adds
132
+ # a `to_ppk` instance method to `OpenSSL::PKey::RSA`.
133
+ module RSA
134
+ # Returns a new {PPK} instance that is equivalent to this key.
135
+ #
136
+ # `to_ppk` can be called on instances of `OpenSSL::PKey::DSA`.
137
+ #
138
+ # @return [PPK] A new instance of {PPK} that is equivalent to this key.
139
+ #
140
+ # @raise [InvalidStateError] If the key has not been initialized.
141
+ def to_ppk
142
+ PPK.new.tap do |ppk|
143
+ ppk.algorithm = 'ssh-rsa'
144
+ begin
145
+ ppk.public_blob = Util.ssh_pack('ssh-rsa', e, n)
146
+ ppk.private_blob = Util.ssh_pack(d, p, q, iqmp)
147
+ rescue NilValueError
148
+ raise InvalidStateError, 'The key has not been fully initialized (the e, n, d, p, q and iqmp parameters must all be assigned)'
149
+ end
150
+ end
151
+ end
152
+ end
153
+
154
+ # Makes the refinements to `OpenSSL` available in PuTTY::Key available
155
+ # globally. After calling {global_install}, it is no longer necessary to
156
+ # include `using PuTTY::Key` when using the `to_ppk` and `from_ppk`
157
+ # methods added to `OpenSSL::PKey`.
158
+ def self.global_install
159
+ PKEY_CLASSES.each do |name, openssl_class|
160
+ mod = const_get(name)
161
+ openssl_class.class_eval do
162
+ include mod
163
+ end
164
+ end
165
+
166
+ ::OpenSSL::PKey.module_eval do
167
+ extend ClassMethods
168
+ end
169
+ end
170
+ end
171
+
172
+ OpenSSL.const_get(:PKEY_CLASSES).each do |name, openssl_class|
173
+ refine openssl_class do
174
+ include OpenSSL.const_get(name)
175
+ end if respond_to?(:refine, true)
176
+ end
177
+
178
+ refine ::OpenSSL::PKey.singleton_class do
179
+ include OpenSSL::ClassMethods
180
+ end if respond_to?(:refine, true)
181
+ end
182
+ end