putty-key 1.0.1 → 1.1.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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/CHANGES.md +16 -0
- data/Gemfile +0 -6
- data/LICENSE +1 -1
- data/README.md +32 -6
- data/Rakefile +24 -0
- data/lib/putty/key.rb +6 -6
- data/lib/putty/key/argon2_params.rb +101 -0
- data/lib/putty/key/error.rb +17 -0
- data/lib/putty/key/libargon2.rb +54 -0
- data/lib/putty/key/ppk.rb +469 -103
- data/lib/putty/key/util.rb +10 -10
- data/lib/putty/key/version.rb +1 -1
- data/putty-key.gemspec +11 -2
- data/test/argon2_params_test.rb +144 -0
- data/test/fixtures/{dss-1024-encrypted.ppk → dss-1024-encrypted-format-2.ppk} +17 -17
- data/test/fixtures/dss-1024-encrypted-format-3.ppk +22 -0
- data/test/fixtures/{dss-1024.ppk → dss-1024-format-2.ppk} +17 -17
- data/test/fixtures/dss-1024-format-3.ppk +17 -0
- data/test/fixtures/{ecdsa-sha2-nistp256-encrypted.ppk → ecdsa-sha2-nistp256-encrypted-format-2.ppk} +10 -10
- data/test/fixtures/ecdsa-sha2-nistp256-encrypted-format-3.ppk +15 -0
- data/test/fixtures/{ecdsa-sha2-nistp256.ppk → ecdsa-sha2-nistp256-format-2.ppk} +10 -10
- data/test/fixtures/ecdsa-sha2-nistp256-format-3.ppk +10 -0
- data/test/fixtures/{ecdsa-sha2-nistp384-encrypted.ppk → ecdsa-sha2-nistp384-encrypted-format-2.ppk} +11 -11
- data/test/fixtures/ecdsa-sha2-nistp384-encrypted-format-3.ppk +16 -0
- data/test/fixtures/{ecdsa-sha2-nistp384.ppk → ecdsa-sha2-nistp384-format-2.ppk} +11 -11
- data/test/fixtures/ecdsa-sha2-nistp384-format-3.ppk +11 -0
- data/test/fixtures/{ecdsa-sha2-nistp521-encrypted.ppk → ecdsa-sha2-nistp521-encrypted-format-2.ppk} +12 -12
- data/test/fixtures/ecdsa-sha2-nistp521-encrypted-format-3.ppk +17 -0
- data/test/fixtures/{ecdsa-sha2-nistp521.ppk → ecdsa-sha2-nistp521-format-2.ppk} +12 -12
- data/test/fixtures/ecdsa-sha2-nistp521-format-3.ppk +12 -0
- data/test/fixtures/{rsa-2048-encrypted.ppk → rsa-2048-encrypted-format-2.ppk} +26 -26
- data/test/fixtures/rsa-2048-encrypted-format-3.ppk +31 -0
- data/test/fixtures/{rsa-2048.ppk → rsa-2048-format-2.ppk} +26 -26
- data/test/fixtures/rsa-2048-format-3.ppk +26 -0
- data/test/fixtures/test-blank-comment.ppk +11 -11
- data/test/fixtures/test-empty-blobs-encrypted.ppk +6 -0
- data/test/fixtures/test-empty-blobs.ppk +6 -0
- data/test/fixtures/{test-encrypted.ppk → test-encrypted-format-2.ppk} +11 -11
- data/test/fixtures/test-encrypted-format-3.ppk +16 -0
- data/test/fixtures/test-encrypted-type-d-format-3.ppk +16 -0
- data/test/fixtures/test-encrypted-type-i-format-3.ppk +16 -0
- data/test/fixtures/{test-unix-line-endings.ppk → test-format-2.ppk} +0 -0
- data/test/fixtures/test-format-3.ppk +11 -0
- data/test/fixtures/test-invalid-argon2-memory-for-libargon2.ppk +16 -0
- data/test/fixtures/test-invalid-argon2-memory-maximum.ppk +16 -0
- data/test/fixtures/test-invalid-argon2-memory.ppk +16 -0
- data/test/fixtures/test-invalid-argon2-parallelism-maximum.ppk +16 -0
- data/test/fixtures/test-invalid-argon2-parallelism.ppk +16 -0
- data/test/fixtures/test-invalid-argon2-passes-maximum.ppk +16 -0
- data/test/fixtures/test-invalid-argon2-passes.ppk +16 -0
- data/test/fixtures/test-invalid-argon2-salt.ppk +16 -0
- data/test/fixtures/test-invalid-blob-lines.ppk +11 -11
- data/test/fixtures/test-invalid-encryption-type.ppk +11 -11
- data/test/fixtures/test-invalid-format-1.ppk +11 -11
- data/test/fixtures/{test-invalid-format-3.ppk → test-invalid-format-4.ppk} +11 -11
- data/test/fixtures/test-invalid-key-derivation.ppk +16 -0
- data/test/fixtures/test-invalid-private-mac.ppk +11 -11
- data/test/fixtures/test-legacy-mac-line-endings.ppk +1 -0
- data/test/fixtures/test-missing-final-line-ending.ppk +11 -0
- data/test/fixtures/test-truncated.ppk +10 -10
- data/test/fixtures/{test.ppk → test-windows-line-endings.ppk} +0 -0
- data/test/openssl_test.rb +243 -53
- data/test/ppk_test.rb +325 -44
- metadata +73 -23
- metadata.gz.sig +0 -0
data/test/ppk_test.rb
CHANGED
@@ -16,18 +16,39 @@ class PPKTest < Minitest::Test
|
|
16
16
|
assert_nil(ppk.private_blob)
|
17
17
|
end
|
18
18
|
|
19
|
-
def
|
20
|
-
|
21
|
-
|
19
|
+
def test_initialize_invalid_format_too_old
|
20
|
+
format = PuTTY::Key::PPK::MINIMUM_FORMAT - 1
|
21
|
+
error = assert_raises(PuTTY::Key::FormatError) { PuTTY::Key::PPK.new(fixture_path("test-invalid-format-#{format}.ppk")) }
|
22
|
+
assert_equal("The ppk file is using an old unsupported format (#{format})", error.message)
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_initialize_invalid_format_too_new
|
26
|
+
format = PuTTY::Key::PPK::MAXIMUM_FORMAT + 1
|
27
|
+
error = assert_raises(PuTTY::Key::FormatError) { PuTTY::Key::PPK.new(fixture_path("test-invalid-format-#{format}.ppk")) }
|
28
|
+
assert_equal("The ppk file is using a format that is too new (#{format})", error.message)
|
29
|
+
end
|
30
|
+
|
31
|
+
[:encryption_type, :key_derivation, :argon2_memory, :argon2_memory_maximum,
|
32
|
+
:argon2_passes, :argon2_passes_maximum, :argon2_parallelism,
|
33
|
+
:argon2_parallelism_maximum, :argon2_salt
|
34
|
+
].each do |feature|
|
35
|
+
define_method("test_initialize_invalid_#{feature}") do
|
36
|
+
path = fixture_path("test-invalid-#{feature.to_s.gsub('_', '-')}.ppk")
|
37
|
+
assert_raises(PuTTY::Key::FormatError) { PuTTY::Key::PPK.new(path, 'Test Passphrase') }
|
22
38
|
end
|
23
39
|
end
|
24
40
|
|
25
|
-
|
26
|
-
|
41
|
+
[:private_mac, :blob_lines].each do |feature|
|
42
|
+
define_method("test_initialize_invalid_#{feature}") do
|
43
|
+
path = fixture_path("test-invalid-#{feature.to_s.gsub('_', '-')}.ppk")
|
44
|
+
assert_raises(PuTTY::Key::FormatError) { PuTTY::Key::PPK.new(path) }
|
45
|
+
end
|
27
46
|
end
|
28
47
|
|
29
|
-
def
|
30
|
-
|
48
|
+
def test_initialize_invalid_argon2_memory_for_libargon2
|
49
|
+
# Allowed by Argon2Params, but not by libargon2.
|
50
|
+
path = fixture_path("test-invalid-argon2-memory-for-libargon2.ppk")
|
51
|
+
assert_raises(PuTTY::Key::Argon2Error) { PuTTY::Key::PPK.new(path, 'Test Passphrase') }
|
31
52
|
end
|
32
53
|
|
33
54
|
def test_initialize_file_not_exists
|
@@ -44,30 +65,51 @@ class PPKTest < Minitest::Test
|
|
44
65
|
assert_raises(PuTTY::Key::FormatError) { PuTTY::Key::PPK.new(fixture_path('test-truncated.ppk')) }
|
45
66
|
end
|
46
67
|
|
47
|
-
def
|
48
|
-
assert_raises(PuTTY::Key::FormatError) { PuTTY::Key::PPK.new(fixture_path('test-invalid-blob-lines.ppk')) }
|
49
|
-
end
|
50
|
-
|
51
|
-
def assert_test_ppk_properties(ppk, comment: TEST_COMMENT, encrypted: false)
|
68
|
+
def assert_test_ppk_properties(ppk, comment: TEST_COMMENT, public_blob: TEST_PUBLIC_BLOB, private_blob: TEST_PRIVATE_BLOB, encrypted: false)
|
52
69
|
assert_equal(Encoding::ASCII_8BIT, ppk.algorithm.encoding)
|
53
70
|
assert_equal(Encoding::ASCII_8BIT, ppk.comment.encoding)
|
54
71
|
assert_equal(Encoding::ASCII_8BIT, ppk.public_blob.encoding)
|
55
72
|
assert_equal(Encoding::ASCII_8BIT, ppk.private_blob.encoding)
|
56
73
|
assert_equal('test'.b, ppk.algorithm)
|
57
74
|
assert_equal(comment, ppk.comment)
|
58
|
-
assert_equal(
|
75
|
+
assert_equal(public_blob, ppk.public_blob)
|
59
76
|
|
60
77
|
if encrypted
|
61
78
|
# When loading an encrypted ppk file, the padding added to the private blob cannot be removed.
|
62
|
-
assert(ppk.private_blob.start_with?(
|
79
|
+
assert(ppk.private_blob.start_with?(private_blob), "Private blob does not start with #{TEST_PRIVATE_BLOB}")
|
63
80
|
else
|
64
|
-
assert_equal(
|
81
|
+
assert_equal(private_blob, ppk.private_blob)
|
65
82
|
end
|
66
83
|
end
|
67
84
|
|
68
|
-
|
69
|
-
|
70
|
-
|
85
|
+
[2, 3].each do |format|
|
86
|
+
define_method("test_initialize_unencrypted_format_#{format}") do
|
87
|
+
ppk = PuTTY::Key::PPK.new(fixture_path("test-format-#{format}.ppk"))
|
88
|
+
assert_test_ppk_properties(ppk)
|
89
|
+
end
|
90
|
+
|
91
|
+
define_method("test_initialize_encrypted_format_#{format}") do
|
92
|
+
ppk = PuTTY::Key::PPK.new(fixture_path("test-encrypted-format-#{format}.ppk"), 'Test Passphrase')
|
93
|
+
assert_test_ppk_properties(ppk, encrypted: true)
|
94
|
+
end
|
95
|
+
|
96
|
+
define_method("test_initialize_encrypted_no_passphrase_#{format}") do
|
97
|
+
path = fixture_path("test-encrypted-format-#{format}.ppk")
|
98
|
+
assert_raises(ArgumentError) { PuTTY::Key::PPK.new(path) }
|
99
|
+
end
|
100
|
+
|
101
|
+
define_method("test_initialize_encrypted_incorrect_passphrase_#{format}") do
|
102
|
+
path = fixture_path("test-encrypted-format-#{format}.ppk")
|
103
|
+
assert_raises(ArgumentError) { PuTTY::Key::PPK.new(path, 'Not Test Passphrase') }
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# type-d and type-i fixtures also use different Argon2 parameters.
|
108
|
+
[:d, :i].each do |type|
|
109
|
+
define_method("test_initialize_encrypted_format_3_type_#{type}") do
|
110
|
+
ppk = PuTTY::Key::PPK.new(fixture_path("test-encrypted-type-#{type}-format-3.ppk"), 'Test Passphrase')
|
111
|
+
assert_test_ppk_properties(ppk, encrypted: true)
|
112
|
+
end
|
71
113
|
end
|
72
114
|
|
73
115
|
def test_initialize_blank_comment
|
@@ -75,27 +117,56 @@ class PPKTest < Minitest::Test
|
|
75
117
|
assert_test_ppk_properties(ppk, comment: ''.b)
|
76
118
|
end
|
77
119
|
|
78
|
-
def
|
79
|
-
ppk = PuTTY::Key::PPK.new(fixture_path('test-
|
80
|
-
assert_test_ppk_properties(ppk, encrypted: true)
|
120
|
+
def test_initialize_empty_blobs
|
121
|
+
ppk = PuTTY::Key::PPK.new(fixture_path('test-empty-blobs.ppk'))
|
122
|
+
assert_test_ppk_properties(ppk, public_blob: ''.b, private_blob: ''.b, encrypted: true)
|
81
123
|
end
|
82
124
|
|
83
|
-
def
|
84
|
-
|
125
|
+
def test_initialize_empty_blobs_encrypted
|
126
|
+
ppk = PuTTY::Key::PPK.new(fixture_path('test-empty-blobs-encrypted.ppk'), 'Test Passphrase')
|
127
|
+
assert_test_ppk_properties(ppk, public_blob: ''.b, private_blob: ''.b, encrypted: true)
|
85
128
|
end
|
86
129
|
|
87
|
-
def
|
88
|
-
|
130
|
+
def test_initialize_missing_final_line_ending
|
131
|
+
ppk = PuTTY::Key::PPK.new(fixture_path('test-missing-final-line-ending.ppk'))
|
132
|
+
assert_test_ppk_properties(ppk)
|
89
133
|
end
|
90
134
|
|
91
135
|
def test_initialize_pathname
|
92
|
-
ppk = PuTTY::Key::PPK.new(Pathname.new(fixture_path('test.ppk')))
|
136
|
+
ppk = PuTTY::Key::PPK.new(Pathname.new(fixture_path('test-format-2.ppk')))
|
93
137
|
assert_test_ppk_properties(ppk)
|
94
138
|
end
|
95
139
|
|
96
|
-
def
|
97
|
-
|
98
|
-
|
140
|
+
def test_initialize_from_io_with_getbyte
|
141
|
+
File.open(fixture_path('test-format-2.ppk'), 'rb') do |file|
|
142
|
+
reader = TestReaderWithGetbyte.new(file)
|
143
|
+
ppk = PuTTY::Key::PPK.new(reader)
|
144
|
+
assert_test_ppk_properties(ppk)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def test_initialize_from_io_with_read
|
149
|
+
File.open(fixture_path('test-format-2.ppk'), 'rb') do |file|
|
150
|
+
reader = TestReaderWithRead.new(file)
|
151
|
+
ppk = PuTTY::Key::PPK.new(reader)
|
152
|
+
assert_test_ppk_properties(ppk)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def test_initialize_from_io_with_binmode
|
157
|
+
File.open(fixture_path('test-format-2.ppk'), 'r') do |file|
|
158
|
+
reader = TestReaderWithBinmode.new(file)
|
159
|
+
ppk = PuTTY::Key::PPK.new(reader)
|
160
|
+
assert_equal(1, reader.binmode_calls)
|
161
|
+
assert_test_ppk_properties(ppk)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
%w(legacy_mac windows).each do |type|
|
166
|
+
define_method("test_initialize_#{type}_line_endings") do
|
167
|
+
ppk = PuTTY::Key::PPK.new(fixture_path("test-#{type.gsub('_', '-')}-line-endings.ppk"))
|
168
|
+
assert_test_ppk_properties(ppk)
|
169
|
+
end
|
99
170
|
end
|
100
171
|
|
101
172
|
def create_test_ppk
|
@@ -140,10 +211,10 @@ class PPKTest < Minitest::Test
|
|
140
211
|
end
|
141
212
|
end
|
142
213
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
214
|
+
[PuTTY::Key::PPK::MINIMUM_FORMAT - 1, PuTTY::Key::PPK::MAXIMUM_FORMAT + 1].each do |format|
|
215
|
+
define_method("test_save_format_#{format}_not_supported") do
|
216
|
+
ppk = create_test_ppk
|
217
|
+
temp_file_name do |file|
|
147
218
|
assert_raises(ArgumentError) { ppk.save(file, 'Test Passphrase', format: format) }
|
148
219
|
end
|
149
220
|
end
|
@@ -180,27 +251,93 @@ class PPKTest < Minitest::Test
|
|
180
251
|
end
|
181
252
|
end
|
182
253
|
|
183
|
-
|
254
|
+
[2, 3].each do |format|
|
255
|
+
define_method("test_save_unencrypted_format_#{format}") do
|
256
|
+
ppk = create_test_ppk
|
257
|
+
temp_file_name do |file|
|
258
|
+
ppk.save(file, format: format)
|
259
|
+
assert_identical_to_fixture("test-format-#{format}.ppk", file)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
define_method("test_save_passphrase_empty_format_#{format}") do
|
264
|
+
ppk = create_test_ppk
|
265
|
+
temp_file_name do |file|
|
266
|
+
ppk.save(file, '', format: format)
|
267
|
+
assert_identical_to_fixture("test-format-#{format}.ppk", file)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
define_method("test_save_encrypted_format_#{format}") do
|
272
|
+
ppk = create_test_ppk
|
273
|
+
temp_file_name do |file|
|
274
|
+
ppk.save(file, 'Test Passphrase', format: format, argon2_params: PuTTY::Key::Argon2Params.new(passes: 8, salt: "\x7d\x5d\x45\x57\xc5\x56\x3a\x5b\x50\x09\xe1\x45\x2c\x51\x8e\x04".b))
|
275
|
+
assert_identical_to_fixture("test-encrypted-format-#{format}.ppk", file)
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
# type_d and type_i tests cover other Argon2 parameters too.
|
281
|
+
def test_save_encrypted_type_d_format_3
|
184
282
|
ppk = create_test_ppk
|
185
283
|
temp_file_name do |file|
|
186
|
-
ppk.save(file)
|
187
|
-
assert_identical_to_fixture(
|
284
|
+
ppk.save(file, 'Test Passphrase', format: 3, argon2_params: PuTTY::Key::Argon2Params.new(type: :d, memory: 4096, passes: 9, salt: "\xbc\x44\x19\x1a\xa9\x26\x73\xa5\xc0\x54\x3f\x37\x36\x33\xdd\xf4".b ))
|
285
|
+
assert_identical_to_fixture("test-encrypted-type-d-format-3.ppk", file)
|
188
286
|
end
|
189
287
|
end
|
190
288
|
|
191
|
-
def
|
289
|
+
def test_save_encrypted_type_i_format_3
|
192
290
|
ppk = create_test_ppk
|
193
291
|
temp_file_name do |file|
|
194
|
-
ppk.save(file, '')
|
195
|
-
assert_identical_to_fixture(
|
292
|
+
ppk.save(file, 'Test Passphrase', format: 3, argon2_params: PuTTY::Key::Argon2Params.new(type: :i, memory: 2048, passes: 5, parallelism: 3, salt: "\xbd\x5e\x3d\x94\x03\xec\x37\x41\x8b\xa5\xae\x1d\x11\x6f\xa9\x75".b ))
|
293
|
+
assert_identical_to_fixture("test-encrypted-type-i-format-3.ppk", file)
|
196
294
|
end
|
197
295
|
end
|
198
296
|
|
199
|
-
def
|
297
|
+
def get_field(file, name)
|
298
|
+
line = File.readlines(file, mode: 'rb').find {|l| l.start_with?("#{name}: ")}
|
299
|
+
line && line.byteslice(name.bytesize + 2, line.bytesize - name.bytesize - 2).chomp("\n")
|
300
|
+
end
|
301
|
+
|
302
|
+
def test_save_chooses_random_salt
|
200
303
|
ppk = create_test_ppk
|
201
304
|
temp_file_name do |file|
|
202
|
-
ppk.save(file, 'Test Passphrase')
|
203
|
-
|
305
|
+
ppk.save(file, 'Test Passphrase', format: 3)
|
306
|
+
salt1 = get_field(file, 'Argon2-Salt')
|
307
|
+
assert_match(/\A[0-9a-f]{32}\z/, salt1)
|
308
|
+
|
309
|
+
File.unlink(file)
|
310
|
+
ppk.save(file, 'Test Passphrase', format: 3)
|
311
|
+
salt2 = get_field(file, 'Argon2-Salt')
|
312
|
+
assert_match(/\A[0-9a-f]{32}\z/, salt2)
|
313
|
+
|
314
|
+
refute_equal(salt1, salt2)
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
def test_save_calculates_passes_required_for_time
|
319
|
+
ppk = create_test_ppk
|
320
|
+
temp_file_name do |file|
|
321
|
+
ppk.save(file, 'Test Passphrase', format: 3, argon2_params: PuTTY::Key::Argon2Params.new(desired_time: 0))
|
322
|
+
passes = get_field(file, 'Argon2-Passes').to_i
|
323
|
+
initial_passes = passes
|
324
|
+
|
325
|
+
100.step(by: 100, to: 1000) do |desired_time|
|
326
|
+
File.unlink(file)
|
327
|
+
ppk.save(file, 'Test Passphrase', format: 3, argon2_params: PuTTY::Key::Argon2Params.new(desired_time: desired_time))
|
328
|
+
passes = get_field(file, 'Argon2-Passes').to_i
|
329
|
+
break if passes > initial_passes
|
330
|
+
end
|
331
|
+
|
332
|
+
assert(passes > initial_passes)
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
def test_save_raises_error_if_libargon2_rejects_parameters
|
337
|
+
ppk = create_test_ppk
|
338
|
+
temp_file_name do |file|
|
339
|
+
argon2_params = PuTTY::Key::Argon2Params.new(memory: 1)
|
340
|
+
assert_raises(PuTTY::Key::Argon2Error) { ppk.save(file, 'Test Passphrase', format: 3, argon2_params: argon2_params) }
|
204
341
|
end
|
205
342
|
end
|
206
343
|
|
@@ -222,11 +359,91 @@ class PPKTest < Minitest::Test
|
|
222
359
|
end
|
223
360
|
end
|
224
361
|
|
362
|
+
def test_save_empty_blobs
|
363
|
+
ppk = create_test_ppk
|
364
|
+
ppk.public_blob = ''.b
|
365
|
+
ppk.private_blob = ''.b
|
366
|
+
temp_file_name do |file|
|
367
|
+
ppk.save(file)
|
368
|
+
assert_identical_to_fixture('test-empty-blobs.ppk', file)
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
def test_save_empty_blobs_encrypted
|
373
|
+
ppk = create_test_ppk
|
374
|
+
ppk.public_blob = ''.b
|
375
|
+
ppk.private_blob = ''.b
|
376
|
+
temp_file_name do |file|
|
377
|
+
ppk.save(file, 'Test Passphrase')
|
378
|
+
assert_identical_to_fixture('test-empty-blobs-encrypted.ppk', file)
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
def get_blob(file, name)
|
383
|
+
lines = File.readlines(file, mode: 'rb')
|
384
|
+
index = lines.find_index {|l| l.start_with?("#{name}-Lines: ")}
|
385
|
+
return nil unless index
|
386
|
+
line = lines[index]
|
387
|
+
count = line.byteslice(name.bytesize + 8, line.bytesize - name.bytesize - 2).chomp("\n").to_i
|
388
|
+
blob_lines = lines[index + 1, count]
|
389
|
+
blob_lines.join("\n").unpack('m48').first
|
390
|
+
end
|
391
|
+
|
392
|
+
[[0, 0], [15, 1], [16, 0], [17, 15], [18, 14], [30, 2], [31, 1], [32, 0]].each do |length, needed|
|
393
|
+
define_method("test_save_encrypted_pads_private_blob_of_length_#{length}_to_multiple_of_block_size_with_sha1") do
|
394
|
+
private_blob = "\0".b * length
|
395
|
+
ppk = create_test_ppk
|
396
|
+
ppk.private_blob = private_blob
|
397
|
+
|
398
|
+
temp_file_name do |file|
|
399
|
+
ppk.save(file, 'Test Passphrase')
|
400
|
+
encrypted_padded_private_blob = get_blob(file, 'Private')
|
401
|
+
assert_equal(length + needed, encrypted_padded_private_blob.bytesize)
|
402
|
+
assert_equal(private_blob, ppk.private_blob)
|
403
|
+
|
404
|
+
loaded_ppk = PuTTY::Key::PPK.new(file, 'Test Passphrase')
|
405
|
+
assert_equal(length + needed, loaded_ppk.private_blob.bytesize)
|
406
|
+
|
407
|
+
if needed == 0
|
408
|
+
assert_equal(private_blob, loaded_ppk.private_blob)
|
409
|
+
else
|
410
|
+
assert_equal(private_blob, loaded_ppk.private_blob.byteslice(0, length))
|
411
|
+
padding = loaded_ppk.private_blob.byteslice(length, needed)
|
412
|
+
expected_padding = OpenSSL::Digest::SHA1.new(private_blob).digest.byteslice(0, needed)
|
413
|
+
assert_equal(expected_padding, padding)
|
414
|
+
end
|
415
|
+
end
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
225
419
|
def test_save_pathname
|
226
420
|
ppk = create_test_ppk
|
227
421
|
temp_file_name do |file|
|
228
422
|
ppk.save(Pathname.new(file))
|
229
|
-
assert_identical_to_fixture('test.ppk', file)
|
423
|
+
assert_identical_to_fixture('test-format-2.ppk', file)
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
def test_save_to_io
|
428
|
+
ppk = create_test_ppk
|
429
|
+
temp_file_name do |file_name|
|
430
|
+
File.open(file_name, 'wb') do |file|
|
431
|
+
writer = TestWriter.new(file)
|
432
|
+
ppk.save(writer)
|
433
|
+
end
|
434
|
+
assert_identical_to_fixture('test-format-2.ppk', file_name)
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
438
|
+
def test_save_to_io_with_binmode
|
439
|
+
ppk = create_test_ppk
|
440
|
+
temp_file_name do |file_name|
|
441
|
+
File.open(file_name, 'w') do |file|
|
442
|
+
writer = TestWriterWithBinmode.new(file)
|
443
|
+
ppk.save(writer)
|
444
|
+
assert_equal(1, writer.binmode_calls)
|
445
|
+
end
|
446
|
+
assert_identical_to_fixture('test-format-2.ppk', file_name)
|
230
447
|
end
|
231
448
|
end
|
232
449
|
|
@@ -235,7 +452,7 @@ class PPKTest < Minitest::Test
|
|
235
452
|
temp_file_name do |file|
|
236
453
|
File.open(file, 'w') { |f| f.write('not test.ppk') }
|
237
454
|
ppk.save(file)
|
238
|
-
assert_identical_to_fixture('test.ppk', file)
|
455
|
+
assert_identical_to_fixture('test-format-2.ppk', file)
|
239
456
|
end
|
240
457
|
end
|
241
458
|
|
@@ -246,4 +463,68 @@ class PPKTest < Minitest::Test
|
|
246
463
|
assert_equal(File.size(file), result)
|
247
464
|
end
|
248
465
|
end
|
466
|
+
|
467
|
+
module BinmodeCallsTest
|
468
|
+
def binmode
|
469
|
+
if instance_variable_defined?(:@binmode_calls)
|
470
|
+
@binmode_calls += 1
|
471
|
+
else
|
472
|
+
@binmode_calls = 1
|
473
|
+
end
|
474
|
+
@io.binmode
|
475
|
+
self
|
476
|
+
end
|
477
|
+
|
478
|
+
def binmode_calls
|
479
|
+
instance_variable_defined?(:@binmode_calls) ? @binmode_calls : 0
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
class TestReaderWithGetbyte
|
484
|
+
def initialize(io)
|
485
|
+
@io = io
|
486
|
+
end
|
487
|
+
|
488
|
+
def getbyte(*args)
|
489
|
+
@io.getbyte(*args)
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
class TestReaderWithRead
|
494
|
+
def initialize(io)
|
495
|
+
@io = io
|
496
|
+
end
|
497
|
+
|
498
|
+
def read(*args)
|
499
|
+
@io.read(*args)
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
class TestReaderWithBinmode < TestReaderWithGetbyte
|
504
|
+
include BinmodeCallsTest
|
505
|
+
|
506
|
+
def getbyte(*args)
|
507
|
+
raise 'binmode must be called before getbyte' unless binmode_calls > 0
|
508
|
+
super
|
509
|
+
end
|
510
|
+
end
|
511
|
+
|
512
|
+
class TestWriter
|
513
|
+
def initialize(io)
|
514
|
+
@io = io
|
515
|
+
end
|
516
|
+
|
517
|
+
def write(*args)
|
518
|
+
@io.write(*args)
|
519
|
+
end
|
520
|
+
end
|
521
|
+
|
522
|
+
class TestWriterWithBinmode < TestWriter
|
523
|
+
include BinmodeCallsTest
|
524
|
+
|
525
|
+
def write(*args)
|
526
|
+
raise 'binmode must be called before write' unless binmode_calls > 0
|
527
|
+
super
|
528
|
+
end
|
529
|
+
end
|
249
530
|
end
|