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