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
data/plugin/Rakefile
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "opal"
|
|
5
|
+
require "fileutils"
|
|
6
|
+
|
|
7
|
+
DIST_DIR = File.expand_path("dist", __dir__)
|
|
8
|
+
SRC_DIR = File.expand_path("src", __dir__)
|
|
9
|
+
|
|
10
|
+
desc "Build all extension components"
|
|
11
|
+
task build: ["build:background", "build:content", "build:inject", "build:popup", "build:manifest", "build:assets"]
|
|
12
|
+
|
|
13
|
+
namespace :build do
|
|
14
|
+
desc "Build background service worker"
|
|
15
|
+
task :background do
|
|
16
|
+
puts "Building background.js..."
|
|
17
|
+
compile_opal("easy_sign/background", "background.js")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
desc "Build content script"
|
|
21
|
+
task :content do
|
|
22
|
+
puts "Building content.js..."
|
|
23
|
+
compile_opal("easy_sign/content", "content.js")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
desc "Build injected page script"
|
|
27
|
+
task :inject do
|
|
28
|
+
puts "Building inject.js..."
|
|
29
|
+
compile_opal("easy_sign/inject", "inject.js")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
desc "Build popup script"
|
|
33
|
+
task :popup do
|
|
34
|
+
puts "Building popup.js..."
|
|
35
|
+
compile_opal("easy_sign/popup", "popup/popup.js")
|
|
36
|
+
|
|
37
|
+
# Copy popup HTML and CSS
|
|
38
|
+
FileUtils.mkdir_p("#{DIST_DIR}/popup")
|
|
39
|
+
FileUtils.cp("templates/popup.html", "#{DIST_DIR}/popup/popup.html")
|
|
40
|
+
FileUtils.cp("templates/popup.css", "#{DIST_DIR}/popup/popup.css")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
desc "Copy manifest.json"
|
|
44
|
+
task :manifest do
|
|
45
|
+
puts "Copying manifest.json..."
|
|
46
|
+
FileUtils.mkdir_p(DIST_DIR)
|
|
47
|
+
FileUtils.cp("templates/manifest.json", "#{DIST_DIR}/manifest.json")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
desc "Copy static assets (icons, etc)"
|
|
51
|
+
task :assets do
|
|
52
|
+
puts "Copying assets..."
|
|
53
|
+
FileUtils.mkdir_p("#{DIST_DIR}/icons")
|
|
54
|
+
if Dir.exist?("assets/icons")
|
|
55
|
+
FileUtils.cp_r("assets/icons/.", "#{DIST_DIR}/icons/")
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
desc "Watch for changes and rebuild"
|
|
61
|
+
task :watch do
|
|
62
|
+
require "listen"
|
|
63
|
+
|
|
64
|
+
puts "Watching for changes in src/ and templates/..."
|
|
65
|
+
|
|
66
|
+
listener = Listen.to(SRC_DIR, "templates") do |modified, added, removed|
|
|
67
|
+
puts "\nChanges detected: #{(modified + added + removed).join(', ')}"
|
|
68
|
+
Rake::Task["build"].reenable
|
|
69
|
+
Rake::Task["build"].invoke
|
|
70
|
+
puts "Rebuild complete."
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
listener.start
|
|
74
|
+
puts "Press Ctrl+C to stop."
|
|
75
|
+
sleep
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
desc "Clean dist directory"
|
|
79
|
+
task :clean do
|
|
80
|
+
puts "Cleaning dist/..."
|
|
81
|
+
FileUtils.rm_rf(Dir.glob("#{DIST_DIR}/*"))
|
|
82
|
+
puts "Clean complete."
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
desc "Run tests"
|
|
86
|
+
task :test do
|
|
87
|
+
require "minitest/autorun"
|
|
88
|
+
Dir.glob("test/**/*_test.rb").each { |f| require_relative f }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Development server for testing
|
|
92
|
+
desc "Start a simple HTTP server for testing"
|
|
93
|
+
task :server do
|
|
94
|
+
require "webrick"
|
|
95
|
+
server = WEBrick::HTTPServer.new(Port: 8080, DocumentRoot: DIST_DIR)
|
|
96
|
+
trap("INT") { server.shutdown }
|
|
97
|
+
puts "Serving dist/ at http://localhost:8080"
|
|
98
|
+
server.start
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def compile_opal(entry_point, output_file)
|
|
102
|
+
# Create a fresh builder with custom paths
|
|
103
|
+
builder = Opal::Builder.new(
|
|
104
|
+
stubs: [],
|
|
105
|
+
compiler_options: {
|
|
106
|
+
dynamic_require_severity: :ignore
|
|
107
|
+
}
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Add source directory to builder's path
|
|
111
|
+
builder.append_paths(SRC_DIR)
|
|
112
|
+
|
|
113
|
+
# Build the entry point
|
|
114
|
+
builder.build(entry_point)
|
|
115
|
+
|
|
116
|
+
output_path = File.join(DIST_DIR, output_file)
|
|
117
|
+
FileUtils.mkdir_p(File.dirname(output_path))
|
|
118
|
+
|
|
119
|
+
File.write(output_path, builder.to_s)
|
|
120
|
+
puts " -> #{output_path} (#{File.size(output_path)} bytes)"
|
|
121
|
+
end
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
# EasySign API Reference
|
|
2
|
+
|
|
3
|
+
The EasySign browser extension exposes a `window.EasySign` object that web applications can use to sign and verify PDF documents using hardware security tokens.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
// Check if EasySign is available
|
|
9
|
+
const status = await window.EasySign.isAvailable();
|
|
10
|
+
|
|
11
|
+
// Sign a PDF
|
|
12
|
+
const result = await window.EasySign.sign(pdfBlob, options);
|
|
13
|
+
|
|
14
|
+
// Verify a signed PDF
|
|
15
|
+
const verification = await window.EasySign.verify(pdfBlob);
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Methods
|
|
19
|
+
|
|
20
|
+
### `window.EasySign.isAvailable()`
|
|
21
|
+
|
|
22
|
+
Check if the EasySign extension is installed and the hardware token is connected.
|
|
23
|
+
|
|
24
|
+
**Parameters:** None
|
|
25
|
+
|
|
26
|
+
**Returns:** `Promise<AvailabilityResult>`
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
interface AvailabilityResult {
|
|
30
|
+
available: boolean; // Extension and native host are working
|
|
31
|
+
tokenPresent: boolean; // Hardware token is connected
|
|
32
|
+
slots: TokenSlot[]; // List of available token slots
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface TokenSlot {
|
|
36
|
+
index: number; // Slot index
|
|
37
|
+
tokenLabel: string; // Token display name
|
|
38
|
+
manufacturer: string; // Token manufacturer
|
|
39
|
+
serial: string; // Token serial number
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Example:**
|
|
44
|
+
```javascript
|
|
45
|
+
window.EasySign.isAvailable()
|
|
46
|
+
.then(result => {
|
|
47
|
+
if (!result.available) {
|
|
48
|
+
alert('Please install the EasySign extension');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (!result.tokenPresent) {
|
|
52
|
+
alert('Please connect your hardware token');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
console.log('Ready to sign!', result.slots);
|
|
56
|
+
})
|
|
57
|
+
.catch(err => {
|
|
58
|
+
console.error('EasySign error:', err);
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
### `window.EasySign.sign(pdfBlob, options)`
|
|
65
|
+
|
|
66
|
+
Sign a PDF document. Opens a popup for secure PIN entry.
|
|
67
|
+
|
|
68
|
+
**Parameters:**
|
|
69
|
+
|
|
70
|
+
| Parameter | Type | Required | Description |
|
|
71
|
+
|-----------|------|----------|-------------|
|
|
72
|
+
| `pdfBlob` | `Blob` | Yes | The PDF file to sign |
|
|
73
|
+
| `options` | `SignOptions` | No | Signing options |
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
interface SignOptions {
|
|
77
|
+
// Signature metadata
|
|
78
|
+
reason?: string; // Reason for signing (e.g., "Approved")
|
|
79
|
+
location?: string; // Location of signing (e.g., "New York")
|
|
80
|
+
|
|
81
|
+
// Visible signature
|
|
82
|
+
visibleSignature?: boolean; // Add visible signature annotation (default: false)
|
|
83
|
+
signaturePosition?: string; // Position: "top_left", "top_right", "bottom_left", "bottom_right"
|
|
84
|
+
signaturePage?: number; // Page number for signature (default: 1)
|
|
85
|
+
|
|
86
|
+
// Timestamp
|
|
87
|
+
timestamp?: boolean; // Add RFC 3161 timestamp (default: false)
|
|
88
|
+
timestampAuthority?: string; // TSA URL (default: http://timestamp.digicert.com)
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Returns:** `Promise<SignResult>`
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
interface SignResult {
|
|
96
|
+
blob: Blob; // The signed PDF file
|
|
97
|
+
signer_name: string; // Certificate common name
|
|
98
|
+
signed_at: string; // ISO 8601 timestamp
|
|
99
|
+
timestamped: boolean; // Whether timestamp was added
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Example:**
|
|
104
|
+
```javascript
|
|
105
|
+
// Get PDF from file input
|
|
106
|
+
const fileInput = document.getElementById('pdf-file');
|
|
107
|
+
const pdfBlob = fileInput.files[0];
|
|
108
|
+
|
|
109
|
+
// Sign with options
|
|
110
|
+
window.EasySign.sign(pdfBlob, {
|
|
111
|
+
reason: 'Document approved',
|
|
112
|
+
location: 'New York, NY',
|
|
113
|
+
visibleSignature: true,
|
|
114
|
+
signaturePosition: 'bottom_right',
|
|
115
|
+
timestamp: true
|
|
116
|
+
})
|
|
117
|
+
.then(result => {
|
|
118
|
+
console.log('Signed by:', result.signer_name);
|
|
119
|
+
console.log('Signed at:', result.signed_at);
|
|
120
|
+
|
|
121
|
+
// Download the signed PDF
|
|
122
|
+
const url = URL.createObjectURL(result.blob);
|
|
123
|
+
const link = document.createElement('a');
|
|
124
|
+
link.href = url;
|
|
125
|
+
link.download = 'signed_document.pdf';
|
|
126
|
+
link.click();
|
|
127
|
+
URL.revokeObjectURL(url);
|
|
128
|
+
})
|
|
129
|
+
.catch(err => {
|
|
130
|
+
if (err.code === 'CANCELLED') {
|
|
131
|
+
console.log('User cancelled signing');
|
|
132
|
+
} else {
|
|
133
|
+
console.error('Signing failed:', err.message);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
### `window.EasySign.verify(pdfBlob, options)`
|
|
141
|
+
|
|
142
|
+
Verify a signed PDF document.
|
|
143
|
+
|
|
144
|
+
**Parameters:**
|
|
145
|
+
|
|
146
|
+
| Parameter | Type | Required | Description |
|
|
147
|
+
|-----------|------|----------|-------------|
|
|
148
|
+
| `pdfBlob` | `Blob` | Yes | The signed PDF file to verify |
|
|
149
|
+
| `options` | `VerifyOptions` | No | Verification options |
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
interface VerifyOptions {
|
|
153
|
+
checkTimestamp?: boolean; // Verify timestamp (default: true)
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Returns:** `Promise<VerifyResult>`
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
interface VerifyResult {
|
|
161
|
+
payload: {
|
|
162
|
+
valid: boolean; // Overall signature validity
|
|
163
|
+
signerName: string; // Certificate common name
|
|
164
|
+
signerOrganization: string; // Certificate organization
|
|
165
|
+
signedAt: string; // Signing timestamp (ISO 8601)
|
|
166
|
+
|
|
167
|
+
// Detailed checks
|
|
168
|
+
signatureValid: boolean; // Cryptographic signature OK
|
|
169
|
+
integrityValid: boolean; // Document not tampered
|
|
170
|
+
certificateValid: boolean; // Certificate not expired
|
|
171
|
+
chainValid: boolean; // Certificate chain OK
|
|
172
|
+
trusted: boolean; // Root CA is trusted
|
|
173
|
+
|
|
174
|
+
// Timestamp
|
|
175
|
+
timestamped: boolean; // Has timestamp
|
|
176
|
+
timestampValid: boolean; // Timestamp is valid
|
|
177
|
+
|
|
178
|
+
// Issues
|
|
179
|
+
errors: string[]; // List of errors
|
|
180
|
+
warnings: string[]; // List of warnings
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**Example:**
|
|
186
|
+
```javascript
|
|
187
|
+
window.EasySign.verify(signedPdfBlob)
|
|
188
|
+
.then(result => {
|
|
189
|
+
const v = result.payload;
|
|
190
|
+
|
|
191
|
+
if (v.valid) {
|
|
192
|
+
console.log('✓ Signature is valid');
|
|
193
|
+
console.log(' Signed by:', v.signerName);
|
|
194
|
+
console.log(' Organization:', v.signerOrganization);
|
|
195
|
+
console.log(' Date:', new Date(v.signedAt).toLocaleString());
|
|
196
|
+
|
|
197
|
+
if (v.timestamped) {
|
|
198
|
+
console.log(' Timestamped: Yes');
|
|
199
|
+
}
|
|
200
|
+
} else {
|
|
201
|
+
console.log('✗ Signature is invalid');
|
|
202
|
+
v.errors.forEach(err => console.log(' Error:', err));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (v.warnings.length > 0) {
|
|
206
|
+
console.log('Warnings:');
|
|
207
|
+
v.warnings.forEach(w => console.log(' -', w));
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Error Handling
|
|
215
|
+
|
|
216
|
+
All methods return Promises that reject with an `Error` object containing a `code` property.
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
interface EasySignError extends Error {
|
|
220
|
+
code: string; // Error code for programmatic handling
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Error Codes
|
|
225
|
+
|
|
226
|
+
| Code | Description | User Action |
|
|
227
|
+
|------|-------------|-------------|
|
|
228
|
+
| `TOKEN_NOT_FOUND` | Hardware token not connected | Connect token and retry |
|
|
229
|
+
| `PIN_INCORRECT` | Wrong PIN entered | Re-enter correct PIN |
|
|
230
|
+
| `TOKEN_LOCKED` | Token locked (too many wrong PINs) | Contact administrator |
|
|
231
|
+
| `INVALID_PDF` | PDF file is corrupted or invalid | Use a valid PDF file |
|
|
232
|
+
| `SIGNING_FAILED` | Signing operation failed | Check token and retry |
|
|
233
|
+
| `VERIFICATION_FAILED` | Verification operation failed | Check PDF file |
|
|
234
|
+
| `NATIVE_HOST_NOT_FOUND` | Native host not installed | Install native host |
|
|
235
|
+
| `TIMEOUT` | Operation timed out | Retry the operation |
|
|
236
|
+
| `CANCELLED` | User cancelled the operation | No action needed |
|
|
237
|
+
| `ORIGIN_NOT_ALLOWED` | Website not allowed to use EasySign | N/A |
|
|
238
|
+
| `INTERNAL_ERROR` | Unexpected error | Report bug |
|
|
239
|
+
|
|
240
|
+
### Error Handling Example
|
|
241
|
+
|
|
242
|
+
```javascript
|
|
243
|
+
window.EasySign.sign(pdfBlob, options)
|
|
244
|
+
.then(result => {
|
|
245
|
+
// Success
|
|
246
|
+
})
|
|
247
|
+
.catch(err => {
|
|
248
|
+
switch (err.code) {
|
|
249
|
+
case 'TOKEN_NOT_FOUND':
|
|
250
|
+
showMessage('Please connect your hardware token');
|
|
251
|
+
break;
|
|
252
|
+
|
|
253
|
+
case 'PIN_INCORRECT':
|
|
254
|
+
showMessage('Incorrect PIN. Please try again.');
|
|
255
|
+
break;
|
|
256
|
+
|
|
257
|
+
case 'TOKEN_LOCKED':
|
|
258
|
+
showMessage('Your token is locked. Contact your IT administrator.');
|
|
259
|
+
break;
|
|
260
|
+
|
|
261
|
+
case 'CANCELLED':
|
|
262
|
+
// User cancelled - no message needed
|
|
263
|
+
break;
|
|
264
|
+
|
|
265
|
+
case 'TIMEOUT':
|
|
266
|
+
showMessage('Operation timed out. Please try again.');
|
|
267
|
+
break;
|
|
268
|
+
|
|
269
|
+
default:
|
|
270
|
+
showMessage(`Signing failed: ${err.message}`);
|
|
271
|
+
console.error('EasySign error:', err);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## TypeScript Definitions
|
|
279
|
+
|
|
280
|
+
For TypeScript projects, you can use these type definitions:
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
declare global {
|
|
284
|
+
interface Window {
|
|
285
|
+
EasySign: {
|
|
286
|
+
isAvailable(): Promise<{
|
|
287
|
+
available: boolean;
|
|
288
|
+
tokenPresent: boolean;
|
|
289
|
+
slots: Array<{
|
|
290
|
+
index: number;
|
|
291
|
+
tokenLabel: string;
|
|
292
|
+
manufacturer: string;
|
|
293
|
+
serial: string;
|
|
294
|
+
}>;
|
|
295
|
+
}>;
|
|
296
|
+
|
|
297
|
+
sign(pdfBlob: Blob, options?: {
|
|
298
|
+
reason?: string;
|
|
299
|
+
location?: string;
|
|
300
|
+
visibleSignature?: boolean;
|
|
301
|
+
signaturePosition?: 'top_left' | 'top_right' | 'bottom_left' | 'bottom_right';
|
|
302
|
+
signaturePage?: number;
|
|
303
|
+
timestamp?: boolean;
|
|
304
|
+
timestampAuthority?: string;
|
|
305
|
+
}): Promise<{
|
|
306
|
+
blob: Blob;
|
|
307
|
+
signer_name: string;
|
|
308
|
+
signed_at: string;
|
|
309
|
+
timestamped: boolean;
|
|
310
|
+
}>;
|
|
311
|
+
|
|
312
|
+
verify(pdfBlob: Blob, options?: {
|
|
313
|
+
checkTimestamp?: boolean;
|
|
314
|
+
}): Promise<{
|
|
315
|
+
payload: {
|
|
316
|
+
valid: boolean;
|
|
317
|
+
signerName: string;
|
|
318
|
+
signerOrganization: string;
|
|
319
|
+
signedAt: string;
|
|
320
|
+
signatureValid: boolean;
|
|
321
|
+
integrityValid: boolean;
|
|
322
|
+
certificateValid: boolean;
|
|
323
|
+
chainValid: boolean;
|
|
324
|
+
trusted: boolean;
|
|
325
|
+
timestamped: boolean;
|
|
326
|
+
timestampValid: boolean;
|
|
327
|
+
errors: string[];
|
|
328
|
+
warnings: string[];
|
|
329
|
+
};
|
|
330
|
+
}>;
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Save this as `easysign.d.ts` in your project.
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## Browser Compatibility
|
|
341
|
+
|
|
342
|
+
| Browser | Minimum Version | Notes |
|
|
343
|
+
|---------|-----------------|-------|
|
|
344
|
+
| Chrome | 88+ | Full support |
|
|
345
|
+
| Edge | 88+ | Chromium-based, full support |
|
|
346
|
+
| Firefox | 78+ | Full support |
|
|
347
|
+
| Safari | Not supported | No native messaging API |
|
|
348
|
+
| Opera | 74+ | Chromium-based, should work |
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## Security Considerations
|
|
353
|
+
|
|
354
|
+
1. **HTTPS Only**: The extension only works on HTTPS sites (except localhost)
|
|
355
|
+
2. **PIN Security**: PINs are never stored, only passed directly to the native host
|
|
356
|
+
3. **Origin Validation**: Only whitelisted origins can use the API
|
|
357
|
+
4. **No Key Export**: Private keys never leave the hardware token
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## Rate Limiting
|
|
362
|
+
|
|
363
|
+
There are no rate limits on the API, but:
|
|
364
|
+
- Each `sign()` call requires user interaction (PIN entry)
|
|
365
|
+
- Native host operations are sequential (one at a time)
|
|
366
|
+
- Signing large PDFs may take several seconds
|