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,183 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "easy_code_sign/version"
|
|
4
|
+
require_relative "easy_code_sign/errors"
|
|
5
|
+
require_relative "easy_code_sign/configuration"
|
|
6
|
+
require_relative "easy_code_sign/providers/base"
|
|
7
|
+
require_relative "easy_code_sign/providers/pkcs11_base"
|
|
8
|
+
require_relative "easy_code_sign/providers/safenet"
|
|
9
|
+
require_relative "easy_code_sign/signable/base"
|
|
10
|
+
require_relative "easy_code_sign/signable/gem_file"
|
|
11
|
+
require_relative "easy_code_sign/signable/zip_file"
|
|
12
|
+
require_relative "easy_code_sign/signable/pdf_file"
|
|
13
|
+
require_relative "easy_code_sign/pdf/timestamp_handler"
|
|
14
|
+
require_relative "easy_code_sign/pdf/appearance_builder"
|
|
15
|
+
require_relative "easy_code_sign/timestamp/request"
|
|
16
|
+
require_relative "easy_code_sign/timestamp/response"
|
|
17
|
+
require_relative "easy_code_sign/timestamp/client"
|
|
18
|
+
require_relative "easy_code_sign/timestamp/verifier"
|
|
19
|
+
require_relative "easy_code_sign/verification/result"
|
|
20
|
+
require_relative "easy_code_sign/verification/trust_store"
|
|
21
|
+
require_relative "easy_code_sign/verification/certificate_chain"
|
|
22
|
+
require_relative "easy_code_sign/verification/signature_checker"
|
|
23
|
+
require_relative "easy_code_sign/deferred_signing_request"
|
|
24
|
+
require_relative "easy_code_sign/signer"
|
|
25
|
+
require_relative "easy_code_sign/verifier"
|
|
26
|
+
|
|
27
|
+
module EasyCodeSign
|
|
28
|
+
class << self
|
|
29
|
+
# Get the current configuration
|
|
30
|
+
# @return [Configuration]
|
|
31
|
+
def configuration
|
|
32
|
+
@configuration ||= Configuration.new
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Configure EasyCodeSign
|
|
36
|
+
# @yield [Configuration] the configuration object
|
|
37
|
+
# @return [Configuration]
|
|
38
|
+
def configure
|
|
39
|
+
yield(configuration)
|
|
40
|
+
configuration
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Reset configuration to defaults
|
|
44
|
+
# @return [Configuration]
|
|
45
|
+
def reset_configuration!
|
|
46
|
+
@configuration = Configuration.new
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Get a provider instance based on current configuration
|
|
50
|
+
# @return [Providers::Base] a provider instance
|
|
51
|
+
def provider
|
|
52
|
+
@provider ||= build_provider
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Reset the cached provider instance
|
|
56
|
+
# @return [void]
|
|
57
|
+
def reset_provider!
|
|
58
|
+
@provider = nil
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Sign a file using the configured provider
|
|
62
|
+
#
|
|
63
|
+
# @param file_path [String] path to the file to sign
|
|
64
|
+
# @param pin [String, nil] PIN for the token (uses callback if nil)
|
|
65
|
+
# @param output_path [String, nil] output path for signed file
|
|
66
|
+
# @param timestamp [Boolean] whether to add timestamp (default: from config)
|
|
67
|
+
# @param algorithm [Symbol] signature algorithm (default: :sha256_rsa)
|
|
68
|
+
# @return [SigningResult] signing result with file path and metadata
|
|
69
|
+
#
|
|
70
|
+
# @example Sign a gem
|
|
71
|
+
# EasyCodeSign.sign("my_gem-1.0.0.gem", pin: "1234")
|
|
72
|
+
#
|
|
73
|
+
# @example Sign with timestamp
|
|
74
|
+
# EasyCodeSign.sign("archive.zip", pin: "1234", timestamp: true)
|
|
75
|
+
#
|
|
76
|
+
def sign(file_path, pin: nil, output_path: nil, timestamp: nil, algorithm: :sha256_rsa)
|
|
77
|
+
signer = Signer.new
|
|
78
|
+
signer.sign(file_path, pin: pin, output_path: output_path, timestamp: timestamp, algorithm: algorithm)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Verify a signed file
|
|
82
|
+
#
|
|
83
|
+
# @param file_path [String] path to the signed file
|
|
84
|
+
# @param check_timestamp [Boolean] whether to verify timestamp (default: true)
|
|
85
|
+
# @param trust_store [Verification::TrustStore, nil] custom trust store
|
|
86
|
+
# @return [Verification::Result] verification result
|
|
87
|
+
#
|
|
88
|
+
# @example Verify a signed gem
|
|
89
|
+
# result = EasyCodeSign.verify("signed.gem")
|
|
90
|
+
# puts result.valid? ? "Valid!" : result.errors.join(", ")
|
|
91
|
+
#
|
|
92
|
+
def verify(file_path, check_timestamp: true, trust_store: nil)
|
|
93
|
+
verifier = Verifier.new(trust_store: trust_store)
|
|
94
|
+
verifier.verify(file_path, check_timestamp: check_timestamp)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Phase 1 of deferred PDF signing.
|
|
98
|
+
# Prepares a PDF with placeholder signature and returns a DeferredSigningRequest
|
|
99
|
+
# containing the digest to be signed by an external signer (Fortify, WebCrypto, etc.).
|
|
100
|
+
#
|
|
101
|
+
# @param file_path [String] path to the PDF
|
|
102
|
+
# @param pin [String, nil] PIN for hardware token (needed for certificate retrieval)
|
|
103
|
+
# @param digest_algorithm [String] "sha256", "sha384", or "sha512"
|
|
104
|
+
# @param timestamp [Boolean] whether to reserve timestamp space
|
|
105
|
+
# @return [DeferredSigningRequest]
|
|
106
|
+
#
|
|
107
|
+
# @example
|
|
108
|
+
# request = EasyCodeSign.prepare_pdf("document.pdf", pin: "1234")
|
|
109
|
+
# request.digest_base64 #=> "abc123..." (send to external signer)
|
|
110
|
+
#
|
|
111
|
+
def prepare_pdf(file_path, pin: nil, digest_algorithm: "sha256", timestamp: false, **extra_options)
|
|
112
|
+
signer = Signer.new
|
|
113
|
+
signer.prepare_pdf(file_path, pin: pin, digest_algorithm: digest_algorithm,
|
|
114
|
+
timestamp: timestamp, **extra_options)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Phase 2 of deferred PDF signing.
|
|
118
|
+
# Embeds an externally-produced raw signature into the prepared PDF.
|
|
119
|
+
#
|
|
120
|
+
# @param deferred_request [DeferredSigningRequest] from prepare_pdf
|
|
121
|
+
# @param raw_signature [String] raw signature bytes from external signer
|
|
122
|
+
# @return [SigningResult]
|
|
123
|
+
#
|
|
124
|
+
# @example
|
|
125
|
+
# result = EasyCodeSign.finalize_pdf(request, raw_signature)
|
|
126
|
+
# result.file_path #=> "document_prepared.pdf"
|
|
127
|
+
#
|
|
128
|
+
def finalize_pdf(deferred_request, raw_signature, **options)
|
|
129
|
+
signer = Signer.new
|
|
130
|
+
signer.finalize_pdf(deferred_request, raw_signature, **options)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Create a verifier instance for batch operations
|
|
134
|
+
# @param trust_store [Verification::TrustStore, nil]
|
|
135
|
+
# @return [Verifier]
|
|
136
|
+
def verifier(trust_store: nil)
|
|
137
|
+
Verifier.new(trust_store: trust_store)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# List available token slots
|
|
141
|
+
# @return [Array<Hash>] array of slot information
|
|
142
|
+
def list_slots
|
|
143
|
+
provider.list_slots
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Create a signer instance for batch operations
|
|
147
|
+
# @return [Signer]
|
|
148
|
+
def signer
|
|
149
|
+
Signer.new
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Detect file type and return appropriate signable handler
|
|
153
|
+
# @param file_path [String] path to file
|
|
154
|
+
# @return [Signable::Base] signable handler
|
|
155
|
+
def signable_for(file_path, **options)
|
|
156
|
+
extension = File.extname(file_path).downcase
|
|
157
|
+
|
|
158
|
+
case extension
|
|
159
|
+
when ".gem"
|
|
160
|
+
Signable::GemFile.new(file_path, **options)
|
|
161
|
+
when ".zip", ".jar", ".apk", ".war", ".ear"
|
|
162
|
+
Signable::ZipFile.new(file_path, **options)
|
|
163
|
+
when ".pdf"
|
|
164
|
+
Signable::PdfFile.new(file_path, **options)
|
|
165
|
+
else
|
|
166
|
+
raise InvalidFileError, "Unsupported file type: #{extension}"
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
private
|
|
171
|
+
|
|
172
|
+
def build_provider
|
|
173
|
+
configuration.validate!
|
|
174
|
+
|
|
175
|
+
case configuration.provider
|
|
176
|
+
when :safenet
|
|
177
|
+
Providers::Safenet.new(configuration)
|
|
178
|
+
else
|
|
179
|
+
raise ConfigurationError, "Unknown provider: #{configuration.provider}"
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
data/plugin/.gitignore
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Built extension output
|
|
2
|
+
dist/
|
|
3
|
+
|
|
4
|
+
# Ruby dependencies
|
|
5
|
+
.bundle/
|
|
6
|
+
vendor/bundle/
|
|
7
|
+
|
|
8
|
+
# Logs
|
|
9
|
+
*.log
|
|
10
|
+
|
|
11
|
+
# OS files
|
|
12
|
+
.DS_Store
|
|
13
|
+
Thumbs.db
|
|
14
|
+
|
|
15
|
+
# Editor files
|
|
16
|
+
*.swp
|
|
17
|
+
*.swo
|
|
18
|
+
*~
|
|
19
|
+
|
|
20
|
+
# Native host packaged binaries
|
|
21
|
+
native_host/dist/
|
data/plugin/Gemfile
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
source "https://rubygems.org"
|
|
4
|
+
|
|
5
|
+
# Opal - Ruby to JavaScript compiler
|
|
6
|
+
gem "opal", "~> 1.8"
|
|
7
|
+
gem "opal-sprockets", "~> 1.0"
|
|
8
|
+
|
|
9
|
+
# Required for Ruby 3.4+
|
|
10
|
+
gem "base64"
|
|
11
|
+
|
|
12
|
+
# Build tooling
|
|
13
|
+
gem "rake", "~> 13.0"
|
|
14
|
+
|
|
15
|
+
# For watching file changes during development
|
|
16
|
+
gem "listen", "~> 3.8"
|
|
17
|
+
|
|
18
|
+
# Testing - use minitest (native host tests are plain Ruby)
|
|
19
|
+
gem "minitest", "~> 5.0"
|
|
20
|
+
|
|
21
|
+
group :development do
|
|
22
|
+
# Code quality
|
|
23
|
+
gem "rubocop", require: false
|
|
24
|
+
end
|
data/plugin/Gemfile.lock
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
GEM
|
|
2
|
+
remote: https://rubygems.org/
|
|
3
|
+
specs:
|
|
4
|
+
ast (2.4.3)
|
|
5
|
+
base64 (0.3.0)
|
|
6
|
+
concurrent-ruby (1.3.6)
|
|
7
|
+
ffi (1.17.3)
|
|
8
|
+
ffi (1.17.3-aarch64-linux-gnu)
|
|
9
|
+
ffi (1.17.3-aarch64-linux-musl)
|
|
10
|
+
ffi (1.17.3-arm-linux-gnu)
|
|
11
|
+
ffi (1.17.3-arm-linux-musl)
|
|
12
|
+
ffi (1.17.3-arm64-darwin)
|
|
13
|
+
ffi (1.17.3-x86-linux-gnu)
|
|
14
|
+
ffi (1.17.3-x86-linux-musl)
|
|
15
|
+
ffi (1.17.3-x86_64-darwin)
|
|
16
|
+
ffi (1.17.3-x86_64-linux-gnu)
|
|
17
|
+
ffi (1.17.3-x86_64-linux-musl)
|
|
18
|
+
json (2.18.0)
|
|
19
|
+
language_server-protocol (3.17.0.5)
|
|
20
|
+
lint_roller (1.1.0)
|
|
21
|
+
listen (3.9.0)
|
|
22
|
+
rb-fsevent (~> 0.10, >= 0.10.3)
|
|
23
|
+
rb-inotify (~> 0.9, >= 0.9.10)
|
|
24
|
+
logger (1.7.0)
|
|
25
|
+
minitest (5.27.0)
|
|
26
|
+
opal (1.8.2)
|
|
27
|
+
ast (>= 2.3.0)
|
|
28
|
+
parser (~> 3.0, >= 3.0.3.2)
|
|
29
|
+
opal-sprockets (1.0.4)
|
|
30
|
+
opal (>= 1.0, < 2.0)
|
|
31
|
+
sprockets (~> 4.0)
|
|
32
|
+
tilt (>= 1.4)
|
|
33
|
+
parallel (1.27.0)
|
|
34
|
+
parser (3.3.10.0)
|
|
35
|
+
ast (~> 2.4.1)
|
|
36
|
+
racc
|
|
37
|
+
prism (1.7.0)
|
|
38
|
+
racc (1.8.1)
|
|
39
|
+
rack (3.2.4)
|
|
40
|
+
rainbow (3.1.1)
|
|
41
|
+
rake (13.3.1)
|
|
42
|
+
rb-fsevent (0.11.2)
|
|
43
|
+
rb-inotify (0.11.1)
|
|
44
|
+
ffi (~> 1.0)
|
|
45
|
+
regexp_parser (2.11.3)
|
|
46
|
+
rubocop (1.82.1)
|
|
47
|
+
json (~> 2.3)
|
|
48
|
+
language_server-protocol (~> 3.17.0.2)
|
|
49
|
+
lint_roller (~> 1.1.0)
|
|
50
|
+
parallel (~> 1.10)
|
|
51
|
+
parser (>= 3.3.0.2)
|
|
52
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
53
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
|
54
|
+
rubocop-ast (>= 1.48.0, < 2.0)
|
|
55
|
+
ruby-progressbar (~> 1.7)
|
|
56
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
|
57
|
+
rubocop-ast (1.49.0)
|
|
58
|
+
parser (>= 3.3.7.2)
|
|
59
|
+
prism (~> 1.7)
|
|
60
|
+
ruby-progressbar (1.13.0)
|
|
61
|
+
sprockets (4.2.2)
|
|
62
|
+
concurrent-ruby (~> 1.0)
|
|
63
|
+
logger
|
|
64
|
+
rack (>= 2.2.4, < 4)
|
|
65
|
+
tilt (2.6.1)
|
|
66
|
+
unicode-display_width (3.2.0)
|
|
67
|
+
unicode-emoji (~> 4.1)
|
|
68
|
+
unicode-emoji (4.2.0)
|
|
69
|
+
|
|
70
|
+
PLATFORMS
|
|
71
|
+
aarch64-linux-gnu
|
|
72
|
+
aarch64-linux-musl
|
|
73
|
+
arm-linux-gnu
|
|
74
|
+
arm-linux-musl
|
|
75
|
+
arm64-darwin
|
|
76
|
+
ruby
|
|
77
|
+
x86-linux-gnu
|
|
78
|
+
x86-linux-musl
|
|
79
|
+
x86_64-darwin
|
|
80
|
+
x86_64-linux-gnu
|
|
81
|
+
x86_64-linux-musl
|
|
82
|
+
|
|
83
|
+
DEPENDENCIES
|
|
84
|
+
base64
|
|
85
|
+
listen (~> 3.8)
|
|
86
|
+
minitest (~> 5.0)
|
|
87
|
+
opal (~> 1.8)
|
|
88
|
+
opal-sprockets (~> 1.0)
|
|
89
|
+
rake (~> 13.0)
|
|
90
|
+
rubocop
|
|
91
|
+
|
|
92
|
+
CHECKSUMS
|
|
93
|
+
ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
|
|
94
|
+
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
|
|
95
|
+
concurrent-ruby (1.3.6) sha256=6b56837e1e7e5292f9864f34b69c5a2cbc75c0cf5338f1ce9903d10fa762d5ab
|
|
96
|
+
ffi (1.17.3) sha256=0e9f39f7bb3934f77ad6feab49662be77e87eedcdeb2a3f5c0234c2938563d4c
|
|
97
|
+
ffi (1.17.3-aarch64-linux-gnu) sha256=28ad573df26560f0aedd8a90c3371279a0b2bd0b4e834b16a2baa10bd7a97068
|
|
98
|
+
ffi (1.17.3-aarch64-linux-musl) sha256=020b33b76775b1abacc3b7d86b287cef3251f66d747092deec592c7f5df764b2
|
|
99
|
+
ffi (1.17.3-arm-linux-gnu) sha256=5bd4cea83b68b5ec0037f99c57d5ce2dd5aa438f35decc5ef68a7d085c785668
|
|
100
|
+
ffi (1.17.3-arm-linux-musl) sha256=0d7626bb96265f9af78afa33e267d71cfef9d9a8eb8f5525344f8da6c7d76053
|
|
101
|
+
ffi (1.17.3-arm64-darwin) sha256=0c690555d4cee17a7f07c04d59df39b2fba74ec440b19da1f685c6579bb0717f
|
|
102
|
+
ffi (1.17.3-x86-linux-gnu) sha256=868a88fcaf5186c3a46b7c7c2b2c34550e1e61a405670ab23f5b6c9971529089
|
|
103
|
+
ffi (1.17.3-x86-linux-musl) sha256=f0286aa6ef40605cf586e61406c446de34397b85dbb08cc99fdaddaef8343945
|
|
104
|
+
ffi (1.17.3-x86_64-darwin) sha256=1f211811eb5cfaa25998322cdd92ab104bfbd26d1c4c08471599c511f2c00bb5
|
|
105
|
+
ffi (1.17.3-x86_64-linux-gnu) sha256=3746b01f677aae7b16dc1acb7cb3cc17b3e35bdae7676a3f568153fb0e2c887f
|
|
106
|
+
ffi (1.17.3-x86_64-linux-musl) sha256=086b221c3a68320b7564066f46fed23449a44f7a1935f1fe5a245bd89d9aea56
|
|
107
|
+
json (2.18.0) sha256=b10506aee4183f5cf49e0efc48073d7b75843ce3782c68dbeb763351c08fd505
|
|
108
|
+
language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc
|
|
109
|
+
lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
|
|
110
|
+
listen (3.9.0) sha256=db9e4424e0e5834480385197c139cb6b0ae0ef28cc13310cfd1ca78377d59c67
|
|
111
|
+
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
|
|
112
|
+
minitest (5.27.0) sha256=2d3b17f8a36fe7801c1adcffdbc38233b938eb0b4966e97a6739055a45fa77d5
|
|
113
|
+
opal (1.8.2) sha256=cedde4e1691d7cdaaf0aadac99fbf1fdb5bb90fe2dcc60e968f984601bd5d4e3
|
|
114
|
+
opal-sprockets (1.0.4) sha256=ba247b54c1cea23a4858f70a8851322c170436ed904b2c9c9d76d0b07e25ccfc
|
|
115
|
+
parallel (1.27.0) sha256=4ac151e1806b755fb4e2dc2332cbf0e54f2e24ba821ff2d3dcf86bf6dc4ae130
|
|
116
|
+
parser (3.3.10.0) sha256=ce3587fa5cc55a88c4ba5b2b37621b3329aadf5728f9eafa36bbd121462aabd6
|
|
117
|
+
prism (1.7.0) sha256=10062f734bf7985c8424c44fac382ac04a58124ea3d220ec3ba9fe4f2da65103
|
|
118
|
+
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
|
|
119
|
+
rack (3.2.4) sha256=5d74b6f75082a643f43c1e76b419c40f0e5527fcfee1e669ac1e6b73c0ccb6f6
|
|
120
|
+
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
|
|
121
|
+
rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c
|
|
122
|
+
rb-fsevent (0.11.2) sha256=43900b972e7301d6570f64b850a5aa67833ee7d87b458ee92805d56b7318aefe
|
|
123
|
+
rb-inotify (0.11.1) sha256=a0a700441239b0ff18eb65e3866236cd78613d6b9f78fea1f9ac47a85e47be6e
|
|
124
|
+
regexp_parser (2.11.3) sha256=ca13f381a173b7a93450e53459075c9b76a10433caadcb2f1180f2c741fc55a4
|
|
125
|
+
rubocop (1.82.1) sha256=09f1a6a654a960eda767aebea33e47603080f8e9c9a3f019bf9b94c9cab5e273
|
|
126
|
+
rubocop-ast (1.49.0) sha256=49c3676d3123a0923d333e20c6c2dbaaae2d2287b475273fddee0c61da9f71fd
|
|
127
|
+
ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
|
|
128
|
+
sprockets (4.2.2) sha256=761e5a49f1c288704763f73139763564c845a8f856d52fba013458f8af1b59b1
|
|
129
|
+
tilt (2.6.1) sha256=35a99bba2adf7c1e362f5b48f9b581cce4edfba98117e34696dde6d308d84770
|
|
130
|
+
unicode-display_width (3.2.0) sha256=0cdd96b5681a5949cdbc2c55e7b420facae74c4aaf9a9815eee1087cb1853c42
|
|
131
|
+
unicode-emoji (4.2.0) sha256=519e69150f75652e40bf736106cfbc8f0f73aa3fb6a65afe62fefa7f80b0f80f
|
|
132
|
+
|
|
133
|
+
BUNDLED WITH
|
|
134
|
+
4.0.3
|
data/plugin/README.md
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# EasySign Browser Plugin
|
|
2
|
+
|
|
3
|
+
Browser extension for signing PDF documents using hardware security tokens (HSM/smart cards).
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
┌─────────────────────────────────────────────────────┐
|
|
9
|
+
│ Web App (Rails, etc.) │
|
|
10
|
+
│ window.EasySign.sign(pdfBlob, options) │
|
|
11
|
+
└─────────────────┬───────────────────────────────────┘
|
|
12
|
+
│ postMessage
|
|
13
|
+
┌─────────────────▼───────────────────────────────────┐
|
|
14
|
+
│ Content Script (Opal.rb → JS) │
|
|
15
|
+
│ Bridges page ↔ extension, validates origins │
|
|
16
|
+
└─────────────────┬───────────────────────────────────┘
|
|
17
|
+
│ chrome.runtime.sendMessage
|
|
18
|
+
┌─────────────────▼───────────────────────────────────┐
|
|
19
|
+
│ Background Service Worker (Opal.rb → JS) │
|
|
20
|
+
│ Opens PIN popup, manages native connection │
|
|
21
|
+
└─────────────────┬───────────────────────────────────┘
|
|
22
|
+
│ Native Messaging (JSON over stdio)
|
|
23
|
+
┌─────────────────▼───────────────────────────────────┐
|
|
24
|
+
│ Native Host (Ruby → Packaged Binary) │
|
|
25
|
+
│ Uses EasyCodeSign gem for actual signing │
|
|
26
|
+
│ Accesses PKCS#11 hardware token │
|
|
27
|
+
└─────────────────────────────────────────────────────┘
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Development Setup
|
|
31
|
+
|
|
32
|
+
### Prerequisites
|
|
33
|
+
|
|
34
|
+
- Ruby 3.2+
|
|
35
|
+
- Node.js (optional, for alternative build tools)
|
|
36
|
+
- Chrome or Firefox browser
|
|
37
|
+
|
|
38
|
+
### Install Dependencies
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
cd plugin
|
|
42
|
+
bundle install
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Build Extension
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# Build all components
|
|
49
|
+
bundle exec rake build
|
|
50
|
+
|
|
51
|
+
# Watch for changes during development
|
|
52
|
+
bundle exec rake watch
|
|
53
|
+
|
|
54
|
+
# Clean build artifacts
|
|
55
|
+
bundle exec rake clean
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Load Extension in Browser
|
|
59
|
+
|
|
60
|
+
**Chrome:**
|
|
61
|
+
1. Open `chrome://extensions`
|
|
62
|
+
2. Enable "Developer mode"
|
|
63
|
+
3. Click "Load unpacked"
|
|
64
|
+
4. Select the `plugin/dist` directory
|
|
65
|
+
|
|
66
|
+
**Firefox:**
|
|
67
|
+
1. Open `about:debugging`
|
|
68
|
+
2. Click "This Firefox"
|
|
69
|
+
3. Click "Load Temporary Add-on"
|
|
70
|
+
4. Select `plugin/dist/manifest.json`
|
|
71
|
+
|
|
72
|
+
### Install Native Host (Development)
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# For Chrome
|
|
76
|
+
./native_host/install/install_chrome.sh
|
|
77
|
+
|
|
78
|
+
# For Firefox
|
|
79
|
+
./native_host/install/install_firefox.sh
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Web App Integration
|
|
83
|
+
|
|
84
|
+
### Check Availability
|
|
85
|
+
|
|
86
|
+
```javascript
|
|
87
|
+
window.EasySign.isAvailable()
|
|
88
|
+
.then(result => {
|
|
89
|
+
console.log('Extension installed:', result.available);
|
|
90
|
+
console.log('Token connected:', result.tokenPresent);
|
|
91
|
+
console.log('Available slots:', result.slots);
|
|
92
|
+
})
|
|
93
|
+
.catch(err => {
|
|
94
|
+
console.error('EasySign not available:', err);
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Sign a PDF
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
// Get PDF as Blob (from file input, fetch, etc.)
|
|
102
|
+
const pdfBlob = await fetch('/document.pdf').then(r => r.blob());
|
|
103
|
+
|
|
104
|
+
// Sign the PDF
|
|
105
|
+
window.EasySign.sign(pdfBlob, {
|
|
106
|
+
reason: 'Approved',
|
|
107
|
+
location: 'New York',
|
|
108
|
+
visibleSignature: true,
|
|
109
|
+
signaturePosition: 'bottom_right',
|
|
110
|
+
timestamp: true
|
|
111
|
+
})
|
|
112
|
+
.then(result => {
|
|
113
|
+
console.log('Signed by:', result.signer_name);
|
|
114
|
+
console.log('Signed at:', result.signed_at);
|
|
115
|
+
|
|
116
|
+
// Download signed PDF
|
|
117
|
+
const url = URL.createObjectURL(result.blob);
|
|
118
|
+
const a = document.createElement('a');
|
|
119
|
+
a.href = url;
|
|
120
|
+
a.download = 'signed_document.pdf';
|
|
121
|
+
a.click();
|
|
122
|
+
|
|
123
|
+
// Or upload to server
|
|
124
|
+
const formData = new FormData();
|
|
125
|
+
formData.append('signed_pdf', result.blob, 'signed.pdf');
|
|
126
|
+
fetch('/upload', { method: 'POST', body: formData });
|
|
127
|
+
})
|
|
128
|
+
.catch(err => {
|
|
129
|
+
console.error('Signing failed:', err.message, err.code);
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Verify a Signed PDF
|
|
134
|
+
|
|
135
|
+
```javascript
|
|
136
|
+
window.EasySign.verify(signedPdfBlob)
|
|
137
|
+
.then(result => {
|
|
138
|
+
if (result.payload.valid) {
|
|
139
|
+
console.log('Signature is valid!');
|
|
140
|
+
console.log('Signer:', result.payload.signerName);
|
|
141
|
+
} else {
|
|
142
|
+
console.log('Signature invalid:', result.payload.errors);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## API Reference
|
|
148
|
+
|
|
149
|
+
### `window.EasySign.isAvailable()`
|
|
150
|
+
|
|
151
|
+
Check if extension is installed and token is connected.
|
|
152
|
+
|
|
153
|
+
**Returns:** `Promise<Object>`
|
|
154
|
+
- `available` (boolean): Extension is installed and native host is connected
|
|
155
|
+
- `tokenPresent` (boolean): Hardware token is connected
|
|
156
|
+
- `slots` (Array): List of available token slots
|
|
157
|
+
|
|
158
|
+
### `window.EasySign.sign(pdfBlob, options)`
|
|
159
|
+
|
|
160
|
+
Sign a PDF document. Opens PIN entry popup.
|
|
161
|
+
|
|
162
|
+
**Parameters:**
|
|
163
|
+
- `pdfBlob` (Blob): The PDF file to sign
|
|
164
|
+
- `options` (Object):
|
|
165
|
+
- `reason` (string): Reason for signing
|
|
166
|
+
- `location` (string): Location of signing
|
|
167
|
+
- `visibleSignature` (boolean): Add visible signature annotation
|
|
168
|
+
- `signaturePosition` (string): Position (`top_left`, `top_right`, `bottom_left`, `bottom_right`)
|
|
169
|
+
- `signaturePage` (number): Page number for signature (default: 1)
|
|
170
|
+
- `timestamp` (boolean): Add RFC 3161 timestamp
|
|
171
|
+
|
|
172
|
+
**Returns:** `Promise<Object>`
|
|
173
|
+
- `blob` (Blob): Signed PDF file
|
|
174
|
+
- `signer_name` (string): Name from signing certificate
|
|
175
|
+
- `signed_at` (string): ISO 8601 timestamp
|
|
176
|
+
- `timestamped` (boolean): Whether timestamp was added
|
|
177
|
+
|
|
178
|
+
### `window.EasySign.verify(pdfBlob, options)`
|
|
179
|
+
|
|
180
|
+
Verify a signed PDF document.
|
|
181
|
+
|
|
182
|
+
**Parameters:**
|
|
183
|
+
- `pdfBlob` (Blob): The signed PDF file
|
|
184
|
+
- `options` (Object):
|
|
185
|
+
- `checkTimestamp` (boolean): Verify timestamp (default: true)
|
|
186
|
+
|
|
187
|
+
**Returns:** `Promise<Object>` with verification details
|
|
188
|
+
|
|
189
|
+
## Error Handling
|
|
190
|
+
|
|
191
|
+
```javascript
|
|
192
|
+
window.EasySign.sign(pdfBlob, options)
|
|
193
|
+
.catch(err => {
|
|
194
|
+
switch (err.code) {
|
|
195
|
+
case 'TOKEN_NOT_FOUND':
|
|
196
|
+
alert('Please connect your hardware token');
|
|
197
|
+
break;
|
|
198
|
+
case 'PIN_INCORRECT':
|
|
199
|
+
alert('Incorrect PIN');
|
|
200
|
+
break;
|
|
201
|
+
case 'TOKEN_LOCKED':
|
|
202
|
+
alert('Token is locked. Contact administrator.');
|
|
203
|
+
break;
|
|
204
|
+
case 'CANCELLED':
|
|
205
|
+
// User cancelled - no action needed
|
|
206
|
+
break;
|
|
207
|
+
default:
|
|
208
|
+
alert('Signing failed: ' + err.message);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Building for Production
|
|
214
|
+
|
|
215
|
+
### Package Native Host
|
|
216
|
+
|
|
217
|
+
The native host can be packaged as a self-contained executable:
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
cd native_host
|
|
221
|
+
bundle exec rake build:package
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
This creates platform-specific binaries in `native_host/dist/`.
|
|
225
|
+
|
|
226
|
+
### Create Installer
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
# macOS
|
|
230
|
+
./create_installer_macos.sh
|
|
231
|
+
|
|
232
|
+
# Windows
|
|
233
|
+
./create_installer_windows.bat
|
|
234
|
+
|
|
235
|
+
# Linux
|
|
236
|
+
./create_installer_linux.sh
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Security
|
|
240
|
+
|
|
241
|
+
- PIN is entered only in browser popup, never on web pages
|
|
242
|
+
- PIN is passed directly to native host, never stored
|
|
243
|
+
- Origin validation restricts which websites can use the API
|
|
244
|
+
- All signing happens in the native host, private keys never leave the token
|
|
245
|
+
|
|
246
|
+
## License
|
|
247
|
+
|
|
248
|
+
MIT License - See LICENSE file
|