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,522 @@
|
|
|
1
|
+
# EasySign Browser Extension - Development Guide
|
|
2
|
+
|
|
3
|
+
This guide covers building, testing, and extending the EasySign browser extension.
|
|
4
|
+
|
|
5
|
+
## Architecture Overview
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
9
|
+
│ Web Application │
|
|
10
|
+
│ window.EasySign.sign(blob) │
|
|
11
|
+
└─────────────────────────────┬───────────────────────────────────┘
|
|
12
|
+
│ window.postMessage
|
|
13
|
+
┌─────────────────────────────▼───────────────────────────────────┐
|
|
14
|
+
│ Content Script (Opal) │
|
|
15
|
+
│ - Injected into every page matching host_permissions │
|
|
16
|
+
│ - Bridges page context ↔ extension context │
|
|
17
|
+
│ - Validates origin before forwarding messages │
|
|
18
|
+
└─────────────────────────────┬───────────────────────────────────┘
|
|
19
|
+
│ chrome.runtime.sendMessage
|
|
20
|
+
┌─────────────────────────────▼───────────────────────────────────┐
|
|
21
|
+
│ Background Service Worker (Opal) │
|
|
22
|
+
│ - Manages native messaging connection │
|
|
23
|
+
│ - Opens PIN popup when signing requested │
|
|
24
|
+
│ - Correlates requests/responses with IDs │
|
|
25
|
+
└─────────────────────────────┬───────────────────────────────────┘
|
|
26
|
+
│ chrome.runtime.connectNative
|
|
27
|
+
┌─────────────────────────────▼───────────────────────────────────┐
|
|
28
|
+
│ Native Messaging Host (Ruby) │
|
|
29
|
+
│ - Stdio JSON protocol (length-prefixed) │
|
|
30
|
+
│ - Uses EasyCodeSign gem for actual signing │
|
|
31
|
+
│ - Accesses PKCS#11 hardware token │
|
|
32
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Project Structure
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
plugin/
|
|
39
|
+
├── Gemfile # Ruby dependencies (Opal, etc.)
|
|
40
|
+
├── Rakefile # Build tasks
|
|
41
|
+
├── README.md # User documentation
|
|
42
|
+
│
|
|
43
|
+
├── src/ # Opal Ruby source files
|
|
44
|
+
│ └── easy_sign/
|
|
45
|
+
│ ├── messaging.rb # Protocol constants & helpers
|
|
46
|
+
│ ├── background.rb # Service worker
|
|
47
|
+
│ ├── content.rb # Content script
|
|
48
|
+
│ ├── inject.rb # Page-injected API
|
|
49
|
+
│ └── popup.rb # PIN popup controller
|
|
50
|
+
│
|
|
51
|
+
├── templates/ # Static files
|
|
52
|
+
│ ├── manifest.json # WebExtension manifest
|
|
53
|
+
│ ├── popup.html # PIN entry popup
|
|
54
|
+
│ └── popup.css # Popup styles
|
|
55
|
+
│
|
|
56
|
+
├── dist/ # Built extension (gitignored)
|
|
57
|
+
│ ├── manifest.json
|
|
58
|
+
│ ├── background.js
|
|
59
|
+
│ ├── content.js
|
|
60
|
+
│ ├── inject.js
|
|
61
|
+
│ └── popup/
|
|
62
|
+
│
|
|
63
|
+
├── native_host/ # Native messaging host
|
|
64
|
+
│ ├── src/
|
|
65
|
+
│ │ ├── easy_sign_host.rb # Main entry point
|
|
66
|
+
│ │ ├── protocol.rb # Message protocol
|
|
67
|
+
│ │ └── signing_service.rb # EasyCodeSign wrapper
|
|
68
|
+
│ ├── install/ # Installation scripts
|
|
69
|
+
│ └── test/ # Native host tests
|
|
70
|
+
│
|
|
71
|
+
├── docs/ # Documentation
|
|
72
|
+
└── test/ # Extension tests
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Development Setup
|
|
76
|
+
|
|
77
|
+
### Prerequisites
|
|
78
|
+
|
|
79
|
+
- Ruby 3.2+
|
|
80
|
+
- Bundler
|
|
81
|
+
- Chrome or Firefox
|
|
82
|
+
- EasyCodeSign gem installed (for native host)
|
|
83
|
+
|
|
84
|
+
### Install Dependencies
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
cd plugin
|
|
88
|
+
bundle install
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Build Extension
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# Full build
|
|
95
|
+
bundle exec rake build
|
|
96
|
+
|
|
97
|
+
# Individual components
|
|
98
|
+
bundle exec rake build:background
|
|
99
|
+
bundle exec rake build:content
|
|
100
|
+
bundle exec rake build:inject
|
|
101
|
+
bundle exec rake build:popup
|
|
102
|
+
bundle exec rake build:manifest
|
|
103
|
+
bundle exec rake build:assets
|
|
104
|
+
|
|
105
|
+
# Watch mode (auto-rebuild on changes)
|
|
106
|
+
bundle exec rake watch
|
|
107
|
+
|
|
108
|
+
# Clean build
|
|
109
|
+
bundle exec rake clean
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Load Extension in Browser
|
|
113
|
+
|
|
114
|
+
**Chrome:**
|
|
115
|
+
1. Open `chrome://extensions`
|
|
116
|
+
2. Enable "Developer mode"
|
|
117
|
+
3. Click "Load unpacked"
|
|
118
|
+
4. Select the `plugin/dist` directory
|
|
119
|
+
5. Note the Extension ID (needed for native host)
|
|
120
|
+
|
|
121
|
+
**Firefox:**
|
|
122
|
+
1. Open `about:debugging#/runtime/this-firefox`
|
|
123
|
+
2. Click "Load Temporary Add-on"
|
|
124
|
+
3. Select `plugin/dist/manifest.json`
|
|
125
|
+
|
|
126
|
+
### Install Native Host (Development)
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
# Set your extension ID
|
|
130
|
+
export EASYSIGN_EXTENSION_ID=your_chrome_extension_id
|
|
131
|
+
|
|
132
|
+
# Install for Chrome
|
|
133
|
+
./native_host/install/install_chrome.sh
|
|
134
|
+
|
|
135
|
+
# Or for Firefox
|
|
136
|
+
export EASYSIGN_EXTENSION_ID=easysign@example.com
|
|
137
|
+
./native_host/install/install_firefox.sh
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Opal.rb Development
|
|
141
|
+
|
|
142
|
+
The extension is written in Ruby and compiled to JavaScript using [Opal](https://opalrb.com/).
|
|
143
|
+
|
|
144
|
+
### Key Concepts
|
|
145
|
+
|
|
146
|
+
**1. JavaScript Interop:**
|
|
147
|
+
```ruby
|
|
148
|
+
# backtick_javascript: true # Required at top of file
|
|
149
|
+
|
|
150
|
+
# Call JavaScript directly
|
|
151
|
+
`console.log("Hello from Opal")`
|
|
152
|
+
|
|
153
|
+
# Access JS objects
|
|
154
|
+
`chrome.runtime.sendMessage(#{message.to_n})`
|
|
155
|
+
|
|
156
|
+
# Convert Ruby to JS native object
|
|
157
|
+
message.to_n
|
|
158
|
+
|
|
159
|
+
# Access JS result in Ruby
|
|
160
|
+
result = `document.getElementById('my-id')`
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**2. Native Module:**
|
|
164
|
+
```ruby
|
|
165
|
+
require "native"
|
|
166
|
+
|
|
167
|
+
# Wrap JS objects
|
|
168
|
+
class ChromeStorage
|
|
169
|
+
include Native::Wrapper
|
|
170
|
+
|
|
171
|
+
def get(key)
|
|
172
|
+
promise = Promise.new
|
|
173
|
+
`chrome.storage.local.get(#{key}, function(result) {
|
|
174
|
+
#{promise.resolve(`result`)}
|
|
175
|
+
})`
|
|
176
|
+
promise
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**3. Promises:**
|
|
182
|
+
```ruby
|
|
183
|
+
require "promise"
|
|
184
|
+
|
|
185
|
+
def async_operation
|
|
186
|
+
Promise.new do |resolve, reject|
|
|
187
|
+
# Async work...
|
|
188
|
+
resolve.call(result)
|
|
189
|
+
# Or on error:
|
|
190
|
+
reject.call(error)
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### File Structure
|
|
196
|
+
|
|
197
|
+
Each Opal source file needs these magic comments:
|
|
198
|
+
```ruby
|
|
199
|
+
# frozen_string_literal: true
|
|
200
|
+
# backtick_javascript: true
|
|
201
|
+
|
|
202
|
+
require "native"
|
|
203
|
+
require "easy_sign/messaging"
|
|
204
|
+
|
|
205
|
+
module EasySign
|
|
206
|
+
class MyComponent
|
|
207
|
+
# ...
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Native Messaging Protocol
|
|
213
|
+
|
|
214
|
+
### Message Format
|
|
215
|
+
|
|
216
|
+
Messages are JSON with a 4-byte little-endian length prefix:
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
[4 bytes: length][JSON data]
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Request Structure
|
|
223
|
+
|
|
224
|
+
```json
|
|
225
|
+
{
|
|
226
|
+
"type": "sign",
|
|
227
|
+
"requestId": "uuid-string",
|
|
228
|
+
"payload": {
|
|
229
|
+
"pdfData": "base64-encoded-pdf",
|
|
230
|
+
"pin": "1234",
|
|
231
|
+
"options": {
|
|
232
|
+
"reason": "Approved",
|
|
233
|
+
"visibleSignature": true
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Response Structure
|
|
240
|
+
|
|
241
|
+
```json
|
|
242
|
+
{
|
|
243
|
+
"type": "sign_response",
|
|
244
|
+
"requestId": "uuid-string",
|
|
245
|
+
"payload": {
|
|
246
|
+
"signedPdfData": "base64-encoded-signed-pdf",
|
|
247
|
+
"signerName": "CN=John Doe",
|
|
248
|
+
"signedAt": "2025-01-06T10:30:00Z"
|
|
249
|
+
},
|
|
250
|
+
"error": null
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Error Response
|
|
255
|
+
|
|
256
|
+
```json
|
|
257
|
+
{
|
|
258
|
+
"type": "error",
|
|
259
|
+
"requestId": "uuid-string",
|
|
260
|
+
"payload": null,
|
|
261
|
+
"error": {
|
|
262
|
+
"code": "PIN_INCORRECT",
|
|
263
|
+
"message": "Incorrect PIN",
|
|
264
|
+
"details": { "retriesRemaining": 2 }
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Testing
|
|
270
|
+
|
|
271
|
+
### Native Host Tests
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
# Run native host unit tests
|
|
275
|
+
cd plugin
|
|
276
|
+
ruby -I native_host/src -I ../lib native_host/test/native_host_test.rb
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Manual Testing
|
|
280
|
+
|
|
281
|
+
1. Build and load the extension
|
|
282
|
+
2. Open a test page with PDF upload
|
|
283
|
+
3. Open DevTools Console
|
|
284
|
+
4. Test the API:
|
|
285
|
+
|
|
286
|
+
```javascript
|
|
287
|
+
// Check availability
|
|
288
|
+
await window.EasySign.isAvailable()
|
|
289
|
+
|
|
290
|
+
// Sign a PDF (will open PIN popup)
|
|
291
|
+
const input = document.querySelector('input[type=file]');
|
|
292
|
+
const blob = input.files[0];
|
|
293
|
+
const result = await window.EasySign.sign(blob, { visibleSignature: true });
|
|
294
|
+
console.log('Signed:', result);
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Test Page
|
|
298
|
+
|
|
299
|
+
Create a simple test page:
|
|
300
|
+
|
|
301
|
+
```html
|
|
302
|
+
<!DOCTYPE html>
|
|
303
|
+
<html>
|
|
304
|
+
<head>
|
|
305
|
+
<title>EasySign Test</title>
|
|
306
|
+
</head>
|
|
307
|
+
<body>
|
|
308
|
+
<h1>EasySign Test</h1>
|
|
309
|
+
|
|
310
|
+
<h2>1. Check Availability</h2>
|
|
311
|
+
<button onclick="checkAvailability()">Check</button>
|
|
312
|
+
<pre id="availability"></pre>
|
|
313
|
+
|
|
314
|
+
<h2>2. Sign PDF</h2>
|
|
315
|
+
<input type="file" id="pdf-input" accept=".pdf">
|
|
316
|
+
<button onclick="signPdf()">Sign</button>
|
|
317
|
+
<pre id="sign-result"></pre>
|
|
318
|
+
|
|
319
|
+
<h2>3. Verify PDF</h2>
|
|
320
|
+
<input type="file" id="signed-pdf-input" accept=".pdf">
|
|
321
|
+
<button onclick="verifyPdf()">Verify</button>
|
|
322
|
+
<pre id="verify-result"></pre>
|
|
323
|
+
|
|
324
|
+
<script>
|
|
325
|
+
async function checkAvailability() {
|
|
326
|
+
try {
|
|
327
|
+
const result = await window.EasySign.isAvailable();
|
|
328
|
+
document.getElementById('availability').textContent =
|
|
329
|
+
JSON.stringify(result, null, 2);
|
|
330
|
+
} catch (e) {
|
|
331
|
+
document.getElementById('availability').textContent =
|
|
332
|
+
'Error: ' + e.message;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
async function signPdf() {
|
|
337
|
+
const input = document.getElementById('pdf-input');
|
|
338
|
+
if (!input.files[0]) {
|
|
339
|
+
alert('Please select a PDF file');
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
try {
|
|
344
|
+
const result = await window.EasySign.sign(input.files[0], {
|
|
345
|
+
reason: 'Test signature',
|
|
346
|
+
visibleSignature: true
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
document.getElementById('sign-result').textContent =
|
|
350
|
+
JSON.stringify({
|
|
351
|
+
signerName: result.signer_name,
|
|
352
|
+
signedAt: result.signed_at,
|
|
353
|
+
blobSize: result.blob.size
|
|
354
|
+
}, null, 2);
|
|
355
|
+
|
|
356
|
+
// Download signed PDF
|
|
357
|
+
const url = URL.createObjectURL(result.blob);
|
|
358
|
+
const a = document.createElement('a');
|
|
359
|
+
a.href = url;
|
|
360
|
+
a.download = 'signed.pdf';
|
|
361
|
+
a.click();
|
|
362
|
+
} catch (e) {
|
|
363
|
+
document.getElementById('sign-result').textContent =
|
|
364
|
+
'Error: ' + e.code + ' - ' + e.message;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
async function verifyPdf() {
|
|
369
|
+
const input = document.getElementById('signed-pdf-input');
|
|
370
|
+
if (!input.files[0]) {
|
|
371
|
+
alert('Please select a signed PDF file');
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
try {
|
|
376
|
+
const result = await window.EasySign.verify(input.files[0]);
|
|
377
|
+
document.getElementById('verify-result').textContent =
|
|
378
|
+
JSON.stringify(result.payload, null, 2);
|
|
379
|
+
} catch (e) {
|
|
380
|
+
document.getElementById('verify-result').textContent =
|
|
381
|
+
'Error: ' + e.code + ' - ' + e.message;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
</script>
|
|
385
|
+
</body>
|
|
386
|
+
</html>
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
## Debugging
|
|
390
|
+
|
|
391
|
+
### Extension Logs
|
|
392
|
+
|
|
393
|
+
**Chrome:**
|
|
394
|
+
1. Go to `chrome://extensions`
|
|
395
|
+
2. Find EasySign, click "Service worker"
|
|
396
|
+
3. View console logs in DevTools
|
|
397
|
+
|
|
398
|
+
**Firefox:**
|
|
399
|
+
1. Go to `about:debugging#/runtime/this-firefox`
|
|
400
|
+
2. Find EasySign, click "Inspect"
|
|
401
|
+
|
|
402
|
+
### Native Host Logs
|
|
403
|
+
|
|
404
|
+
The native host writes to stderr, which browsers typically discard. For debugging:
|
|
405
|
+
|
|
406
|
+
```ruby
|
|
407
|
+
# In native host code
|
|
408
|
+
$stderr.puts "Debug: #{message.inspect}"
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
To capture logs, modify the host to write to a file:
|
|
412
|
+
|
|
413
|
+
```ruby
|
|
414
|
+
# At top of easy_sign_host.rb
|
|
415
|
+
LOG_FILE = File.open('/tmp/easysign.log', 'a')
|
|
416
|
+
|
|
417
|
+
def log(message)
|
|
418
|
+
LOG_FILE.puts "[#{Time.now}] #{message}"
|
|
419
|
+
LOG_FILE.flush
|
|
420
|
+
end
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### Content Script Debugging
|
|
424
|
+
|
|
425
|
+
Content script logs appear in the page's DevTools console:
|
|
426
|
+
```ruby
|
|
427
|
+
puts "Content script loaded" # Appears in page console
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
## Extending the Extension
|
|
431
|
+
|
|
432
|
+
### Adding New Message Types
|
|
433
|
+
|
|
434
|
+
1. **Define in messaging.rb:**
|
|
435
|
+
```ruby
|
|
436
|
+
module Types
|
|
437
|
+
MY_NEW_REQUEST = "my_new_request"
|
|
438
|
+
MY_NEW_RESPONSE = "my_new_response"
|
|
439
|
+
end
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
2. **Handle in background.rb:**
|
|
443
|
+
```ruby
|
|
444
|
+
when Messaging::Types::MY_NEW_REQUEST
|
|
445
|
+
handle_my_new_request(message, sender, send_response)
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
3. **Add to native host protocol.rb:**
|
|
449
|
+
```ruby
|
|
450
|
+
module Types
|
|
451
|
+
MY_NEW = "my_new"
|
|
452
|
+
MY_NEW_RESPONSE = "my_new_response"
|
|
453
|
+
end
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
4. **Implement in easy_sign_host.rb:**
|
|
457
|
+
```ruby
|
|
458
|
+
when Protocol::Types::MY_NEW
|
|
459
|
+
process_my_new(request_id, message[:payload])
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
### Adding Configuration Options
|
|
463
|
+
|
|
464
|
+
1. **Add to manifest.json:**
|
|
465
|
+
```json
|
|
466
|
+
"options_ui": {
|
|
467
|
+
"page": "options/options.html",
|
|
468
|
+
"open_in_tab": false
|
|
469
|
+
}
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
2. **Create options page**
|
|
473
|
+
3. **Store settings with chrome.storage.local**
|
|
474
|
+
|
|
475
|
+
### Supporting New Token Providers
|
|
476
|
+
|
|
477
|
+
1. **Add provider to EasyCodeSign gem** (main library)
|
|
478
|
+
2. **Configure in native host signing_service.rb:**
|
|
479
|
+
```ruby
|
|
480
|
+
config.provider = ENV.fetch("EASYSIGN_PROVIDER", "safenet").to_sym
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
## Packaging for Production
|
|
484
|
+
|
|
485
|
+
### Build Release
|
|
486
|
+
|
|
487
|
+
```bash
|
|
488
|
+
# Clean build
|
|
489
|
+
bundle exec rake clean build
|
|
490
|
+
|
|
491
|
+
# Create ZIP for Chrome Web Store
|
|
492
|
+
cd dist
|
|
493
|
+
zip -r ../easysign-chrome.zip .
|
|
494
|
+
|
|
495
|
+
# Create XPI for Firefox (just a ZIP with .xpi extension)
|
|
496
|
+
cd dist
|
|
497
|
+
zip -r ../easysign-firefox.xpi .
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### Package Native Host
|
|
501
|
+
|
|
502
|
+
See [ruby-packer documentation](https://github.com/nicyuxuan/ruby-packer) for creating self-contained executables.
|
|
503
|
+
|
|
504
|
+
```bash
|
|
505
|
+
gem install rubyc
|
|
506
|
+
rubyc native_host/src/easy_sign_host.rb -o native_host/dist/easy_sign_host
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
## Contributing
|
|
510
|
+
|
|
511
|
+
1. Fork the repository
|
|
512
|
+
2. Create a feature branch
|
|
513
|
+
3. Make your changes
|
|
514
|
+
4. Run tests
|
|
515
|
+
5. Submit a pull request
|
|
516
|
+
|
|
517
|
+
### Code Style
|
|
518
|
+
|
|
519
|
+
- Follow Ruby style guide
|
|
520
|
+
- Use `frozen_string_literal: true`
|
|
521
|
+
- Add `# backtick_javascript: true` for Opal files with JS interop
|
|
522
|
+
- Document public methods
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# EasySign Browser Extension - Installation Guide
|
|
2
|
+
|
|
3
|
+
This guide covers installing the EasySign browser extension and native messaging host for end users.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
Before installing EasySign, ensure you have:
|
|
8
|
+
|
|
9
|
+
1. **Hardware Token**: SafeNet eToken (or compatible PKCS#11 device)
|
|
10
|
+
2. **Token Drivers**: SafeNet Authentication Client installed
|
|
11
|
+
3. **Browser**: Chrome 88+ or Firefox 78+
|
|
12
|
+
|
|
13
|
+
## Quick Install
|
|
14
|
+
|
|
15
|
+
### Step 1: Install Browser Extension
|
|
16
|
+
|
|
17
|
+
**Chrome:**
|
|
18
|
+
1. Download `easysign-chrome.zip` from the releases page
|
|
19
|
+
2. Open `chrome://extensions`
|
|
20
|
+
3. Enable "Developer mode" (toggle in top right)
|
|
21
|
+
4. Click "Load unpacked"
|
|
22
|
+
5. Select the extracted extension folder
|
|
23
|
+
|
|
24
|
+
**Firefox:**
|
|
25
|
+
1. Download `easysign-firefox.xpi` from the releases page
|
|
26
|
+
2. Open `about:addons`
|
|
27
|
+
3. Click the gear icon → "Install Add-on From File..."
|
|
28
|
+
4. Select the downloaded `.xpi` file
|
|
29
|
+
|
|
30
|
+
### Step 2: Install Native Host
|
|
31
|
+
|
|
32
|
+
The native host enables the extension to communicate with your hardware token.
|
|
33
|
+
|
|
34
|
+
**macOS:**
|
|
35
|
+
```bash
|
|
36
|
+
# Download and run installer
|
|
37
|
+
curl -L https://github.com/mpantel/easy_code_sign/releases/latest/download/install-macos.sh | bash
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Linux:**
|
|
41
|
+
```bash
|
|
42
|
+
# Download and run installer
|
|
43
|
+
curl -L https://github.com/mpantel/easy_code_sign/releases/latest/download/install-linux.sh | bash
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Windows:**
|
|
47
|
+
1. Download `easysign-native-host-windows.exe` from releases
|
|
48
|
+
2. Run the installer as Administrator
|
|
49
|
+
3. Follow the installation wizard
|
|
50
|
+
|
|
51
|
+
### Step 3: Verify Installation
|
|
52
|
+
|
|
53
|
+
1. Open any website (e.g., `https://example.com`)
|
|
54
|
+
2. Open browser Developer Tools (F12)
|
|
55
|
+
3. In Console, type:
|
|
56
|
+
```javascript
|
|
57
|
+
window.EasySign.isAvailable().then(console.log)
|
|
58
|
+
```
|
|
59
|
+
4. You should see:
|
|
60
|
+
```javascript
|
|
61
|
+
{ available: true, tokenPresent: true, slots: [...] }
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Manual Installation
|
|
65
|
+
|
|
66
|
+
If automatic installation doesn't work, follow these manual steps:
|
|
67
|
+
|
|
68
|
+
### Install Native Host Manually
|
|
69
|
+
|
|
70
|
+
**macOS - Chrome:**
|
|
71
|
+
```bash
|
|
72
|
+
# Create directory
|
|
73
|
+
mkdir -p ~/Library/Application\ Support/Google/Chrome/NativeMessagingHosts
|
|
74
|
+
|
|
75
|
+
# Copy manifest (edit paths first!)
|
|
76
|
+
cat > ~/Library/Application\ Support/Google/Chrome/NativeMessagingHosts/com.easysign.host.json << 'EOF'
|
|
77
|
+
{
|
|
78
|
+
"name": "com.easysign.host",
|
|
79
|
+
"description": "EasySign PDF Signing Native Host",
|
|
80
|
+
"path": "/path/to/easy_sign_host",
|
|
81
|
+
"type": "stdio",
|
|
82
|
+
"allowed_origins": ["chrome-extension://YOUR_EXTENSION_ID/"]
|
|
83
|
+
}
|
|
84
|
+
EOF
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**macOS - Firefox:**
|
|
88
|
+
```bash
|
|
89
|
+
mkdir -p ~/Library/Application\ Support/Mozilla/NativeMessagingHosts
|
|
90
|
+
|
|
91
|
+
cat > ~/Library/Application\ Support/Mozilla/NativeMessagingHosts/com.easysign.host.json << 'EOF'
|
|
92
|
+
{
|
|
93
|
+
"name": "com.easysign.host",
|
|
94
|
+
"description": "EasySign PDF Signing Native Host",
|
|
95
|
+
"path": "/path/to/easy_sign_host",
|
|
96
|
+
"type": "stdio",
|
|
97
|
+
"allowed_extensions": ["easysign@example.com"]
|
|
98
|
+
}
|
|
99
|
+
EOF
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Linux - Chrome:**
|
|
103
|
+
```bash
|
|
104
|
+
mkdir -p ~/.config/google-chrome/NativeMessagingHosts
|
|
105
|
+
# Copy manifest as above
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**Linux - Firefox:**
|
|
109
|
+
```bash
|
|
110
|
+
mkdir -p ~/.mozilla/native-messaging-hosts
|
|
111
|
+
# Copy manifest as above
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Windows:**
|
|
115
|
+
1. Copy `easy_sign_host.exe` to `C:\Program Files\EasySign\`
|
|
116
|
+
2. Create registry key:
|
|
117
|
+
- Chrome: `HKCU\Software\Google\Chrome\NativeMessagingHosts\com.easysign.host`
|
|
118
|
+
- Firefox: `HKCU\Software\Mozilla\NativeMessagingHosts\com.easysign.host`
|
|
119
|
+
3. Set default value to manifest path
|
|
120
|
+
|
|
121
|
+
## Troubleshooting
|
|
122
|
+
|
|
123
|
+
### Extension Not Working
|
|
124
|
+
|
|
125
|
+
**"Native host not found" error:**
|
|
126
|
+
- Verify the native host manifest is in the correct location
|
|
127
|
+
- Check that the `path` in the manifest points to the actual executable
|
|
128
|
+
- Ensure the executable has execute permissions (`chmod +x`)
|
|
129
|
+
|
|
130
|
+
**"Token not found" error:**
|
|
131
|
+
- Check that your hardware token is connected
|
|
132
|
+
- Verify SafeNet drivers are installed
|
|
133
|
+
- Try running `pkcs11-tool --list-slots` to verify token visibility
|
|
134
|
+
|
|
135
|
+
**Extension icon is grayed out:**
|
|
136
|
+
- The extension may be disabled - check `chrome://extensions`
|
|
137
|
+
- Try removing and re-adding the extension
|
|
138
|
+
|
|
139
|
+
### Permission Issues
|
|
140
|
+
|
|
141
|
+
**macOS Gatekeeper blocks native host:**
|
|
142
|
+
```bash
|
|
143
|
+
# Allow the native host to run
|
|
144
|
+
xattr -d com.apple.quarantine /path/to/easy_sign_host
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Linux permission denied:**
|
|
148
|
+
```bash
|
|
149
|
+
# Ensure executable permission
|
|
150
|
+
chmod +x /path/to/easy_sign_host
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Getting Extension ID
|
|
154
|
+
|
|
155
|
+
**Chrome:**
|
|
156
|
+
1. Go to `chrome://extensions`
|
|
157
|
+
2. Enable "Developer mode"
|
|
158
|
+
3. The ID is shown under the extension name
|
|
159
|
+
|
|
160
|
+
**Firefox:**
|
|
161
|
+
1. Go to `about:debugging#/runtime/this-firefox`
|
|
162
|
+
2. Find EasySign in the list
|
|
163
|
+
3. The ID is shown as "Extension ID"
|
|
164
|
+
|
|
165
|
+
## Updating
|
|
166
|
+
|
|
167
|
+
### Update Extension
|
|
168
|
+
- Chrome: Will auto-update from Web Store
|
|
169
|
+
- Firefox: Will auto-update from Add-ons
|
|
170
|
+
|
|
171
|
+
### Update Native Host
|
|
172
|
+
Run the installer again - it will replace the existing installation.
|
|
173
|
+
|
|
174
|
+
## Uninstalling
|
|
175
|
+
|
|
176
|
+
### Remove Extension
|
|
177
|
+
- Chrome: Go to `chrome://extensions`, click "Remove"
|
|
178
|
+
- Firefox: Go to `about:addons`, click "Remove"
|
|
179
|
+
|
|
180
|
+
### Remove Native Host
|
|
181
|
+
|
|
182
|
+
**macOS/Linux:**
|
|
183
|
+
```bash
|
|
184
|
+
# Chrome
|
|
185
|
+
rm ~/Library/Application\ Support/Google/Chrome/NativeMessagingHosts/com.easysign.host.json
|
|
186
|
+
|
|
187
|
+
# Firefox
|
|
188
|
+
rm ~/Library/Application\ Support/Mozilla/NativeMessagingHosts/com.easysign.host.json
|
|
189
|
+
|
|
190
|
+
# Remove executable
|
|
191
|
+
rm /path/to/easy_sign_host
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**Windows:**
|
|
195
|
+
Run the uninstaller or manually delete:
|
|
196
|
+
- Registry keys under `NativeMessagingHosts`
|
|
197
|
+
- `C:\Program Files\EasySign\` folder
|
|
198
|
+
|
|
199
|
+
## Security Notes
|
|
200
|
+
|
|
201
|
+
- The extension only activates on HTTPS sites (and localhost for development)
|
|
202
|
+
- Your PIN is never stored - it's entered fresh each time you sign
|
|
203
|
+
- The native host runs only when needed and exits after signing
|
|
204
|
+
- Private keys never leave your hardware token
|