Class: Ksef::Auth::Signer
- Inherits:
-
Object
- Object
- Ksef::Auth::Signer
- Includes:
- Xades
- Defined in:
- lib/ksef/auth/signer.rb
Overview
Produces the XAdES-BES enveloped signature that POST /auth/xades-signature
requires (docs/REFERENCE.md §4.3).
The algorithms come from Xades and the XML from SignatureTemplate; what lives here is the part that has to be exactly right — what gets canonicalised, in what order, and over which bytes.
Why this signs a String and returns a String
A digest over "the document" has to match what the verifier computes after parsing
the bytes we send. Nokogiri's to_xml pretty-prints on output without adding text
nodes to the tree, so an in-memory tree and its serialised form can canonicalise
differently — the classic XML-DSig footgun. Signing the serialised bytes and emitting
them without reformatting removes the discrepancy entirely. ksef-client-csharp deals
with the same problem by setting PreserveWhitespace = true.
Constant Summary collapse
- SAVE_OPTIONS =
Emit exactly the bytes that were signed:
AS_XMLalone excludesFORMAT, so nothing is re-indented. Passing the default would silently invalidate every signature this produces. Nokogiri::XML::Node::SaveOptions::AS_XML
- CLOCK_SKEW_SECONDS =
SigningTimeis backdated by a minute, lifted fromksef-client-csharp'sCertificateTimeBuffer = TimeSpan.FromMinutes(-1). It is unexplained there but is plainly a clock-skew guard: a signing time fractionally in the future relative to the server's clock invites rejection, and being a minute early costs nothing. 60
Constants included from Xades
Xades::C14N_MODE, Xades::CANONICALIZATION, Xades::DIGEST_METHOD, Xades::DS, Xades::ENVELOPED_SIGNATURE, Xades::SIGNATURE_ID, Xades::SIGNATURE_METHOD, Xades::SIGNED_PROPERTIES_ID, Xades::SIGNED_PROPERTIES_TYPE, Xades::XADES, Xades::XPATH_NAMESPACES
Instance Attribute Summary collapse
-
#certificate ⇒ Object
readonly
Returns the value of attribute certificate.
Instance Method Summary collapse
-
#initialize(certificate:, key:, signing_time: nil, clock_skew: CLOCK_SKEW_SECONDS) ⇒ Signer
constructor
A new instance of Signer.
-
#sign(input, validate: true) ⇒ String
The document with a
ds:Signatureappended to its root.
Constructor Details
#initialize(certificate:, key:, signing_time: nil, clock_skew: CLOCK_SKEW_SECONDS) ⇒ Signer
Returns a new instance of Signer.
44 45 46 47 48 49 50 |
# File 'lib/ksef/auth/signer.rb', line 44 def initialize(certificate:, key:, signing_time: nil, clock_skew: CLOCK_SKEW_SECONDS) @certificate = certificate @key = key @signing_time = signing_time @clock_skew = clock_skew verify_key_matches_certificate end |
Instance Attribute Details
#certificate ⇒ Object (readonly)
Returns the value of attribute certificate.
37 38 39 |
# File 'lib/ksef/auth/signer.rb', line 37 def certificate @certificate end |
Instance Method Details
#sign(input, validate: true) ⇒ String
Returns the document with a ds:Signature appended to its root.
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/ksef/auth/signer.rb', line 60 def sign(input, validate: true) xml = input.respond_to?(:to_xml) ? input.to_xml : input.to_s document = Nokogiri::XML(xml) raise ValidationError, "Cannot sign: the document has no root element" if document.root.nil? # `TokenRequest` knows whether its own context type can be meaningfully validated # (§14.4: two of the four have defective upstream patterns), and carries the advisory # explaining a failure that is not the caller's fault. Delegating to it means a # `NipVatUe` or `PeppolId` request fails with the reason rather than a bare schema # error — and can still be signed with `validate: false`, which is the resolution # §14.4 arrived at. validate!(input, xml) if validate # Computed before the signature exists, which is exactly what the # enveloped-signature transform reproduces for the verifier. document.root.add_child(signature_for(digest(document.canonicalize(C14N_MODE)))) seal(document) document.to_xml(save_with: SAVE_OPTIONS) end |