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
@@ -0,0 +1,223 @@
1
+ /* EasySign Popup Styles */
2
+
3
+ :root {
4
+ --primary-color: #2563eb;
5
+ --primary-hover: #1d4ed8;
6
+ --danger-color: #dc2626;
7
+ --success-color: #16a34a;
8
+ --text-color: #1f2937;
9
+ --text-muted: #6b7280;
10
+ --bg-color: #ffffff;
11
+ --bg-secondary: #f3f4f6;
12
+ --border-color: #e5e7eb;
13
+ --border-radius: 8px;
14
+ --shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
15
+ }
16
+
17
+ * {
18
+ box-sizing: border-box;
19
+ margin: 0;
20
+ padding: 0;
21
+ }
22
+
23
+ body {
24
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
25
+ font-size: 14px;
26
+ line-height: 1.5;
27
+ color: var(--text-color);
28
+ background: var(--bg-color);
29
+ width: 320px;
30
+ min-height: 280px;
31
+ }
32
+
33
+ .container {
34
+ display: flex;
35
+ flex-direction: column;
36
+ min-height: 280px;
37
+ }
38
+
39
+ /* Header */
40
+ .header {
41
+ padding: 12px 16px;
42
+ border-bottom: 1px solid var(--border-color);
43
+ background: var(--bg-secondary);
44
+ }
45
+
46
+ .logo {
47
+ display: flex;
48
+ align-items: center;
49
+ gap: 8px;
50
+ color: var(--primary-color);
51
+ font-weight: 600;
52
+ font-size: 16px;
53
+ }
54
+
55
+ .logo svg {
56
+ flex-shrink: 0;
57
+ }
58
+
59
+ /* Main content */
60
+ .main {
61
+ flex: 1;
62
+ padding: 16px;
63
+ display: flex;
64
+ flex-direction: column;
65
+ gap: 16px;
66
+ }
67
+
68
+ .main h2 {
69
+ font-size: 18px;
70
+ font-weight: 600;
71
+ color: var(--text-color);
72
+ }
73
+
74
+ /* Context info */
75
+ .context {
76
+ background: var(--bg-secondary);
77
+ border-radius: var(--border-radius);
78
+ padding: 12px;
79
+ }
80
+
81
+ .context-row {
82
+ display: flex;
83
+ align-items: center;
84
+ gap: 8px;
85
+ }
86
+
87
+ .context-row + .context-row {
88
+ margin-top: 8px;
89
+ }
90
+
91
+ .context-label {
92
+ color: var(--text-muted);
93
+ font-size: 12px;
94
+ }
95
+
96
+ .context-value {
97
+ font-size: 13px;
98
+ color: var(--text-color);
99
+ word-break: break-all;
100
+ }
101
+
102
+ .context-value.status {
103
+ color: var(--text-muted);
104
+ font-style: italic;
105
+ }
106
+
107
+ /* Form */
108
+ .form {
109
+ display: flex;
110
+ flex-direction: column;
111
+ gap: 8px;
112
+ }
113
+
114
+ .label {
115
+ font-size: 13px;
116
+ font-weight: 500;
117
+ color: var(--text-color);
118
+ }
119
+
120
+ .pin-input {
121
+ width: 100%;
122
+ padding: 10px 12px;
123
+ font-size: 16px;
124
+ border: 1px solid var(--border-color);
125
+ border-radius: var(--border-radius);
126
+ outline: none;
127
+ transition: border-color 0.2s, box-shadow 0.2s;
128
+ }
129
+
130
+ .pin-input:focus {
131
+ border-color: var(--primary-color);
132
+ box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
133
+ }
134
+
135
+ .pin-input:disabled {
136
+ background: var(--bg-secondary);
137
+ cursor: not-allowed;
138
+ }
139
+
140
+ .pin-input::placeholder {
141
+ color: var(--text-muted);
142
+ }
143
+
144
+ /* Error message */
145
+ .error {
146
+ color: var(--danger-color);
147
+ font-size: 13px;
148
+ padding: 8px 12px;
149
+ background: rgba(220, 38, 38, 0.1);
150
+ border-radius: var(--border-radius);
151
+ }
152
+
153
+ /* Action buttons */
154
+ .actions {
155
+ display: flex;
156
+ gap: 12px;
157
+ margin-top: auto;
158
+ }
159
+
160
+ .btn {
161
+ flex: 1;
162
+ padding: 10px 16px;
163
+ font-size: 14px;
164
+ font-weight: 500;
165
+ border: none;
166
+ border-radius: var(--border-radius);
167
+ cursor: pointer;
168
+ transition: background-color 0.2s, opacity 0.2s;
169
+ }
170
+
171
+ .btn:disabled {
172
+ opacity: 0.6;
173
+ cursor: not-allowed;
174
+ }
175
+
176
+ .btn-primary {
177
+ background: var(--primary-color);
178
+ color: white;
179
+ }
180
+
181
+ .btn-primary:hover:not(:disabled) {
182
+ background: var(--primary-hover);
183
+ }
184
+
185
+ .btn-secondary {
186
+ background: var(--bg-secondary);
187
+ color: var(--text-color);
188
+ border: 1px solid var(--border-color);
189
+ }
190
+
191
+ .btn-secondary:hover:not(:disabled) {
192
+ background: var(--border-color);
193
+ }
194
+
195
+ /* Footer */
196
+ .footer {
197
+ padding: 8px 16px;
198
+ border-top: 1px solid var(--border-color);
199
+ text-align: center;
200
+ }
201
+
202
+ .footer-text {
203
+ font-size: 11px;
204
+ color: var(--text-muted);
205
+ }
206
+
207
+ /* Loading state */
208
+ .loading .pin-input,
209
+ .loading .btn {
210
+ pointer-events: none;
211
+ opacity: 0.6;
212
+ }
213
+
214
+ /* Animations */
215
+ @keyframes shake {
216
+ 0%, 100% { transform: translateX(0); }
217
+ 20%, 60% { transform: translateX(-5px); }
218
+ 40%, 80% { transform: translateX(5px); }
219
+ }
220
+
221
+ .shake {
222
+ animation: shake 0.4s ease-in-out;
223
+ }
@@ -0,0 +1,59 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>EasySign - Enter PIN</title>
7
+ <link rel="stylesheet" href="popup.css">
8
+ </head>
9
+ <body>
10
+ <div class="container">
11
+ <header class="header">
12
+ <div class="logo">
13
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
14
+ <path d="M9 12L11 14L15 10M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
15
+ </svg>
16
+ <span>EasySign</span>
17
+ </div>
18
+ </header>
19
+
20
+ <main class="main">
21
+ <h2>Sign Document</h2>
22
+
23
+ <div class="context">
24
+ <div class="context-row">
25
+ <span class="context-label">Website:</span>
26
+ <span id="origin-text" class="context-value">-</span>
27
+ </div>
28
+ <div class="context-row">
29
+ <span id="status-text" class="context-value status">Ready to sign</span>
30
+ </div>
31
+ </div>
32
+
33
+ <div class="form">
34
+ <label for="pin-input" class="label">Enter your token PIN:</label>
35
+ <input
36
+ type="password"
37
+ id="pin-input"
38
+ class="pin-input"
39
+ placeholder="PIN"
40
+ autocomplete="off"
41
+ autofocus
42
+ >
43
+ <div id="error-text" class="error" style="display: none;"></div>
44
+ </div>
45
+
46
+ <div class="actions">
47
+ <button id="cancel-btn" class="btn btn-secondary">Cancel</button>
48
+ <button id="submit-btn" class="btn btn-primary">Sign</button>
49
+ </div>
50
+ </main>
51
+
52
+ <footer class="footer">
53
+ <span class="footer-text">Your PIN is never stored</span>
54
+ </footer>
55
+ </div>
56
+
57
+ <script src="popup.js"></script>
58
+ </body>
59
+ </html>
@@ -0,0 +1,4 @@
1
+ module EasyCodeSign
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class EasyCodeSignModuleTest < EasyCodeSignTest
6
+ def test_has_version_number
7
+ refute_nil EasyCodeSign::VERSION
8
+ end
9
+
10
+ def test_configure_yields_configuration_object
11
+ yielded = nil
12
+ EasyCodeSign.configure { |c| yielded = c }
13
+ assert_instance_of EasyCodeSign::Configuration, yielded
14
+ end
15
+
16
+ def test_configure_allows_setting_values
17
+ EasyCodeSign.configure do |config|
18
+ config.provider = :safenet
19
+ config.timestamp_authority = "http://timestamp.example.com"
20
+ end
21
+
22
+ assert_equal :safenet, EasyCodeSign.configuration.provider
23
+ assert_equal "http://timestamp.example.com", EasyCodeSign.configuration.timestamp_authority
24
+ end
25
+
26
+ def test_configuration_returns_configuration_instance
27
+ assert_instance_of EasyCodeSign::Configuration, EasyCodeSign.configuration
28
+ end
29
+
30
+ def test_configuration_returns_same_instance
31
+ assert_same EasyCodeSign.configuration, EasyCodeSign.configuration
32
+ end
33
+
34
+ def test_reset_configuration_creates_new_instance
35
+ old_config = EasyCodeSign.configuration
36
+ EasyCodeSign.reset_configuration!
37
+ refute_same old_config, EasyCodeSign.configuration
38
+ end
39
+ end
40
+
41
+ class ConfigurationTest < EasyCodeSignTest
42
+ def setup
43
+ super
44
+ @config = EasyCodeSign::Configuration.new
45
+ end
46
+
47
+ def test_default_provider_is_safenet
48
+ assert_equal :safenet, @config.provider
49
+ end
50
+
51
+ def test_default_timestamp_hash_algorithm_is_sha256
52
+ assert_equal :sha256, @config.timestamp_hash_algorithm
53
+ end
54
+
55
+ def test_default_require_timestamp_is_false
56
+ refute @config.require_timestamp
57
+ end
58
+
59
+ def test_default_check_revocation_is_true
60
+ assert @config.check_revocation
61
+ end
62
+
63
+ def test_default_network_timeout_is_30
64
+ assert_equal 30, @config.network_timeout
65
+ end
66
+
67
+ def test_default_slot_index_is_0
68
+ assert_equal 0, @config.slot_index
69
+ end
70
+
71
+ def test_validate_raises_when_provider_nil
72
+ @config.provider = nil
73
+ error = assert_raises(EasyCodeSign::ConfigurationError) { @config.validate! }
74
+ assert_match(/Provider/, error.message)
75
+ end
76
+
77
+ def test_validate_raises_when_pkcs11_library_nil
78
+ @config.provider = :safenet
79
+ @config.pkcs11_library = nil
80
+ error = assert_raises(EasyCodeSign::ConfigurationError) { @config.validate! }
81
+ assert_match(/PKCS#11 library/, error.message)
82
+ end
83
+
84
+ def test_validate_raises_when_require_timestamp_without_tsa
85
+ @config.pkcs11_library = __FILE__
86
+ @config.require_timestamp = true
87
+ @config.timestamp_authority = nil
88
+ error = assert_raises(EasyCodeSign::ConfigurationError) { @config.validate! }
89
+ assert_match(/Timestamp authority/, error.message)
90
+ end
91
+ end
92
+
93
+ class ErrorsTest < Minitest::Test
94
+ def test_error_is_standard_error
95
+ assert_kind_of StandardError, EasyCodeSign::Error.new
96
+ end
97
+
98
+ def test_pin_error_stores_retries_remaining
99
+ error = EasyCodeSign::PinError.new("Wrong PIN", retries_remaining: 2)
100
+ assert_equal 2, error.retries_remaining
101
+ end
102
+
103
+ def test_pkcs11_error_stores_error_code
104
+ error = EasyCodeSign::Pkcs11Error.new("PKCS11 failure", pkcs11_error_code: 0x00000003)
105
+ assert_equal 0x00000003, error.pkcs11_error_code
106
+ end
107
+
108
+ def test_certificate_chain_error_stores_certificate_and_reason
109
+ error = EasyCodeSign::CertificateChainError.new(
110
+ "Invalid chain",
111
+ certificate: "cert_data",
112
+ reason: :expired
113
+ )
114
+ assert_equal "cert_data", error.certificate
115
+ assert_equal :expired, error.reason
116
+ end
117
+
118
+ def test_timestamp_authority_error_stores_http_status
119
+ error = EasyCodeSign::TimestampAuthorityError.new("TSA failed", http_status: 503)
120
+ assert_equal 503, error.http_status
121
+ end
122
+ end