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