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,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,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
|