easy_code_sign 0.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 +7 -0
- data/CHANGELOG.md +95 -0
- data/LICENSE +21 -0
- data/README.md +331 -0
- data/Rakefile +16 -0
- data/exe/easysign +7 -0
- data/lib/easy_code_sign/cli.rb +428 -0
- data/lib/easy_code_sign/configuration.rb +102 -0
- data/lib/easy_code_sign/deferred_signing_request.rb +104 -0
- data/lib/easy_code_sign/errors.rb +113 -0
- data/lib/easy_code_sign/pdf/appearance_builder.rb +104 -0
- data/lib/easy_code_sign/pdf/timestamp_handler.rb +31 -0
- data/lib/easy_code_sign/providers/base.rb +126 -0
- data/lib/easy_code_sign/providers/pkcs11_base.rb +197 -0
- data/lib/easy_code_sign/providers/safenet.rb +109 -0
- data/lib/easy_code_sign/signable/base.rb +98 -0
- data/lib/easy_code_sign/signable/gem_file.rb +224 -0
- data/lib/easy_code_sign/signable/pdf_file.rb +486 -0
- data/lib/easy_code_sign/signable/zip_file.rb +226 -0
- data/lib/easy_code_sign/signer.rb +254 -0
- data/lib/easy_code_sign/timestamp/client.rb +184 -0
- data/lib/easy_code_sign/timestamp/request.rb +114 -0
- data/lib/easy_code_sign/timestamp/response.rb +246 -0
- data/lib/easy_code_sign/timestamp/verifier.rb +227 -0
- data/lib/easy_code_sign/verification/certificate_chain.rb +298 -0
- data/lib/easy_code_sign/verification/result.rb +222 -0
- data/lib/easy_code_sign/verification/signature_checker.rb +196 -0
- data/lib/easy_code_sign/verification/trust_store.rb +140 -0
- data/lib/easy_code_sign/verifier.rb +426 -0
- data/lib/easy_code_sign/version.rb +5 -0
- data/lib/easy_code_sign.rb +183 -0
- data/plugin/.gitignore +21 -0
- data/plugin/Gemfile +24 -0
- data/plugin/Gemfile.lock +134 -0
- data/plugin/README.md +248 -0
- data/plugin/Rakefile +121 -0
- data/plugin/docs/API_REFERENCE.md +366 -0
- data/plugin/docs/DEVELOPMENT.md +522 -0
- data/plugin/docs/INSTALLATION.md +204 -0
- data/plugin/native_host/build/Rakefile +90 -0
- data/plugin/native_host/install/com.easysign.host.json +9 -0
- data/plugin/native_host/install/install_chrome.sh +81 -0
- data/plugin/native_host/install/install_firefox.sh +81 -0
- data/plugin/native_host/src/easy_sign_host.rb +158 -0
- data/plugin/native_host/src/protocol.rb +101 -0
- data/plugin/native_host/src/signing_service.rb +167 -0
- data/plugin/native_host/test/native_host_test.rb +113 -0
- data/plugin/src/easy_sign/background.rb +323 -0
- data/plugin/src/easy_sign/content.rb +74 -0
- data/plugin/src/easy_sign/inject.rb +239 -0
- data/plugin/src/easy_sign/messaging.rb +109 -0
- data/plugin/src/easy_sign/popup.rb +200 -0
- data/plugin/templates/manifest.json +58 -0
- data/plugin/templates/popup.css +223 -0
- data/plugin/templates/popup.html +59 -0
- data/sig/easy_code_sign.rbs +4 -0
- data/test/easy_code_sign_test.rb +122 -0
- data/test/pdf_signable_test.rb +569 -0
- data/test/signable_test.rb +334 -0
- data/test/test_helper.rb +18 -0
- data/test/timestamp_test.rb +163 -0
- data/test/verification_test.rb +350 -0
- metadata +219 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
require "tempfile"
|
|
5
|
+
require "zip"
|
|
6
|
+
|
|
7
|
+
class SignableBaseTest < Minitest::Test
|
|
8
|
+
def test_raises_for_nonexistent_file
|
|
9
|
+
assert_raises(EasyCodeSign::InvalidFileError) do
|
|
10
|
+
EasyCodeSign::Signable::Base.new("/nonexistent/file.txt")
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def test_default_hash_algorithm_is_sha256
|
|
15
|
+
file = Tempfile.new(["test", ".txt"])
|
|
16
|
+
file.write("test content")
|
|
17
|
+
file.close
|
|
18
|
+
|
|
19
|
+
signable = TestSignable.new(file.path)
|
|
20
|
+
assert_equal :sha256, signable.hash_algorithm
|
|
21
|
+
ensure
|
|
22
|
+
file.unlink
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def test_compute_hash_returns_correct_digest
|
|
26
|
+
file = Tempfile.new(["test", ".txt"])
|
|
27
|
+
file.write("test content")
|
|
28
|
+
file.close
|
|
29
|
+
|
|
30
|
+
signable = TestSignable.new(file.path)
|
|
31
|
+
expected = OpenSSL::Digest::SHA256.digest("hello")
|
|
32
|
+
assert_equal expected, signable.compute_hash("hello")
|
|
33
|
+
ensure
|
|
34
|
+
file.unlink
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def test_signature_algorithm_for_rsa
|
|
38
|
+
file = Tempfile.new(["test", ".txt"])
|
|
39
|
+
file.close
|
|
40
|
+
|
|
41
|
+
signable = TestSignable.new(file.path)
|
|
42
|
+
assert_equal :sha256_rsa, signable.signature_algorithm(:rsa)
|
|
43
|
+
ensure
|
|
44
|
+
file.unlink
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Concrete implementation for testing abstract base
|
|
48
|
+
class TestSignable < EasyCodeSign::Signable::Base
|
|
49
|
+
def prepare_for_signing; end
|
|
50
|
+
def content_to_sign; "test"; end
|
|
51
|
+
def apply_signature(sig, chain, timestamp_token: nil); file_path; end
|
|
52
|
+
def extract_signature; nil; end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
class ZipFileSignableTest < Minitest::Test
|
|
57
|
+
def setup
|
|
58
|
+
@temp_zip = Tempfile.new(["test", ".zip"])
|
|
59
|
+
create_test_zip(@temp_zip.path)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def teardown
|
|
63
|
+
@temp_zip.close
|
|
64
|
+
@temp_zip.unlink
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def test_validates_zip_extension
|
|
68
|
+
txt_file = Tempfile.new(["test", ".txt"])
|
|
69
|
+
txt_file.write("content")
|
|
70
|
+
txt_file.close
|
|
71
|
+
|
|
72
|
+
assert_raises(EasyCodeSign::InvalidFileError) do
|
|
73
|
+
EasyCodeSign::Signable::ZipFile.new(txt_file.path)
|
|
74
|
+
end
|
|
75
|
+
ensure
|
|
76
|
+
txt_file.unlink
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def test_accepts_valid_zip_file
|
|
80
|
+
signable = EasyCodeSign::Signable::ZipFile.new(@temp_zip.path)
|
|
81
|
+
assert_instance_of EasyCodeSign::Signable::ZipFile, signable
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def test_file_list_returns_zip_contents
|
|
85
|
+
signable = EasyCodeSign::Signable::ZipFile.new(@temp_zip.path)
|
|
86
|
+
files = signable.file_list
|
|
87
|
+
|
|
88
|
+
assert_includes files, "hello.txt"
|
|
89
|
+
assert_includes files, "subdir/world.txt"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def test_prepare_for_signing_builds_manifest
|
|
93
|
+
signable = EasyCodeSign::Signable::ZipFile.new(@temp_zip.path)
|
|
94
|
+
signable.prepare_for_signing
|
|
95
|
+
|
|
96
|
+
# Should not raise and content_to_sign should return data
|
|
97
|
+
refute_nil signable.content_to_sign
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def test_signed_returns_false_for_unsigned_zip
|
|
101
|
+
signable = EasyCodeSign::Signable::ZipFile.new(@temp_zip.path)
|
|
102
|
+
refute signable.signed?
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def test_extract_signature_returns_nil_for_unsigned
|
|
106
|
+
signable = EasyCodeSign::Signable::ZipFile.new(@temp_zip.path)
|
|
107
|
+
assert_nil signable.extract_signature
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
private
|
|
111
|
+
|
|
112
|
+
def create_test_zip(path)
|
|
113
|
+
Zip::File.open(path, Zip::File::CREATE) do |zip|
|
|
114
|
+
zip.get_output_stream("hello.txt") { |f| f.write("Hello, World!") }
|
|
115
|
+
zip.get_output_stream("subdir/world.txt") { |f| f.write("Nested file") }
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
class GemFileSignableTest < Minitest::Test
|
|
121
|
+
def setup
|
|
122
|
+
@temp_gem = Tempfile.new(["test", ".gem"])
|
|
123
|
+
create_test_gem(@temp_gem.path)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def teardown
|
|
127
|
+
@temp_gem.close
|
|
128
|
+
@temp_gem.unlink
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def test_validates_gem_extension
|
|
132
|
+
txt_file = Tempfile.new(["test", ".txt"])
|
|
133
|
+
txt_file.write("content")
|
|
134
|
+
txt_file.close
|
|
135
|
+
|
|
136
|
+
assert_raises(EasyCodeSign::InvalidFileError) do
|
|
137
|
+
EasyCodeSign::Signable::GemFile.new(txt_file.path)
|
|
138
|
+
end
|
|
139
|
+
ensure
|
|
140
|
+
txt_file.unlink
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def test_accepts_valid_gem_file
|
|
144
|
+
signable = EasyCodeSign::Signable::GemFile.new(@temp_gem.path)
|
|
145
|
+
assert_instance_of EasyCodeSign::Signable::GemFile, signable
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def test_signed_returns_false_for_unsigned_gem
|
|
149
|
+
signable = EasyCodeSign::Signable::GemFile.new(@temp_gem.path)
|
|
150
|
+
refute signable.signed?
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def test_prepare_for_signing_extracts_content
|
|
154
|
+
signable = EasyCodeSign::Signable::GemFile.new(@temp_gem.path)
|
|
155
|
+
signable.prepare_for_signing
|
|
156
|
+
|
|
157
|
+
refute_nil signable.content_to_sign
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
private
|
|
161
|
+
|
|
162
|
+
def create_test_gem(path)
|
|
163
|
+
# Create a minimal gem-like tar archive
|
|
164
|
+
File.open(path, "wb") do |io|
|
|
165
|
+
Gem::Package::TarWriter.new(io) do |tar|
|
|
166
|
+
# Add a fake data.tar.gz
|
|
167
|
+
data = "fake gem data content"
|
|
168
|
+
tar.add_file_simple("data.tar.gz", 0o644, data.bytesize) { |f| f.write(data) }
|
|
169
|
+
|
|
170
|
+
# Add a fake metadata.gz
|
|
171
|
+
metadata = "fake metadata content"
|
|
172
|
+
tar.add_file_simple("metadata.gz", 0o644, metadata.bytesize) { |f| f.write(metadata) }
|
|
173
|
+
|
|
174
|
+
# Add checksums
|
|
175
|
+
checksums = "checksums content"
|
|
176
|
+
tar.add_file_simple("checksums.yaml.gz", 0o644, checksums.bytesize) { |f| f.write(checksums) }
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
class SignerTest < Minitest::Test
|
|
183
|
+
def test_creates_signable_for_gem_extension
|
|
184
|
+
signer = EasyCodeSign::Signer.new
|
|
185
|
+
|
|
186
|
+
temp_gem = Tempfile.new(["test", ".gem"])
|
|
187
|
+
create_minimal_gem(temp_gem.path)
|
|
188
|
+
|
|
189
|
+
signable = signer.send(:create_signable, temp_gem.path)
|
|
190
|
+
assert_instance_of EasyCodeSign::Signable::GemFile, signable
|
|
191
|
+
ensure
|
|
192
|
+
temp_gem&.close
|
|
193
|
+
temp_gem&.unlink
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def test_creates_signable_for_zip_extension
|
|
197
|
+
signer = EasyCodeSign::Signer.new
|
|
198
|
+
|
|
199
|
+
temp_zip = Tempfile.new(["test", ".zip"])
|
|
200
|
+
Zip::File.open(temp_zip.path, Zip::File::CREATE) do |zip|
|
|
201
|
+
zip.get_output_stream("test.txt") { |f| f.write("test") }
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
signable = signer.send(:create_signable, temp_zip.path)
|
|
205
|
+
assert_instance_of EasyCodeSign::Signable::ZipFile, signable
|
|
206
|
+
ensure
|
|
207
|
+
temp_zip&.close
|
|
208
|
+
temp_zip&.unlink
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def test_raises_for_unsupported_extension
|
|
212
|
+
signer = EasyCodeSign::Signer.new
|
|
213
|
+
|
|
214
|
+
temp_file = Tempfile.new(["test", ".exe"])
|
|
215
|
+
temp_file.close
|
|
216
|
+
|
|
217
|
+
assert_raises(EasyCodeSign::InvalidFileError) do
|
|
218
|
+
signer.send(:create_signable, temp_file.path)
|
|
219
|
+
end
|
|
220
|
+
ensure
|
|
221
|
+
temp_file&.unlink
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
private
|
|
225
|
+
|
|
226
|
+
def create_minimal_gem(path)
|
|
227
|
+
File.open(path, "wb") do |io|
|
|
228
|
+
Gem::Package::TarWriter.new(io) do |tar|
|
|
229
|
+
tar.add_file_simple("data.tar.gz", 0o644, 4) { |f| f.write("data") }
|
|
230
|
+
tar.add_file_simple("metadata.gz", 0o644, 4) { |f| f.write("meta") }
|
|
231
|
+
tar.add_file_simple("checksums.yaml.gz", 0o644, 4) { |f| f.write("sums") }
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
class SigningResultTest < Minitest::Test
|
|
238
|
+
def test_timestamped_returns_true_when_timestamp_present
|
|
239
|
+
cert = OpenSSL::X509::Certificate.new
|
|
240
|
+
cert.subject = OpenSSL::X509::Name.parse("/CN=Test Signer")
|
|
241
|
+
|
|
242
|
+
token = EasyCodeSign::Timestamp::TimestampToken.new(
|
|
243
|
+
token_der: "fake",
|
|
244
|
+
timestamp: Time.now,
|
|
245
|
+
serial_number: 1,
|
|
246
|
+
policy_oid: "1.2.3",
|
|
247
|
+
tsa_url: "http://tsa.example.com"
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
result = EasyCodeSign::SigningResult.new(
|
|
251
|
+
file_path: "/path/to/file",
|
|
252
|
+
certificate: cert,
|
|
253
|
+
algorithm: :sha256_rsa,
|
|
254
|
+
timestamp_token: token,
|
|
255
|
+
signed_at: Time.now
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
assert result.timestamped?
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def test_timestamped_returns_false_when_no_timestamp
|
|
262
|
+
cert = OpenSSL::X509::Certificate.new
|
|
263
|
+
cert.subject = OpenSSL::X509::Name.parse("/CN=Test Signer")
|
|
264
|
+
|
|
265
|
+
result = EasyCodeSign::SigningResult.new(
|
|
266
|
+
file_path: "/path/to/file",
|
|
267
|
+
certificate: cert,
|
|
268
|
+
algorithm: :sha256_rsa,
|
|
269
|
+
timestamp_token: nil,
|
|
270
|
+
signed_at: Time.now
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
refute result.timestamped?
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def test_signer_name_returns_certificate_subject
|
|
277
|
+
cert = OpenSSL::X509::Certificate.new
|
|
278
|
+
cert.subject = OpenSSL::X509::Name.parse("/CN=Test Signer/O=Test Org")
|
|
279
|
+
|
|
280
|
+
result = EasyCodeSign::SigningResult.new(
|
|
281
|
+
file_path: "/path/to/file",
|
|
282
|
+
certificate: cert,
|
|
283
|
+
algorithm: :sha256_rsa,
|
|
284
|
+
timestamp_token: nil,
|
|
285
|
+
signed_at: Time.now
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
assert_includes result.signer_name, "Test Signer"
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def test_to_h_returns_hash_representation
|
|
292
|
+
cert = OpenSSL::X509::Certificate.new
|
|
293
|
+
cert.subject = OpenSSL::X509::Name.parse("/CN=Test")
|
|
294
|
+
signed_at = Time.now
|
|
295
|
+
|
|
296
|
+
result = EasyCodeSign::SigningResult.new(
|
|
297
|
+
file_path: "/path/to/file.gem",
|
|
298
|
+
certificate: cert,
|
|
299
|
+
algorithm: :sha256_rsa,
|
|
300
|
+
timestamp_token: nil,
|
|
301
|
+
signed_at: signed_at
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
hash = result.to_h
|
|
305
|
+
assert_equal "/path/to/file.gem", hash[:file_path]
|
|
306
|
+
assert_equal :sha256_rsa, hash[:algorithm]
|
|
307
|
+
assert_equal false, hash[:timestamped]
|
|
308
|
+
assert_equal signed_at, hash[:signed_at]
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def test_timestamp_returns_time_from_token
|
|
312
|
+
cert = OpenSSL::X509::Certificate.new
|
|
313
|
+
cert.subject = OpenSSL::X509::Name.parse("/CN=Test")
|
|
314
|
+
ts_time = Time.utc(2024, 1, 15, 12, 0, 0)
|
|
315
|
+
|
|
316
|
+
token = EasyCodeSign::Timestamp::TimestampToken.new(
|
|
317
|
+
token_der: "fake",
|
|
318
|
+
timestamp: ts_time,
|
|
319
|
+
serial_number: 1,
|
|
320
|
+
policy_oid: "1.2.3",
|
|
321
|
+
tsa_url: "http://tsa.example.com"
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
result = EasyCodeSign::SigningResult.new(
|
|
325
|
+
file_path: "/path/to/file",
|
|
326
|
+
certificate: cert,
|
|
327
|
+
algorithm: :sha256_rsa,
|
|
328
|
+
timestamp_token: token,
|
|
329
|
+
signed_at: Time.now
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
assert_equal ts_time, result.timestamp
|
|
333
|
+
end
|
|
334
|
+
end
|
data/test/test_helper.rb
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
|
4
|
+
require "easy_code_sign"
|
|
5
|
+
require "minitest/autorun"
|
|
6
|
+
|
|
7
|
+
# Base test class with common helpers
|
|
8
|
+
class EasyCodeSignTest < Minitest::Test
|
|
9
|
+
def setup
|
|
10
|
+
EasyCodeSign.reset_configuration!
|
|
11
|
+
EasyCodeSign.reset_provider!
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def teardown
|
|
15
|
+
EasyCodeSign.reset_configuration!
|
|
16
|
+
EasyCodeSign.reset_provider!
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class TimestampRequestTest < Minitest::Test
|
|
6
|
+
def test_creates_request_with_defaults
|
|
7
|
+
request = EasyCodeSign::Timestamp::Request.new("test data")
|
|
8
|
+
|
|
9
|
+
assert_equal :sha256, request.algorithm
|
|
10
|
+
assert request.cert_req
|
|
11
|
+
assert_nil request.policy_oid
|
|
12
|
+
refute_nil request.nonce
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def test_computes_message_imprint_hash
|
|
16
|
+
data = "test signature data"
|
|
17
|
+
request = EasyCodeSign::Timestamp::Request.new(data, algorithm: :sha256)
|
|
18
|
+
|
|
19
|
+
expected = OpenSSL::Digest::SHA256.digest(data)
|
|
20
|
+
assert_equal expected, request.message_imprint_hash
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def test_supports_different_algorithms
|
|
24
|
+
data = "test data"
|
|
25
|
+
|
|
26
|
+
sha256_request = EasyCodeSign::Timestamp::Request.new(data, algorithm: :sha256)
|
|
27
|
+
sha384_request = EasyCodeSign::Timestamp::Request.new(data, algorithm: :sha384)
|
|
28
|
+
sha512_request = EasyCodeSign::Timestamp::Request.new(data, algorithm: :sha512)
|
|
29
|
+
|
|
30
|
+
assert_equal "2.16.840.1.101.3.4.2.1", sha256_request.algorithm_oid
|
|
31
|
+
assert_equal "2.16.840.1.101.3.4.2.2", sha384_request.algorithm_oid
|
|
32
|
+
assert_equal "2.16.840.1.101.3.4.2.3", sha512_request.algorithm_oid
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def test_to_der_produces_valid_asn1
|
|
36
|
+
request = EasyCodeSign::Timestamp::Request.new("test data")
|
|
37
|
+
der = request.to_der
|
|
38
|
+
|
|
39
|
+
# Should be parseable ASN.1
|
|
40
|
+
asn1 = OpenSSL::ASN1.decode(der)
|
|
41
|
+
assert_instance_of OpenSSL::ASN1::Sequence, asn1
|
|
42
|
+
|
|
43
|
+
# First element should be version (INTEGER 1)
|
|
44
|
+
assert_equal 1, asn1.value[0].value
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def test_generates_unique_nonces
|
|
48
|
+
request1 = EasyCodeSign::Timestamp::Request.new("data")
|
|
49
|
+
request2 = EasyCodeSign::Timestamp::Request.new("data")
|
|
50
|
+
|
|
51
|
+
refute_equal request1.nonce, request2.nonce
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def test_raises_for_unsupported_algorithm
|
|
55
|
+
assert_raises(ArgumentError) do
|
|
56
|
+
EasyCodeSign::Timestamp::Request.new("data", algorithm: :md5).algorithm_oid
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
class TimestampClientTest < Minitest::Test
|
|
62
|
+
def test_initializes_with_url
|
|
63
|
+
client = EasyCodeSign::Timestamp::Client.new("http://timestamp.example.com")
|
|
64
|
+
|
|
65
|
+
assert_equal "http://timestamp.example.com", client.url
|
|
66
|
+
assert_equal 30, client.timeout
|
|
67
|
+
assert_nil client.username
|
|
68
|
+
assert_nil client.password
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def test_initializes_with_custom_options
|
|
72
|
+
client = EasyCodeSign::Timestamp::Client.new(
|
|
73
|
+
"http://timestamp.example.com",
|
|
74
|
+
timeout: 60,
|
|
75
|
+
username: "user",
|
|
76
|
+
password: "pass"
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
assert_equal 60, client.timeout
|
|
80
|
+
assert_equal "user", client.username
|
|
81
|
+
assert_equal "pass", client.password
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def test_known_tsas_are_defined
|
|
85
|
+
assert_includes EasyCodeSign::Timestamp::Client::KNOWN_TSAS, :digicert
|
|
86
|
+
assert_includes EasyCodeSign::Timestamp::Client::KNOWN_TSAS, :globalsign
|
|
87
|
+
assert_includes EasyCodeSign::Timestamp::Client::KNOWN_TSAS, :sectigo
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
class TimestampTokenTest < Minitest::Test
|
|
92
|
+
def setup
|
|
93
|
+
@token = EasyCodeSign::Timestamp::TimestampToken.new(
|
|
94
|
+
token_der: "fake_der_data",
|
|
95
|
+
timestamp: Time.utc(2024, 1, 15, 12, 0, 0),
|
|
96
|
+
serial_number: 12345,
|
|
97
|
+
policy_oid: "1.2.3.4",
|
|
98
|
+
tsa_url: "http://timestamp.example.com"
|
|
99
|
+
)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def test_stores_all_attributes
|
|
103
|
+
assert_equal "fake_der_data", @token.token_der
|
|
104
|
+
assert_equal 12345, @token.serial_number
|
|
105
|
+
assert_equal "1.2.3.4", @token.policy_oid
|
|
106
|
+
assert_equal "http://timestamp.example.com", @token.tsa_url
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def test_timestamp_iso8601
|
|
110
|
+
assert_equal "2024-01-15T12:00:00Z", @token.timestamp_iso8601
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def test_to_h_returns_hash
|
|
114
|
+
hash = @token.to_h
|
|
115
|
+
|
|
116
|
+
assert_equal "2024-01-15T12:00:00Z", hash[:timestamp]
|
|
117
|
+
assert_equal 12345, hash[:serial_number]
|
|
118
|
+
assert_equal "1.2.3.4", hash[:policy_oid]
|
|
119
|
+
assert_equal "http://timestamp.example.com", hash[:tsa_url]
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
class TimestampVerifierTest < Minitest::Test
|
|
124
|
+
def test_initializes_with_default_trust_store
|
|
125
|
+
verifier = EasyCodeSign::Timestamp::Verifier.new
|
|
126
|
+
|
|
127
|
+
assert_instance_of OpenSSL::X509::Store, verifier.trust_store
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def test_initializes_with_custom_trust_store
|
|
131
|
+
custom_store = OpenSSL::X509::Store.new
|
|
132
|
+
verifier = EasyCodeSign::Timestamp::Verifier.new(trust_store: custom_store)
|
|
133
|
+
|
|
134
|
+
assert_same custom_store, verifier.trust_store
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
class TimestampVerificationResultTest < Minitest::Test
|
|
139
|
+
def test_initializes_with_default_values
|
|
140
|
+
result = EasyCodeSign::Timestamp::VerificationResult.new
|
|
141
|
+
|
|
142
|
+
refute result.valid?
|
|
143
|
+
refute result.token_parsed
|
|
144
|
+
refute result.signature_valid
|
|
145
|
+
refute result.imprint_valid
|
|
146
|
+
refute result.chain_valid
|
|
147
|
+
assert_empty result.errors
|
|
148
|
+
assert_empty result.warnings
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def test_to_h_returns_complete_hash
|
|
152
|
+
result = EasyCodeSign::Timestamp::VerificationResult.new
|
|
153
|
+
result.valid = true
|
|
154
|
+
result.timestamp = Time.utc(2024, 1, 15)
|
|
155
|
+
result.serial_number = 123
|
|
156
|
+
|
|
157
|
+
hash = result.to_h
|
|
158
|
+
|
|
159
|
+
assert hash[:valid]
|
|
160
|
+
assert_equal Time.utc(2024, 1, 15), hash[:timestamp]
|
|
161
|
+
assert_equal 123, hash[:serial_number]
|
|
162
|
+
end
|
|
163
|
+
end
|