shojiku 0.1.0 → 0.2.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 +4 -4
- data/README.md +46 -15
- data/lib/shojiku/client.rb +6 -6
- data/lib/shojiku/engine.rb +46 -14
- data/lib/shojiku/external_signer.rb +144 -0
- data/lib/shojiku/local_pem.rb +14 -4
- data/lib/shojiku/version.rb +1 -1
- data/lib/shojiku.rb +1 -0
- metadata +5 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3b481d059f220a867ef40b179f1144342866f85f742e6efec0b7c6a6ee153207
|
|
4
|
+
data.tar.gz: 0b9644e8961d4c0f759929b7d0dbd7d3bb76415fe63d5efd7d65f20a1899be1b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e2220169bdc93028f743bcfe407a3bcfb8f830ee3a8b9a1109bd121c76c76f07675583cbff1300b19be836305e202b6a455255d7643869c4dc01d806b3d80bde
|
|
7
|
+
data.tar.gz: 82a9ebddebaca0137c4b72a5c6ac858767f4f7a7f0b555a5288405f2cf0f23d8ce0ea07b5925d2a6af04b39878e2a26ab93f3f3b4d43417546815ddaffb31cea
|
data/README.md
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
# Shojiku for Ruby
|
|
2
2
|
|
|
3
|
-
Ruby bindings for [Shojiku](
|
|
3
|
+
Ruby bindings for [Shojiku](https://shojiku.pages.dev) — a document engine that
|
|
4
4
|
turns a YAML template plus your data into a deterministic PDF, signs it,
|
|
5
5
|
and verifies it.
|
|
6
6
|
|
|
7
|
-
> **Unreleased.** The gem is written and gated but not published yet —
|
|
8
|
-
> all seven Shojiku SDKs publish together at v0.1.0. Until then, install
|
|
9
|
-
> from a repository clone (see [Building the engine
|
|
10
|
-
> library](#building-the-engine-library) below), or use the CLI or the
|
|
11
|
-
> Docker image — see the [quickstart](../../docs/quickstart.md).
|
|
12
|
-
|
|
13
7
|
## Install
|
|
14
8
|
|
|
15
9
|
```bash
|
|
@@ -104,6 +98,39 @@ on a passing verdict as well as a failing one. A document whose
|
|
|
104
98
|
signature does not verify comes back as a *failed* result that still
|
|
105
99
|
carries the full report.
|
|
106
100
|
|
|
101
|
+
### Signing with a key this process never holds
|
|
102
|
+
|
|
103
|
+
When the private key lives in a cloud KMS, an HSM or a smartcard, use
|
|
104
|
+
`ExternalSigner` instead. Shojiku hands out the bytes a signature has to
|
|
105
|
+
cover; your block signs them wherever the key is and hands the signature
|
|
106
|
+
back, so the key never enters your application:
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
provider = Shojiku::ExternalSigner.new(cert: "signer.crt",
|
|
110
|
+
algorithm: :ecdsa_p256_sha256) do |to_be_signed|
|
|
111
|
+
kms.sign(key_id: ENV.fetch("KEY_ID"), message: to_be_signed,
|
|
112
|
+
message_type: "RAW", signing_algorithm: "ECDSA_SHA_256").signature
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
signed = result.artifact.sign(provider)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The call site is unchanged — which provider you pass is the only
|
|
119
|
+
difference, and a provider registered by name works the same way under
|
|
120
|
+
`strict:`. This gem ships no cloud client of its own: the block is
|
|
121
|
+
whichever client your application already uses.
|
|
122
|
+
|
|
123
|
+
Two details worth getting right. The bytes handed to the block are the
|
|
124
|
+
CMS **signed attributes**, not the document's digest — a service that
|
|
125
|
+
signs a digest must hash *these* bytes with SHA-256 itself. And the
|
|
126
|
+
signature is that operation's raw output: PKCS#1 v1.5 bytes for
|
|
127
|
+
`:rsa_pkcs1_sha256`, an ASN.1 DER sequence for `:ecdsa_p256_sha256`,
|
|
128
|
+
which is what AWS KMS and Google Cloud KMS both return unchanged.
|
|
129
|
+
|
|
130
|
+
Exceptions raised inside your block are *not* swallowed into a failed
|
|
131
|
+
result: an outage at your key service is not a fact about the document,
|
|
132
|
+
and `Result#failure?` would read as one.
|
|
133
|
+
|
|
107
134
|
### Results, not exceptions
|
|
108
135
|
|
|
109
136
|
Nothing raises in the normal flow. Every operation returns a result you
|
|
@@ -282,7 +309,9 @@ Nothing in this gem downloads anything, at install time or at run time.
|
|
|
282
309
|
|
|
283
310
|
## Building the engine library
|
|
284
311
|
|
|
285
|
-
|
|
312
|
+
The platform gems carry the library, so this is only for a platform they
|
|
313
|
+
do not cover, or for working against an engine you changed. Build it from
|
|
314
|
+
a clone:
|
|
286
315
|
|
|
287
316
|
```bash
|
|
288
317
|
make capi-lib
|
|
@@ -307,13 +336,15 @@ Ruby 3.3 or newer.
|
|
|
307
336
|
|
|
308
337
|
## Documentation
|
|
309
338
|
|
|
310
|
-
- [Template reference](
|
|
311
|
-
YAML the engine renders
|
|
312
|
-
- [SDK policy](
|
|
313
|
-
Shojiku SDK implements
|
|
339
|
+
- [Template reference](https://github.com/kengos/shojiku/blob/main/docs/engine/README.md) —
|
|
340
|
+
how to write the YAML the engine renders
|
|
341
|
+
- [SDK policy](https://github.com/kengos/shojiku/blob/main/docs/agents/sdk.md) —
|
|
342
|
+
the lifecycle contract every Shojiku SDK implements
|
|
314
343
|
|
|
315
344
|
## License
|
|
316
345
|
|
|
317
|
-
Licensed under any of
|
|
318
|
-
[
|
|
319
|
-
|
|
346
|
+
Licensed under any of
|
|
347
|
+
[Apache-2.0](https://github.com/kengos/shojiku/blob/main/LICENSE-APACHE),
|
|
348
|
+
[MIT](https://github.com/kengos/shojiku/blob/main/LICENSE-MIT), or
|
|
349
|
+
[BSD-3-Clause](https://github.com/kengos/shojiku/blob/main/LICENSE-BSD),
|
|
350
|
+
at your option.
|
data/lib/shojiku/client.rb
CHANGED
|
@@ -128,8 +128,11 @@ module Shojiku
|
|
|
128
128
|
# Signs an artifact with `provider`, returning a {Result}. The signed
|
|
129
129
|
# bytes begin with the input byte for byte — signing appends a revision.
|
|
130
130
|
#
|
|
131
|
-
# `provider` is a {LocalPem} (
|
|
132
|
-
#
|
|
131
|
+
# `provider` is a {LocalPem} (a key in this process), an {ExternalSigner}
|
|
132
|
+
# (a key that never enters it — a cloud KMS, an HSM, a smartcard), or the
|
|
133
|
+
# NAME of one registered in configuration. A strict client takes the name
|
|
134
|
+
# only. Which kind it is changes nothing here: the provider knows how to
|
|
135
|
+
# reach the engine for its own sort of key.
|
|
133
136
|
def sign(artifact, provider)
|
|
134
137
|
signer = @settings.lockdown.provider!(provider)
|
|
135
138
|
@settings.lockdown.signable!(artifact)
|
|
@@ -177,10 +180,7 @@ module Shojiku
|
|
|
177
180
|
# The signed document inherits the origin of what it signed: appending a
|
|
178
181
|
# revision does not launder where the document came from.
|
|
179
182
|
def signed(artifact, provider)
|
|
180
|
-
snapshot = @engine.
|
|
181
|
-
pdf: artifact.bytes, key: provider.key, certificate: provider.certificate,
|
|
182
|
-
passphrase: provider.passphrase
|
|
183
|
-
)
|
|
183
|
+
snapshot = provider.sign_with(@engine, artifact.bytes)
|
|
184
184
|
Outcome.document(snapshot, step: :sign, client: self, origin: artifact.origin)
|
|
185
185
|
rescue MaterialUnreadable => e
|
|
186
186
|
Result.failed(Failure.new(step: :sign, kind: e.kind, message: e.message))
|
data/lib/shojiku/engine.rb
CHANGED
|
@@ -33,46 +33,78 @@ module Shojiku
|
|
|
33
33
|
INT32 = "l"
|
|
34
34
|
SIZE = Fiddle::SIZEOF_SIZE_T == 8 ? "Q" : "L"
|
|
35
35
|
|
|
36
|
+
# Every data argument crosses as a (pointer, length) PAIR — nothing is
|
|
37
|
+
# NUL-terminated, because PDF bytes contain NUL — and every call ends with
|
|
38
|
+
# the out-slot the result handle is written to. Spelling the signatures as
|
|
39
|
+
# a table rather than one call each keeps them impossible to disagree
|
|
40
|
+
# with the header: an operation's arity is the count of its arguments.
|
|
41
|
+
PAIR = [VOIDP, SIZE_T].freeze
|
|
42
|
+
OUT = [VOIDP].freeze
|
|
43
|
+
|
|
36
44
|
# Only the lifecycle the SDK contract defines is bound: engine info,
|
|
37
45
|
# render, sign, verify. `validate` and `preview` are the authoring
|
|
38
46
|
# surface's, not an artifact lifecycle's — the Designer reaches them
|
|
39
47
|
# through the WASM bindings, and binding them here would be surface with
|
|
40
48
|
# no contract behind it.
|
|
49
|
+
SIGNATURES = {
|
|
50
|
+
shojiku_engine_info: OUT,
|
|
51
|
+
shojiku_render: PAIR + OUT,
|
|
52
|
+
shojiku_sign: (PAIR * 4) + OUT,
|
|
53
|
+
shojiku_sign_prepare: (PAIR * 3) + OUT,
|
|
54
|
+
shojiku_sign_complete: (PAIR * 4) + OUT,
|
|
55
|
+
shojiku_verify: (PAIR * 2) + OUT
|
|
56
|
+
}.freeze
|
|
57
|
+
|
|
41
58
|
def initialize(library)
|
|
42
59
|
@library = library
|
|
43
|
-
@
|
|
44
|
-
@render = library.function(:shojiku_render, [VOIDP, SIZE_T, VOIDP], INT)
|
|
45
|
-
@sign = library.function(
|
|
46
|
-
:shojiku_sign,
|
|
47
|
-
[VOIDP, SIZE_T, VOIDP, SIZE_T, VOIDP, SIZE_T, VOIDP, SIZE_T, VOIDP], INT
|
|
48
|
-
)
|
|
49
|
-
@verify = library.function(:shojiku_verify, [VOIDP, SIZE_T, VOIDP, SIZE_T, VOIDP], INT)
|
|
60
|
+
@calls = SIGNATURES.to_h { |name, args| [name, library.function(name, args, INT)] }
|
|
50
61
|
declare_accessors(library)
|
|
51
62
|
end
|
|
52
63
|
|
|
53
64
|
def engine_info
|
|
54
|
-
invoke { |out|
|
|
65
|
+
invoke { |out| call(:shojiku_engine_info, out) }
|
|
55
66
|
end
|
|
56
67
|
|
|
57
68
|
def render(request)
|
|
58
|
-
invoke { |out|
|
|
69
|
+
invoke { |out| call(:shojiku_render, request, request.bytesize, out) }
|
|
59
70
|
end
|
|
60
71
|
|
|
61
72
|
def sign(pdf:, key:, certificate:, passphrase: nil)
|
|
62
73
|
invoke do |out|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
74
|
+
call(:shojiku_sign, pdf, pdf.bytesize, key, key.bytesize,
|
|
75
|
+
certificate, certificate.bytesize,
|
|
76
|
+
passphrase, passphrase ? passphrase.bytesize : 0, out)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# The two halves of signing with a key held elsewhere. Both take the same
|
|
81
|
+
# document, certificate and algorithm — the pair is stateless, so the
|
|
82
|
+
# second call re-derives what the first prepared.
|
|
83
|
+
def sign_prepare(pdf:, certificate:, algorithm:)
|
|
84
|
+
invoke do |out|
|
|
85
|
+
call(:shojiku_sign_prepare, pdf, pdf.bytesize, certificate, certificate.bytesize,
|
|
86
|
+
algorithm, algorithm.bytesize, out)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def sign_complete(pdf:, certificate:, algorithm:, signature:)
|
|
91
|
+
invoke do |out|
|
|
92
|
+
call(:shojiku_sign_complete, pdf, pdf.bytesize, certificate, certificate.bytesize,
|
|
93
|
+
algorithm, algorithm.bytesize, signature, signature.bytesize, out)
|
|
67
94
|
end
|
|
68
95
|
end
|
|
69
96
|
|
|
70
97
|
def verify(pdf:, anchors:)
|
|
71
|
-
invoke { |out|
|
|
98
|
+
invoke { |out| call(:shojiku_verify, pdf, pdf.bytesize, anchors, anchors.bytesize, out) }
|
|
72
99
|
end
|
|
73
100
|
|
|
74
101
|
private
|
|
75
102
|
|
|
103
|
+
# The one place a lifecycle call crosses.
|
|
104
|
+
def call(name, *)
|
|
105
|
+
@calls.fetch(name).call(*)
|
|
106
|
+
end
|
|
107
|
+
|
|
76
108
|
def declare_accessors(library)
|
|
77
109
|
buffers = %i[shojiku_result_pdf shojiku_result_json shojiku_result_diagnostics_json
|
|
78
110
|
shojiku_result_error_json]
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shojiku
|
|
4
|
+
# A signing provider for a key this process is never given.
|
|
5
|
+
#
|
|
6
|
+
# The second provider, and the shape {LocalPem}'s own comment promised: a
|
|
7
|
+
# new class rather than new arguments on `sign`, so the call site is
|
|
8
|
+
# unchanged in all seven SDKs.
|
|
9
|
+
#
|
|
10
|
+
# The engine hands out the bytes a signature has to cover; the block signs
|
|
11
|
+
# them wherever the key actually lives — AWS KMS, Google Cloud KMS, an HSM,
|
|
12
|
+
# a smartcard, another service entirely — and hands the signature back:
|
|
13
|
+
#
|
|
14
|
+
# provider = Shojiku::ExternalSigner.new(cert: "signer.crt",
|
|
15
|
+
# algorithm: :ecdsa_p256_sha256) do |to_be_signed|
|
|
16
|
+
# kms.sign(key_id: ENV.fetch("KEY_ID"), message: to_be_signed,
|
|
17
|
+
# message_type: "RAW", signing_algorithm: "ECDSA_SHA_256").signature
|
|
18
|
+
# end
|
|
19
|
+
# client.sign(artifact, provider)
|
|
20
|
+
#
|
|
21
|
+
# Shojiku ships no cloud client of its own, deliberately: the block is
|
|
22
|
+
# whatever client your application already has, and the SDK stays a wrapper
|
|
23
|
+
# with nothing to keep in step with a vendor's releases.
|
|
24
|
+
#
|
|
25
|
+
# **What the block receives is the signed ATTRIBUTES, not the document
|
|
26
|
+
# digest.** A service that signs a digest must hash these bytes with SHA-256
|
|
27
|
+
# itself. Signing the document digest instead produces a document that fails
|
|
28
|
+
# verification, so the distinction is not cosmetic.
|
|
29
|
+
#
|
|
30
|
+
# The signature is the raw output of that operation: PKCS#1 v1.5 bytes for
|
|
31
|
+
# `:rsa_pkcs1_sha256`, an ASN.1 DER sequence for `:ecdsa_p256_sha256` —
|
|
32
|
+
# which is what both major cloud key services return unchanged.
|
|
33
|
+
#
|
|
34
|
+
# The certificate comes either from a path (`cert:`) or from bytes already
|
|
35
|
+
# in memory (`cert_pem:`), explicit rather than sniffed, exactly as
|
|
36
|
+
# {LocalPem} takes its material.
|
|
37
|
+
class ExternalSigner
|
|
38
|
+
FORMS = "`cert:` (a path) or `cert_pem:` (bytes)"
|
|
39
|
+
|
|
40
|
+
# The wire spellings the engine accepts, keyed by the Ruby-side names.
|
|
41
|
+
# A Symbol reads better at a call site; a String is accepted because
|
|
42
|
+
# configuration files produce them.
|
|
43
|
+
ALGORITHMS = {
|
|
44
|
+
rsa_pkcs1_sha256: "rsa-pkcs1-sha256",
|
|
45
|
+
ecdsa_p256_sha256: "ecdsa-p256-sha256"
|
|
46
|
+
}.freeze
|
|
47
|
+
|
|
48
|
+
attr_reader :algorithm
|
|
49
|
+
|
|
50
|
+
def initialize(cert: nil, cert_pem: nil, algorithm: nil, &block)
|
|
51
|
+
@cert_path = cert
|
|
52
|
+
@cert_pem = cert_pem
|
|
53
|
+
@algorithm = wire_algorithm(algorithm)
|
|
54
|
+
@block = block
|
|
55
|
+
one_source!(cert, cert_pem)
|
|
56
|
+
raise UsageError, "ExternalSigner needs a block that signs the bytes it is given" unless block
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Redacted for the same reason {LocalPem} is. Nothing here is key
|
|
60
|
+
# material — that is the point of this provider — but a block closes over
|
|
61
|
+
# whatever built it, which in practice is a client holding credentials.
|
|
62
|
+
def inspect
|
|
63
|
+
"#<#{self.class.name} cert=#{form} algorithm=#{@algorithm}>"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def certificate
|
|
67
|
+
@certificate ||= @cert_pem || Material.read(@cert_path, "certificate_unreadable")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Signs `pdf` in two calls, with the block in between.
|
|
71
|
+
#
|
|
72
|
+
# Both engine calls take the same document, certificate and algorithm:
|
|
73
|
+
# the pair is stateless, so the second re-derives what the first prepared.
|
|
74
|
+
# Keeping them inside ONE method is what makes that impossible to get
|
|
75
|
+
# wrong from Ruby — there is no way to pair a prepare of one document
|
|
76
|
+
# with a complete of another.
|
|
77
|
+
#
|
|
78
|
+
# A prepare that did not succeed is returned as it is: an unreadable
|
|
79
|
+
# certificate or a document the signer refuses is a fact about the
|
|
80
|
+
# inputs, and paying for a signature afterwards would tell the caller
|
|
81
|
+
# nothing new.
|
|
82
|
+
def sign_with(engine, pdf)
|
|
83
|
+
prepared = engine.sign_prepare(pdf: pdf, certificate: certificate, algorithm: @algorithm)
|
|
84
|
+
return prepared unless prepared.status.zero? && prepared.success
|
|
85
|
+
|
|
86
|
+
engine.sign_complete(
|
|
87
|
+
pdf: pdf, certificate: certificate, algorithm: @algorithm,
|
|
88
|
+
signature: signature_for(prepared)
|
|
89
|
+
)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
# Runs the block over the bytes the engine wants signed.
|
|
95
|
+
#
|
|
96
|
+
# The block's own exceptions are deliberately not rescued: it is the
|
|
97
|
+
# caller's code talking to the caller's key service, and turning its
|
|
98
|
+
# failures into a failed {Result} would file a caller's outage under
|
|
99
|
+
# "something was wrong with this document".
|
|
100
|
+
# `unpack1("m")` rather than `Base64.decode64`: base64 is a bundled gem
|
|
101
|
+
# now, and this SDK's runtime dependency list is exactly one entry
|
|
102
|
+
# (fiddle) on purpose. The unpack directive is core String, so decoding
|
|
103
|
+
# the payload costs nothing an application has to install.
|
|
104
|
+
def signature_for(prepared)
|
|
105
|
+
to_be_signed = JSON.parse(prepared.json).fetch("toBeSigned").unpack1("m")
|
|
106
|
+
signature = @block.call(to_be_signed)
|
|
107
|
+
unless signature.is_a?(String) && !signature.empty?
|
|
108
|
+
raise UsageError,
|
|
109
|
+
"the signer block must return the signature as a non-empty String of bytes"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
signature.b
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def wire_algorithm(algorithm)
|
|
116
|
+
raise UsageError, "ExternalSigner needs `algorithm:` (#{named})" if algorithm.nil?
|
|
117
|
+
|
|
118
|
+
ALGORITHMS.fetch(algorithm.to_sym) do
|
|
119
|
+
raise UsageError, "`algorithm:` must be one of #{named}"
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def named
|
|
124
|
+
ALGORITHMS.keys.map(&:inspect).join(" or ")
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# The path, or a note that the bytes came from memory — a configured path
|
|
128
|
+
# is not secret, and it is the one thing worth seeing when a provider
|
|
129
|
+
# loaded the wrong certificate.
|
|
130
|
+
def form
|
|
131
|
+
@cert_path ? @cert_path.to_s : "[pem bytes]"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Explicit, never sniffed, in BOTH directions — {LocalPem}'s rule, for
|
|
135
|
+
# the same reason: guessing whether a string is a path or a PEM body is
|
|
136
|
+
# how the wrong file gets read.
|
|
137
|
+
def one_source!(path, pem)
|
|
138
|
+
raise UsageError, "ExternalSigner takes either #{FORMS}, not both" if path && pem
|
|
139
|
+
return if path || pem
|
|
140
|
+
|
|
141
|
+
raise UsageError, "ExternalSigner needs either #{FORMS}"
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
data/lib/shojiku/local_pem.rb
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
module Shojiku
|
|
4
4
|
# A signing provider backed by a PEM key and certificate.
|
|
5
5
|
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# change in seven languages.
|
|
6
|
+
# For a key this process may hold. When it may not — a cloud KMS, an HSM, a
|
|
7
|
+
# smartcard — the provider is an {ExternalSigner} instead, which is why this
|
|
8
|
+
# is a named class rather than a pair of arguments on `sign`: the second
|
|
9
|
+
# provider added a class, not a signature change in seven languages.
|
|
10
10
|
#
|
|
11
11
|
# The material comes either from paths (`key:` / `cert:`) or from bytes
|
|
12
12
|
# already in memory (`key_pem:` / `cert_pem:`), so a key fetched from a
|
|
@@ -53,6 +53,16 @@ module Shojiku
|
|
|
53
53
|
@certificate ||= @cert_pem || Material.read(@cert_path, "certificate_unreadable")
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
+
# Signs `pdf`, in one call, with the key this provider holds.
|
|
57
|
+
#
|
|
58
|
+
# Each provider knows how to reach the engine for its own kind of key, so
|
|
59
|
+
# the client asks the provider rather than branching on its class — the
|
|
60
|
+
# difference between a local key and one held elsewhere is two engine
|
|
61
|
+
# calls instead of one, and that belongs here.
|
|
62
|
+
def sign_with(engine, pdf)
|
|
63
|
+
engine.sign(pdf: pdf, key: key, certificate: certificate, passphrase: passphrase)
|
|
64
|
+
end
|
|
65
|
+
|
|
56
66
|
private
|
|
57
67
|
|
|
58
68
|
# The path, or a note that the bytes came from memory. A configured file
|
data/lib/shojiku/version.rb
CHANGED
data/lib/shojiku.rb
CHANGED
|
@@ -14,6 +14,7 @@ require_relative "shojiku/result"
|
|
|
14
14
|
require_relative "shojiku/verification_report"
|
|
15
15
|
require_relative "shojiku/artifact"
|
|
16
16
|
require_relative "shojiku/local_pem"
|
|
17
|
+
require_relative "shojiku/external_signer"
|
|
17
18
|
require_relative "shojiku/lockdown"
|
|
18
19
|
require_relative "shojiku/sources"
|
|
19
20
|
require_relative "shojiku/template_root"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: shojiku
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- kengos
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-08-
|
|
11
|
+
date: 2026-08-08 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: fiddle
|
|
@@ -45,6 +45,7 @@ files:
|
|
|
45
45
|
- lib/shojiku/engine.rb
|
|
46
46
|
- lib/shojiku/env.rb
|
|
47
47
|
- lib/shojiku/errors.rb
|
|
48
|
+
- lib/shojiku/external_signer.rb
|
|
48
49
|
- lib/shojiku/failure.rb
|
|
49
50
|
- lib/shojiku/library.rb
|
|
50
51
|
- lib/shojiku/local_pem.rb
|
|
@@ -58,13 +59,13 @@ files:
|
|
|
58
59
|
- lib/shojiku/template_root.rb
|
|
59
60
|
- lib/shojiku/verification_report.rb
|
|
60
61
|
- lib/shojiku/version.rb
|
|
61
|
-
homepage: https://
|
|
62
|
+
homepage: https://shojiku.pages.dev
|
|
62
63
|
licenses:
|
|
63
64
|
- MIT
|
|
64
65
|
- Apache-2.0
|
|
65
66
|
- BSD-3-Clause
|
|
66
67
|
metadata:
|
|
67
|
-
homepage_uri: https://
|
|
68
|
+
homepage_uri: https://shojiku.pages.dev
|
|
68
69
|
source_code_uri: https://github.com/kengos/shojiku/tree/main/sdk/ruby
|
|
69
70
|
documentation_uri: https://github.com/kengos/shojiku/blob/main/docs/engine/README.md
|
|
70
71
|
rubygems_mfa_required: 'true'
|