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.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +95 -0
  3. data/LICENSE +21 -0
  4. data/README.md +331 -0
  5. data/Rakefile +16 -0
  6. data/exe/easysign +7 -0
  7. data/lib/easy_code_sign/cli.rb +428 -0
  8. data/lib/easy_code_sign/configuration.rb +102 -0
  9. data/lib/easy_code_sign/deferred_signing_request.rb +104 -0
  10. data/lib/easy_code_sign/errors.rb +113 -0
  11. data/lib/easy_code_sign/pdf/appearance_builder.rb +104 -0
  12. data/lib/easy_code_sign/pdf/timestamp_handler.rb +31 -0
  13. data/lib/easy_code_sign/providers/base.rb +126 -0
  14. data/lib/easy_code_sign/providers/pkcs11_base.rb +197 -0
  15. data/lib/easy_code_sign/providers/safenet.rb +109 -0
  16. data/lib/easy_code_sign/signable/base.rb +98 -0
  17. data/lib/easy_code_sign/signable/gem_file.rb +224 -0
  18. data/lib/easy_code_sign/signable/pdf_file.rb +486 -0
  19. data/lib/easy_code_sign/signable/zip_file.rb +226 -0
  20. data/lib/easy_code_sign/signer.rb +254 -0
  21. data/lib/easy_code_sign/timestamp/client.rb +184 -0
  22. data/lib/easy_code_sign/timestamp/request.rb +114 -0
  23. data/lib/easy_code_sign/timestamp/response.rb +246 -0
  24. data/lib/easy_code_sign/timestamp/verifier.rb +227 -0
  25. data/lib/easy_code_sign/verification/certificate_chain.rb +298 -0
  26. data/lib/easy_code_sign/verification/result.rb +222 -0
  27. data/lib/easy_code_sign/verification/signature_checker.rb +196 -0
  28. data/lib/easy_code_sign/verification/trust_store.rb +140 -0
  29. data/lib/easy_code_sign/verifier.rb +426 -0
  30. data/lib/easy_code_sign/version.rb +5 -0
  31. data/lib/easy_code_sign.rb +183 -0
  32. data/plugin/.gitignore +21 -0
  33. data/plugin/Gemfile +24 -0
  34. data/plugin/Gemfile.lock +134 -0
  35. data/plugin/README.md +248 -0
  36. data/plugin/Rakefile +121 -0
  37. data/plugin/docs/API_REFERENCE.md +366 -0
  38. data/plugin/docs/DEVELOPMENT.md +522 -0
  39. data/plugin/docs/INSTALLATION.md +204 -0
  40. data/plugin/native_host/build/Rakefile +90 -0
  41. data/plugin/native_host/install/com.easysign.host.json +9 -0
  42. data/plugin/native_host/install/install_chrome.sh +81 -0
  43. data/plugin/native_host/install/install_firefox.sh +81 -0
  44. data/plugin/native_host/src/easy_sign_host.rb +158 -0
  45. data/plugin/native_host/src/protocol.rb +101 -0
  46. data/plugin/native_host/src/signing_service.rb +167 -0
  47. data/plugin/native_host/test/native_host_test.rb +113 -0
  48. data/plugin/src/easy_sign/background.rb +323 -0
  49. data/plugin/src/easy_sign/content.rb +74 -0
  50. data/plugin/src/easy_sign/inject.rb +239 -0
  51. data/plugin/src/easy_sign/messaging.rb +109 -0
  52. data/plugin/src/easy_sign/popup.rb +200 -0
  53. data/plugin/templates/manifest.json +58 -0
  54. data/plugin/templates/popup.css +223 -0
  55. data/plugin/templates/popup.html +59 -0
  56. data/sig/easy_code_sign.rbs +4 -0
  57. data/test/easy_code_sign_test.rb +122 -0
  58. data/test/pdf_signable_test.rb +569 -0
  59. data/test/signable_test.rb +334 -0
  60. data/test/test_helper.rb +18 -0
  61. data/test/timestamp_test.rb +163 -0
  62. data/test/verification_test.rb +350 -0
  63. metadata +219 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 795cefa5ad0f01177caeb61d8aaa3c910e6796a5d0f7caad13653a762ada80c1
4
+ data.tar.gz: 4a65763e2aa2e0396c285c5bf3d75e3dc36697cf4e5f50b4489403a4e0789489
5
+ SHA512:
6
+ metadata.gz: 0ceaf7fcd326449fd314d493828ed33ca12b4c004f86e08cef72ab1f22212ce92525a4d9ee1aeed5f421dc8636c0504e7b78e75bb5530ef6da1e8aeda929002e
7
+ data.tar.gz: 0e59683b775e2809b102022b0f16dd3ed142e1fdb1296c83162e1f2fc0747b75440189ea43372f07518f99d7e95e2040af19713ea79fecaf47f03bd011c0fbf3
data/CHANGELOG.md ADDED
@@ -0,0 +1,95 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Added
11
+
12
+ - **PDF Document Signing**
13
+ - Sign PDF documents (.pdf) with PKCS#7 digital signatures
14
+ - Visible signature annotations with customizable position (top-left, top-right, bottom-left, bottom-right)
15
+ - Signature metadata (reason, location, contact info)
16
+ - Page selection for signature placement
17
+ - RFC 3161 timestamp embedding in PDF signatures
18
+ - ByteRange-based signing for PDF incremental updates
19
+
20
+ - **PDF Verification**
21
+ - Verify signed PDF documents
22
+ - ByteRange integrity checking
23
+ - Extract and display PDF signature metadata
24
+
25
+ - **CLI Enhancements for PDF**
26
+ - `--visible-signature` - Add visible signature annotation
27
+ - `--signature-page` - Select page for signature
28
+ - `--signature-position` - Position preset (top_left, top_right, bottom_left, bottom_right)
29
+ - `--signature-reason` - Reason for signing
30
+ - `--signature-location` - Signing location
31
+
32
+ ### Dependencies
33
+
34
+ - Added HexaPDF (~> 1.0) for PDF manipulation and signing
35
+
36
+ ## [0.1.0] - 2025-01-06
37
+
38
+ ### Added
39
+
40
+ - **Core Signing Functionality**
41
+ - Sign Ruby gems (.gem) with PKCS#7 detached signatures compatible with `gem cert`
42
+ - Sign ZIP archives (.zip, .jar, .apk, .war, .ear) using JAR-style signing with META-INF manifest
43
+ - Batch signing support for multiple files in a single token session
44
+
45
+ - **Hardware Token Support**
46
+ - SafeNet eToken integration via PKCS#11
47
+ - Extensible provider architecture for future HSM support
48
+ - Automatic PKCS#11 library detection on macOS, Linux, and Windows
49
+ - Secure PIN entry via interactive prompt (never passed as CLI argument)
50
+ - Token slot listing and management
51
+
52
+ - **RFC 3161 Timestamping**
53
+ - Full RFC 3161 timestamp protocol support
54
+ - Compatible with common TSAs (DigiCert, GlobalSign, Sectigo, SSL.com)
55
+ - Timestamp verification with certificate chain validation
56
+ - Configurable hash algorithms (SHA-256, SHA-384, SHA-512)
57
+
58
+ - **Signature Verification**
59
+ - Cryptographic signature validation
60
+ - File integrity checking (tamper detection)
61
+ - Certificate validity and expiration checking
62
+ - Certificate chain validation
63
+ - Trust anchor verification using system CA store or custom trust stores
64
+ - Timestamp validation for point-in-time verification
65
+ - Certificate revocation checking (OCSP with CRL fallback)
66
+
67
+ - **Command-Line Interface**
68
+ - `easysign sign` - Sign files with hardware token
69
+ - `easysign verify` - Verify signed files with detailed output
70
+ - `easysign list-slots` - List available token slots
71
+ - `easysign info` - Display signature information
72
+ - JSON output option for scripting and automation
73
+ - Verbose and quiet modes
74
+
75
+ - **Ruby API**
76
+ - Simple high-level API (`EasyCodeSign.sign`, `EasyCodeSign.verify`)
77
+ - Comprehensive configuration system
78
+ - Custom trust store support
79
+ - PIN callback for programmatic secure PIN entry
80
+ - Detailed result objects with structured error reporting
81
+
82
+ - **Error Handling**
83
+ - 18 specific error types for clear diagnostics
84
+ - Hierarchical error classes for flexible rescue
85
+ - PIN retry tracking with lockout warnings
86
+ - Network timeout handling for TSA requests
87
+
88
+ ### Security
89
+
90
+ - PINs are never logged, stored, or passed as command-line arguments
91
+ - Secure interactive PIN prompt using `noecho` to prevent display
92
+ - Private keys never leave the hardware token
93
+ - Certificate revocation checking enabled by default
94
+
95
+ [0.1.0]: https://github.com/mpantel/easy_code_sign/releases/tag/v0.1.0
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 michail
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,331 @@
1
+ # EasyCodeSign
2
+
3
+ A Ruby gem for signing and verifying Ruby gems, ZIP files, and PDF documents using hardware security tokens (HSM/smart cards). Currently supports SafeNet eToken with plans for additional providers.
4
+
5
+ ## Features
6
+
7
+ - **Sign Ruby gems** (.gem) - Creates PKCS#7 signatures compatible with `gem cert`
8
+ - **Sign ZIP archives** (.zip, .jar, .apk, .war, .ear) - JAR-style signing with META-INF manifest
9
+ - **Sign PDF documents** (.pdf) - Digital signatures with optional visible annotations
10
+ - **Hardware token support** - SafeNet eToken via PKCS#11 (extensible for other HSMs)
11
+ - **RFC 3161 timestamping** - Proves signature existed at a specific time
12
+ - **Full verification** - Signature, certificate chain, trust, and timestamp validation
13
+ - **Certificate revocation checking** - OCSP and CRL support
14
+ - **Command-line interface** - Easy-to-use CLI for signing and verification
15
+
16
+ ## Installation
17
+
18
+ Add to your Gemfile:
19
+
20
+ ```ruby
21
+ gem 'easy_code_sign'
22
+ ```
23
+
24
+ Or install directly:
25
+
26
+ ```bash
27
+ gem install easy_code_sign
28
+ ```
29
+
30
+ ### Prerequisites
31
+
32
+ - Ruby 3.2+
33
+ - SafeNet eToken drivers and PKCS#11 library installed
34
+ - Hardware token with code signing certificate
35
+
36
+ ## Command-Line Usage
37
+
38
+ ### Sign a file
39
+
40
+ ```bash
41
+ # Basic signing (will prompt for PIN securely)
42
+ easysign sign my_gem-1.0.0.gem
43
+
44
+ # Sign with timestamp
45
+ easysign sign my_gem-1.0.0.gem --timestamp --tsa http://timestamp.digicert.com
46
+
47
+ # Sign with custom output path
48
+ easysign sign archive.zip --output signed_archive.zip
49
+
50
+ # Use specific PKCS#11 library
51
+ easysign sign my_gem.gem --library /path/to/libeToken.dylib
52
+ ```
53
+
54
+ ### Sign a PDF
55
+
56
+ ```bash
57
+ # Basic PDF signing (invisible signature)
58
+ easysign sign document.pdf
59
+
60
+ # PDF with visible signature annotation
61
+ easysign sign document.pdf --visible-signature --signature-position bottom-right
62
+
63
+ # PDF with timestamp and metadata
64
+ easysign sign document.pdf -t --visible-signature --signature-reason "Approved" --signature-location "New York"
65
+
66
+ # PDF signing on specific page
67
+ easysign sign document.pdf --visible-signature --signature-page 2
68
+ ```
69
+
70
+ > **Security Note:** The PIN is always entered interactively via a secure prompt.
71
+ > It is never passed as a command-line argument to prevent exposure in shell
72
+ > history, process listings, or log files.
73
+
74
+ ### Verify a signature
75
+
76
+ ```bash
77
+ # Basic verification
78
+ easysign verify signed.gem
79
+
80
+ # Output as JSON
81
+ easysign verify signed.gem --json
82
+
83
+ # Use custom trust store
84
+ easysign verify signed.gem --trust-store /path/to/ca-certs/
85
+ ```
86
+
87
+ ### List available tokens
88
+
89
+ ```bash
90
+ easysign list-slots
91
+ ```
92
+
93
+ ### Show signature information
94
+
95
+ ```bash
96
+ easysign info signed.gem
97
+ ```
98
+
99
+ ## Ruby API
100
+
101
+ ### Configuration
102
+
103
+ ```ruby
104
+ require 'easy_code_sign'
105
+
106
+ EasyCodeSign.configure do |config|
107
+ # Token provider (:safenet is currently supported)
108
+ config.provider = :safenet
109
+
110
+ # Path to PKCS#11 library (auto-detected if not specified)
111
+ config.pkcs11_library = '/usr/local/lib/libeToken.dylib'
112
+
113
+ # Token slot index (default: 0)
114
+ config.slot_index = 0
115
+
116
+ # Timestamp authority URL (optional)
117
+ config.timestamp_authority = 'http://timestamp.digicert.com'
118
+
119
+ # Hash algorithm for timestamps (default: :sha256)
120
+ config.timestamp_hash_algorithm = :sha256
121
+
122
+ # Require timestamp for all signatures (default: false)
123
+ config.require_timestamp = false
124
+
125
+ # Check certificate revocation during verification (default: true)
126
+ config.check_revocation = true
127
+
128
+ # Network timeout in seconds (default: 30)
129
+ config.network_timeout = 30
130
+
131
+ # Custom trust store path for verification (optional)
132
+ config.trust_store_path = '/path/to/custom/ca-certs'
133
+
134
+ # PIN callback for interactive PIN entry
135
+ config.pin_callback = ->(slot_info) {
136
+ print "Enter PIN for #{slot_info[:slot_index]}: "
137
+ $stdin.noecho(&:gets).chomp
138
+ }
139
+ end
140
+ ```
141
+
142
+ ### Signing
143
+
144
+ ```ruby
145
+ # Sign a gem
146
+ result = EasyCodeSign.sign('my_gem-1.0.0.gem', pin: '1234')
147
+ puts "Signed: #{result.file_path}"
148
+ puts "Signer: #{result.signer_name}"
149
+
150
+ # Sign with timestamp
151
+ result = EasyCodeSign.sign('my_gem-1.0.0.gem',
152
+ pin: '1234',
153
+ timestamp: true
154
+ )
155
+ puts "Timestamp: #{result.timestamp}"
156
+
157
+ # Sign with custom output path
158
+ result = EasyCodeSign.sign('archive.zip',
159
+ pin: '1234',
160
+ output_path: 'signed_archive.zip',
161
+ algorithm: :sha256_rsa
162
+ )
163
+
164
+ # Batch signing (single token session)
165
+ signer = EasyCodeSign.signer
166
+ results = signer.sign_batch(
167
+ ['gem1.gem', 'gem2.gem', 'archive.zip'],
168
+ pin: '1234'
169
+ )
170
+ ```
171
+
172
+ ### Verification
173
+
174
+ ```ruby
175
+ # Verify a signed file
176
+ result = EasyCodeSign.verify('signed.gem')
177
+
178
+ if result.valid?
179
+ puts "Signature is valid!"
180
+ puts "Signed by: #{result.signer_name}"
181
+ puts "Organization: #{result.signer_organization}"
182
+
183
+ if result.timestamped?
184
+ puts "Timestamp: #{result.timestamp}"
185
+ puts "TSA: #{result.timestamp_authority}"
186
+ end
187
+ else
188
+ puts "Verification failed:"
189
+ result.errors.each { |e| puts " - #{e}" }
190
+ end
191
+
192
+ # Detailed verification status
193
+ puts "Signature valid: #{result.signature_valid?}"
194
+ puts "Integrity valid: #{result.integrity_valid?}"
195
+ puts "Certificate valid: #{result.certificate_valid?}"
196
+ puts "Chain valid: #{result.chain_valid?}"
197
+ puts "Trusted: #{result.trusted?}"
198
+
199
+ # Get full result as hash
200
+ puts result.to_h
201
+
202
+ # Use custom trust store
203
+ trust_store = EasyCodeSign::Verification::TrustStore.new
204
+ trust_store.add_file('/path/to/custom_ca.pem')
205
+ result = EasyCodeSign.verify('signed.gem', trust_store: trust_store)
206
+
207
+ # Batch verification
208
+ verifier = EasyCodeSign.verifier
209
+ results = verifier.verify_batch(['file1.gem', 'file2.zip'])
210
+ results.each do |path, result|
211
+ puts "#{path}: #{result.valid? ? 'VALID' : 'INVALID'}"
212
+ end
213
+ ```
214
+
215
+ ### Working with Tokens
216
+
217
+ ```ruby
218
+ # List available token slots
219
+ slots = EasyCodeSign.list_slots
220
+ slots.each do |slot|
221
+ puts "Slot #{slot[:index]}: #{slot[:token_label]}"
222
+ puts " Serial: #{slot[:serial]}"
223
+ end
224
+
225
+ # Direct provider access
226
+ provider = EasyCodeSign.provider
227
+ provider.with_session(pin: '1234') do |session|
228
+ cert = session.certificate
229
+ puts "Certificate: #{cert.subject}"
230
+ puts "Expires: #{cert.not_after}"
231
+
232
+ chain = session.certificate_chain
233
+ puts "Chain length: #{chain.length}"
234
+ end
235
+ ```
236
+
237
+ ## Supported Timestamp Authorities
238
+
239
+ Common free TSA endpoints:
240
+
241
+ | Provider | URL |
242
+ |----------|-----|
243
+ | DigiCert | `http://timestamp.digicert.com` |
244
+ | GlobalSign | `http://timestamp.globalsign.com/tsa/r6advanced1` |
245
+ | Sectigo | `http://timestamp.sectigo.com` |
246
+ | SSL.com | `http://ts.ssl.com` |
247
+
248
+ ## Error Handling
249
+
250
+ ```ruby
251
+ begin
252
+ EasyCodeSign.sign('file.gem', pin: '1234')
253
+ rescue EasyCodeSign::TokenNotFoundError
254
+ puts "Hardware token not connected"
255
+ rescue EasyCodeSign::PinError => e
256
+ puts "PIN error: #{e.message}"
257
+ puts "Retries remaining: #{e.retries_remaining}" if e.retries_remaining
258
+ rescue EasyCodeSign::TokenLockedError
259
+ puts "Token is locked - contact your administrator"
260
+ rescue EasyCodeSign::TimestampAuthorityError => e
261
+ puts "Timestamp failed: #{e.message}"
262
+ puts "HTTP status: #{e.http_status}" if e.http_status
263
+ rescue EasyCodeSign::InvalidFileError => e
264
+ puts "Invalid file: #{e.message}"
265
+ rescue EasyCodeSign::Error => e
266
+ puts "Signing error: #{e.message}"
267
+ end
268
+ ```
269
+
270
+ ## Architecture
271
+
272
+ ```
273
+ EasyCodeSign
274
+ ├── Providers # Hardware token abstraction
275
+ │ ├── Base # Abstract provider interface
276
+ │ ├── Pkcs11Base # Shared PKCS#11 functionality
277
+ │ └── Safenet # SafeNet eToken implementation
278
+ ├── Signable # File type handlers
279
+ │ ├── Base # Abstract signable interface
280
+ │ ├── GemFile # Ruby gem signing
281
+ │ └── ZipFile # JAR-style ZIP signing
282
+ ├── Timestamp # RFC 3161 timestamping
283
+ │ ├── Client # TSA HTTP client
284
+ │ ├── Request # TimeStampReq builder
285
+ │ ├── Response # TimeStampResp parser
286
+ │ └── Verifier # Timestamp verification
287
+ ├── Verification # Signature verification
288
+ │ ├── Result # Verification result
289
+ │ ├── TrustStore # CA certificate management
290
+ │ ├── CertificateChain# Chain validation
291
+ │ └── SignatureChecker# Cryptographic verification
292
+ ├── Signer # Signing orchestrator
293
+ ├── Verifier # Verification orchestrator
294
+ └── CLI # Command-line interface
295
+ ```
296
+
297
+ ## Security Considerations
298
+
299
+ - **PINs are never passed as CLI arguments** - Always entered via secure interactive prompt
300
+ - **PINs are never logged or stored** - Use `pin_callback` for programmatic secure entry
301
+ - **Hardware tokens protect private keys** - Keys never leave the HSM
302
+ - **Timestamps provide non-repudiation** - Signatures remain valid after certificate expiry
303
+ - **Certificate revocation is checked** - OCSP (real-time) with CRL fallback
304
+ - **System CA store is used by default** - Custom trust stores supported
305
+
306
+ ## Development
307
+
308
+ ```bash
309
+ # Install dependencies
310
+ bin/setup
311
+
312
+ # Run tests
313
+ bundle exec rake test
314
+
315
+ # Run linter
316
+ bundle exec rubocop
317
+
318
+ # Interactive console
319
+ bin/console
320
+
321
+ # Install locally
322
+ bundle exec rake install
323
+ ```
324
+
325
+ ## Contributing
326
+
327
+ Bug reports and pull requests are welcome on GitHub at https://github.com/mpantel/easy_code_sign.
328
+
329
+ ## License
330
+
331
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/*_test.rb"]
10
+ end
11
+
12
+ require "rubocop/rake_task"
13
+
14
+ RuboCop::RakeTask.new
15
+
16
+ task default: %i[test rubocop]
data/exe/easysign ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "easy_code_sign"
5
+ require "easy_code_sign/cli"
6
+
7
+ EasyCodeSign::CLI.start(ARGV)