putty-key 1.0.0

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