hanko_xml 0.1.0-aarch64-linux → 0.2.0-aarch64-linux

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.
@@ -12,6 +12,10 @@ module HankoXml
12
12
  module Xades
13
13
  NS = "http://uri.etsi.org/01903/v1.3.2#"
14
14
 
15
+ # XAdES 1.4.1 (EN 319 132-1), the namespace of ArchiveTimeStamp and
16
+ # TimeStampValidationData (#169).
17
+ NS141 = "http://uri.etsi.org/01903/v1.4.1#"
18
+
15
19
  # Earlier XAdES namespaces (TS 101 903 v1.1.1, v1.2.2) that Xades::Verifier
16
20
  # also reads (#86). Xades::Signer writes only NS.
17
21
  LEGACY_NAMESPACES = %w[http://uri.etsi.org/01903/v1.2.2# http://uri.etsi.org/01903/v1.1.1#].freeze
@@ -70,8 +74,8 @@ module HankoXml
70
74
  end
71
75
 
72
76
  # The authenticated view of a verified <xades:SignedProperties>
73
- # (Xades::Verification#signed_properties). Unsigned properties
74
- # (timestamps, validation data) are never read.
77
+ # (Xades::Verification#signed_properties). Timestamps are
78
+ # Xades::Verification#signature_timestamps (#163).
75
79
  SignedProperties = Struct.new(:signing_time, :signature_policy, :claimed_roles, :data_object_formats,
76
80
  keyword_init: true) do
77
81
  def initialize(**)
@@ -88,10 +92,17 @@ module HankoXml
88
92
  # certificate:), and optionally SignaturePolicyIdentifier, SignerRoleV2
89
93
  # and DataObjectFormat.
90
94
  #
91
- # Always builds a new <Signature> (template: :build). Timestamps and
92
- # validation data (XAdES-T/LT/LTA) are separate: #81, #82.
95
+ # With timestamper:, a XAdES-T signature (#162): an RFC 3161 token over
96
+ # the canonicalized <ds:SignatureValue> in a <xades:SignatureTimeStamp>.
97
+ #
98
+ # With validation_data: as well, XAdES-LT (#167): CertificateValues and
99
+ # RevocationValues after the SignatureTimeStamp. Xades::Augmenter adds
100
+ # either to a signature that already exists.
101
+ #
102
+ # Always builds a new <Signature> (template: :build).
93
103
  class Signer < HankoXml::Signer
94
- attr_reader :signing_time, :signature_policy, :claimed_roles, :data_object_format
104
+ attr_reader :signing_time, :signature_policy, :claimed_roles, :data_object_format,
105
+ :timestamper, :timestamp_canonicalization_method, :validation_data
95
106
 
96
107
  # @param certificate [Key, OpenSSL::X509::Certificate, String, Pathname] Required: SigningCertificateV2
97
108
  # commits to it.
@@ -102,9 +113,18 @@ module HankoXml
102
113
  # of this Signer's own references, which get an Id when they have none.
103
114
  # @param signature_id [String, nil] The <Signature> Id; generated when nil, since QualifyingProperties
104
115
  # targets it.
116
+ # @param timestamper [#call, nil] Called with a DER RFC 3161 TimeStampReq, returns the TSA's DER
117
+ # TimeStampResp. The library never opens a connection: the callable owns the TSA exchange.
118
+ # @param timestamp_digest_method [Symbol, nil] The imprint digest; nil means digest_method:.
119
+ # @param timestamp_canonicalization_method [Symbol] How <ds:SignatureValue> is canonicalized for the
120
+ # imprint.
121
+ # @param validation_data [ValidationData, Hash, nil] Certificates, CRLs and OCSP responses to embed
122
+ # (XAdES-LT); needs timestamper:, since B-LT builds on B-T.
105
123
  # All other keywords are {HankoXml::Signer}'s.
106
124
  def initialize(certificate: nil, signing_time: nil, signature_policy: nil, claimed_roles: [],
107
- data_object_format: nil, signature_id: nil, template: :build, **options, &block)
125
+ data_object_format: nil, signature_id: nil, template: :build, timestamper: nil,
126
+ timestamp_digest_method: nil, timestamp_canonicalization_method: :exc_c14n, validation_data: nil,
127
+ **options, &block)
108
128
  unless template.to_sym == :build
109
129
  raise HankoXml::ConfigurationError,
110
130
  "template: #{template.inspect} is not supported for XAdES; it always builds a new <Signature>"
@@ -117,6 +137,14 @@ module HankoXml
117
137
  @signature_policy = coerce_signature_policy(signature_policy)
118
138
  @claimed_roles = Array(claimed_roles).map { |role| role.to_s.dup.freeze }.freeze
119
139
  @data_object_format = coerce_data_object_format(data_object_format)
140
+ SignatureTimeStamper.check_timestamper!(timestamper)
141
+ @timestamper = timestamper
142
+ @timestamp_digest_method = timestamp_digest_method
143
+ @timestamp_canonicalization_method = timestamp_canonicalization_method
144
+ @validation_data = ValidationData.coerce(validation_data)
145
+ if @validation_data && !timestamper
146
+ raise HankoXml::ConfigurationError, "validation_data: needs timestamper: (XAdES-LT builds on XAdES-T)"
147
+ end
120
148
 
121
149
  super(certificate: certificate, signature_id: signature_id || "xades-#{SecureRandom.hex(8)}",
122
150
  template: :build, **options, &block)
@@ -142,8 +170,37 @@ module HankoXml
142
170
  "#{signature_id}-signedprops"
143
171
  end
144
172
 
173
+ # The imprint digest: timestamp_digest_method:, or else digest_method:.
174
+ def timestamp_digest_method
175
+ @timestamp_digest_method || digest_method
176
+ end
177
+
178
+ def signature_timestamp_id
179
+ "#{signature_id}-sigts"
180
+ end
181
+
182
+ def to_h
183
+ super.merge(signing_time: signing_time, signature_policy: signature_policy, claimed_roles: claimed_roles,
184
+ data_object_format: data_object_format, timestamper: timestamper,
185
+ timestamp_digest_method: timestamp_digest_method,
186
+ timestamp_canonicalization_method: timestamp_canonicalization_method,
187
+ validation_data: validation_data)
188
+ end
189
+
145
190
  private
146
191
 
192
+ # xmlsec has signed; before the signed values reach the caller's
193
+ # document, obtain the timestamp over the new SignatureValue. A failure
194
+ # raises before anything is written back.
195
+ def write_back_signature(doc, signed_bytes, index, caller_sig_node)
196
+ token = timestamper && SignatureTimeStamper.new(self).token(signed_bytes, index)
197
+ super
198
+ return unless token
199
+
200
+ caller_sig_node.at_xpath(SignatureTimeStamper::ENCAPSULATED_XPATH, "ds" => HankoXml::NS["ds"], "xades" => NS)
201
+ .content = [token].pack("m0")
202
+ end
203
+
147
204
  def signed_properties_reference
148
205
  HankoXml::Reference.new(uri: "##{signed_properties_id}", type: SIGNED_PROPERTIES_TYPE,
149
206
  transforms: [:exc_c14n], digest_method: digest_method)
@@ -179,6 +236,10 @@ module HankoXml
179
236
  # Checked with the key material, after the block may have set certificate:.
180
237
  def check_key_material!
181
238
  super
239
+ if timestamper
240
+ SignatureTimeStamper.check_algorithms!(policy, digest: timestamp_digest_method,
241
+ canonicalization: timestamp_canonicalization_method)
242
+ end
182
243
  return if certificate.is_a?(HankoXml::Key) && certificate.certificate?
183
244
 
184
245
  raise HankoXml::ConfigurationError, "XAdES needs certificate:, which SigningCertificateV2 commits to"
@@ -207,6 +268,7 @@ module HankoXml
207
268
 
208
269
  build_signature_properties(xades(signed, "SignedSignatureProperties"))
209
270
  build_data_object_properties(signed) if @signer.data_object_format
271
+ build_unsigned_properties(qualifying) if @signer.timestamper
210
272
  object
211
273
  end
212
274
 
@@ -247,6 +309,26 @@ module HankoXml
247
309
  hash.add_child(element("DigestValue")).content = policy.digest_value
248
310
  end
249
311
 
312
+ # SignatureTimeStamp, then any validation data (#167).
313
+ def build_unsigned_properties(qualifying)
314
+ unsigned = xades(xades(qualifying, "UnsignedProperties"), "UnsignedSignatureProperties")
315
+ build_signature_timestamp(unsigned)
316
+ @signer.validation_data&.append_to(unsigned, @xades_ns)
317
+ end
318
+
319
+ # The SignatureTimeStamp skeleton: the CanonicalizationMethod is in the
320
+ # document before signing, so xmlsec reads it to canonicalize the
321
+ # SignatureValue (#161), as the verifier will. No Reference covers
322
+ # UnsignedProperties, so filling EncapsulatedTimeStamp afterwards
323
+ # changes nothing signed.
324
+ def build_signature_timestamp(unsigned)
325
+ timestamp = xades(unsigned, "SignatureTimeStamp")
326
+ timestamp["Id"] = @signer.signature_timestamp_id
327
+ method = timestamp.add_child(element("CanonicalizationMethod"))
328
+ method["Algorithm"] = HankoXml::Algorithms.uri_for(:canonicalization, @signer.timestamp_canonicalization_method)
329
+ xades(timestamp, "EncapsulatedTimeStamp")
330
+ end
331
+
250
332
  def build_data_object_properties(signed)
251
333
  properties = xades(signed, "SignedDataObjectProperties")
252
334
  format = @signer.data_object_format
@@ -284,4 +366,9 @@ module HankoXml
284
366
  end
285
367
  end
286
368
 
369
+ require_relative "xades/locator"
370
+ require_relative "xades/timestamp"
371
+ require_relative "xades/validation_data"
372
+ require_relative "xades/augmenter"
373
+ require_relative "xades/long_term"
287
374
  require_relative "xades/verifier"
@@ -8,5 +8,7 @@ module HankoXml
8
8
  def self.sign: (String xml_bytes, String key_pem, Array[String] cert_pems, Array[String] id_attributes, Integer index) -> String
9
9
  def self.scan: (String xml_bytes, Array[String] id_attributes, bool strict) -> Hash[Symbol, untyped]
10
10
  def self.validate_schema: (String xml_bytes, Integer index, String xsd_path) -> Array[String]
11
+ def self.reference_data: (String xml_bytes, Array[String] trusted_pems, Array[String] id_attributes, Integer index, Array[[String, String]] referenced_documents, bool allow_xpath_filter2) -> Array[String?]
12
+ def self.canonicalize: (String xml_bytes, Integer index, Array[[String, String, Integer?]] target_path, Array[[String, String, Integer?]] method_path) -> String
11
13
  end
12
14
  end
@@ -1,6 +1,7 @@
1
1
  module HankoXml
2
2
  module Xades
3
3
  NS: String
4
+ NS141: String
4
5
  LEGACY_NAMESPACES: Array[String]
5
6
  NAMESPACES: Array[String]
6
7
  SIGNED_PROPERTIES_TYPE: String
@@ -23,11 +24,19 @@ module HankoXml
23
24
  def initialize: (?mime_type: String?, ?description: String?, ?encoding: String?) -> void
24
25
  end
25
26
 
27
+ # Takes a DER TimeStampReq, returns the TSA's DER TimeStampResp (#162).
28
+ interface _Timestamper
29
+ def call: (String request_der) -> String
30
+ end
31
+
26
32
  class Signer < HankoXml::Signer
27
33
  attr_reader signing_time: Time?
28
34
  attr_reader signature_policy: (SignaturePolicy | :implied)?
29
35
  attr_reader claimed_roles: Array[String]
30
36
  attr_reader data_object_format: DataObjectFormat?
37
+ attr_reader timestamper: _Timestamper?
38
+ attr_reader timestamp_canonicalization_method: Symbol
39
+ attr_reader validation_data: ValidationData?
31
40
 
32
41
  def initialize: (
33
42
  ?certificate: (Key | OpenSSL::X509::Certificate | String | _ToPath)?,
@@ -37,10 +46,52 @@ module HankoXml
37
46
  ?data_object_format: (DataObjectFormat | Hash[Symbol, untyped])?,
38
47
  ?signature_id: String?,
39
48
  ?template: Symbol,
49
+ ?timestamper: _Timestamper?,
50
+ ?timestamp_digest_method: Symbol?,
51
+ ?timestamp_canonicalization_method: Symbol,
52
+ ?validation_data: (ValidationData | Hash[Symbol, untyped])?,
40
53
  **untyped
41
54
  ) ?{ (self) -> void } -> void
42
55
 
43
56
  def signed_properties_id: () -> String
57
+ def signature_timestamp_id: () -> String
58
+ def timestamp_digest_method: () -> Symbol
59
+ end
60
+
61
+ # XAdES-LT validation material (#167). Embedded, never trusted.
62
+ class ValidationData
63
+ attr_reader certificates: Array[OpenSSL::X509::Certificate]
64
+ attr_reader crls: Array[OpenSSL::X509::CRL]
65
+ attr_reader ocsp_responses: Array[OpenSSL::OCSP::Response]
66
+
67
+ def initialize: (
68
+ ?certificates: Array[OpenSSL::X509::Certificate | String | Key],
69
+ ?crls: Array[OpenSSL::X509::CRL | String],
70
+ ?ocsp_responses: Array[OpenSSL::OCSP::Response | String]
71
+ ) -> void
72
+ def self.coerce: ((ValidationData | Hash[Symbol, untyped])? value) -> ValidationData?
73
+ end
74
+
75
+ # Adds a SignatureTimeStamp and XAdES-LT validation data to an existing signature (#167).
76
+ class Augmenter
77
+ attr_reader timestamper: _Timestamper?
78
+ attr_reader timestamp_digest_method: Symbol
79
+ attr_reader timestamp_canonicalization_method: Symbol
80
+ attr_reader validation_data: ValidationData?
81
+ attr_reader archive_timestamp: bool
82
+ attr_reader archive_canonicalization_method: Symbol
83
+ attr_reader policy: Policy
84
+
85
+ def initialize: (
86
+ ?timestamper: _Timestamper?,
87
+ ?timestamp_digest_method: Symbol,
88
+ ?timestamp_canonicalization_method: Symbol,
89
+ ?validation_data: (ValidationData | Hash[Symbol, untyped])?,
90
+ ?archive_timestamp: bool,
91
+ ?archive_canonicalization_method: Symbol,
92
+ ?policy: Policy
93
+ ) -> void
94
+ def augment: (untyped source, ?signature: Integer?) -> SignedDocument
44
95
  end
45
96
  end
46
97
  end
@@ -57,12 +108,51 @@ module HankoXml
57
108
  attr_reader data_object_formats: Array[Hash[Symbol, String?]]
58
109
  end
59
110
 
111
+ class SignatureTimeStamp
112
+ attr_reader id: String?
113
+ attr_reader time: Time
114
+ attr_reader serial_number: Integer
115
+ attr_reader tsa_certificate: OpenSSL::X509::Certificate?
116
+ attr_reader digest_method: String
117
+ attr_reader policy_id: String?
118
+ end
119
+
120
+ # The revocation evidence a chain certificate passed with (#168).
121
+ class RevocationStatus
122
+ attr_reader certificate: OpenSSL::X509::Certificate
123
+ attr_reader source: :ocsp | :crl
124
+ attr_reader issued_at: Time
125
+ attr_reader status: :good | :revoked_later
126
+ end
127
+
128
+ # A passed XAdES-LT validation (#168).
129
+ class LongTermValidation
130
+ attr_reader time: Time
131
+ attr_reader certificate_chain: Array[OpenSSL::X509::Certificate]
132
+ attr_reader revocation: Array[RevocationStatus]
133
+ end
134
+
60
135
  class Verification < HankoXml::Verification
61
136
  attr_reader signed_properties: SignedProperties?
137
+ attr_reader signature_timestamps: Array[SignatureTimeStamp]?
138
+ attr_reader long_term_validation: LongTermValidation?
139
+ attr_reader archive_timestamps: Array[SignatureTimeStamp]?
140
+ def timestamp_time: () -> Time?
62
141
  end
63
142
 
64
143
  class Verifier < HankoXml::Verifier
65
- def initialize: (?signature_policy: (SignaturePolicy | Hash[Symbol, untyped] | :implied)?, **untyped) -> void
144
+ TIMESTAMP_MODES: Array[Symbol]
145
+ LONG_TERM_MODES: Array[Symbol]
146
+
147
+ def initialize: (
148
+ ?signature_policy: (SignaturePolicy | Hash[Symbol, untyped] | :implied)?,
149
+ ?timestamps: :verify | :require | :ignore,
150
+ ?tsa_store: OpenSSL::X509::Store?,
151
+ ?tsa_certificates: Array[OpenSSL::X509::Certificate | String | Key]?,
152
+ ?long_term: (:verify | :require | :ignore)?,
153
+ ?revocation_grace: Numeric,
154
+ **untyped
155
+ ) -> void
66
156
  def verify: (untyped xml) -> Verification
67
157
  def verify_all: (untyped xml) -> Array[Verification]
68
158
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hanko_xml
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: aarch64-linux
6
6
  authors:
7
7
  - Danimar Ribeiro
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-10-15 00:00:00.000000000 Z
11
+ date: 2026-10-19 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: hanko_xml signs and verifies XML-DSig signatures by binding to libxmlsec1
14
14
  through a Rust extension built with Magnus and rb-sys. It works with Nokogiri documents
@@ -50,6 +50,11 @@ files:
50
50
  - lib/hanko_xml/verifier.rb
51
51
  - lib/hanko_xml/version.rb
52
52
  - lib/hanko_xml/xades.rb
53
+ - lib/hanko_xml/xades/augmenter.rb
54
+ - lib/hanko_xml/xades/locator.rb
55
+ - lib/hanko_xml/xades/long_term.rb
56
+ - lib/hanko_xml/xades/timestamp.rb
57
+ - lib/hanko_xml/xades/validation_data.rb
53
58
  - lib/hanko_xml/xades/verifier.rb
54
59
  - lib/hanko_xml/xml.rb
55
60
  - lib/hanko_xml/xmldsig-core-schema.xsd