moxml 0.1.25 → 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.
Files changed (181) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/release.yml +9 -0
  3. data/.github/workflows/windows.yml +62 -0
  4. data/Gemfile +3 -1
  5. data/README.adoc +133 -6
  6. data/docs/signature/algorithms.md +108 -0
  7. data/docs/signature/architecture.md +148 -0
  8. data/docs/signature/c14n.md +110 -0
  9. data/docs/signature/examples.md +109 -0
  10. data/docs/signature/flows.md +132 -0
  11. data/docs/signature/quick-reference.md +115 -0
  12. data/docs/signature/security.md +143 -0
  13. data/examples/signature/auto_key_extraction.rb +24 -0
  14. data/examples/signature/enveloped_rsa.rb +45 -0
  15. data/examples/signature/hmac.rb +33 -0
  16. data/lib/compat/opal/moxml_boot.rb +5 -0
  17. data/lib/moxml/adapter/base.rb +53 -49
  18. data/lib/moxml/adapter/customized_leptris/declaration.rb +32 -0
  19. data/lib/moxml/adapter/customized_leptris/doctype.rb +31 -0
  20. data/lib/moxml/adapter/customized_leptris/entity_reference.rb +12 -0
  21. data/lib/moxml/adapter/customized_leptris/text_segment.rb +26 -0
  22. data/lib/moxml/adapter/customized_leptris.rb +16 -0
  23. data/lib/moxml/adapter/customized_libxml/cdata.rb +3 -12
  24. data/lib/moxml/adapter/customized_libxml/comment.rb +2 -9
  25. data/lib/moxml/adapter/customized_libxml/declaration.rb +1 -10
  26. data/lib/moxml/adapter/customized_libxml/node.rb +8 -0
  27. data/lib/moxml/adapter/customized_libxml/processing_instruction.rb +2 -10
  28. data/lib/moxml/adapter/customized_libxml/text.rb +2 -9
  29. data/lib/moxml/adapter/customized_oga/entity_decoder.rb +37 -0
  30. data/lib/moxml/adapter/customized_oga/raw_value_override.rb +52 -0
  31. data/lib/moxml/adapter/customized_oga/xml_generator.rb +4 -34
  32. data/lib/moxml/adapter/customized_oga.rb +2 -0
  33. data/lib/moxml/adapter/customized_ox/entity_reference.rb +1 -17
  34. data/lib/moxml/adapter/customized_rexml/entity_reference.rb +1 -11
  35. data/lib/moxml/adapter/headed_ox.rb +9 -128
  36. data/lib/moxml/adapter/leptris.rb +965 -0
  37. data/lib/moxml/adapter/libxml.rb +131 -99
  38. data/lib/moxml/adapter/nokogiri.rb +29 -8
  39. data/lib/moxml/adapter/oga.rb +91 -54
  40. data/lib/moxml/adapter/ox.rb +93 -137
  41. data/lib/moxml/adapter/rexml.rb +61 -50
  42. data/lib/moxml/adapter.rb +2 -1
  43. data/lib/moxml/attribute.rb +8 -4
  44. data/lib/moxml/attribute_resolver.rb +189 -0
  45. data/lib/moxml/c14n/attribute_handler.rb +71 -0
  46. data/lib/moxml/c14n/character_encoder.rb +29 -0
  47. data/lib/moxml/c14n/data_model.rb +145 -0
  48. data/lib/moxml/c14n/exclusive.rb +188 -0
  49. data/lib/moxml/c14n/inclusive_10.rb +42 -0
  50. data/lib/moxml/c14n/inclusive_11.rb +21 -0
  51. data/lib/moxml/c14n/namespace_context.rb +68 -0
  52. data/lib/moxml/c14n/namespace_handler.rb +71 -0
  53. data/lib/moxml/c14n/node.rb +42 -0
  54. data/lib/moxml/c14n/nodes/attribute_node.rb +50 -0
  55. data/lib/moxml/c14n/nodes/comment_node.rb +29 -0
  56. data/lib/moxml/c14n/nodes/element_node.rb +62 -0
  57. data/lib/moxml/c14n/nodes/namespace_node.rb +41 -0
  58. data/lib/moxml/c14n/nodes/processing_instruction_node.rb +30 -0
  59. data/lib/moxml/c14n/nodes/root_node.rb +22 -0
  60. data/lib/moxml/c14n/nodes/text_node.rb +29 -0
  61. data/lib/moxml/c14n/nodes.rb +17 -0
  62. data/lib/moxml/c14n/processor.rb +118 -0
  63. data/lib/moxml/c14n/writer.rb +121 -0
  64. data/lib/moxml/c14n/xml_base_handler.rb +140 -0
  65. data/lib/moxml/c14n.rb +96 -0
  66. data/lib/moxml/config.rb +29 -3
  67. data/lib/moxml/context.rb +12 -0
  68. data/lib/moxml/element.rb +29 -12
  69. data/lib/moxml/entity/reference.rb +28 -0
  70. data/lib/moxml/entity.rb +125 -0
  71. data/lib/moxml/node.rb +90 -1
  72. data/lib/moxml/sax/namespace_splitter.rb +5 -2
  73. data/lib/moxml/signature/algorithms/base64_transform.rb +40 -0
  74. data/lib/moxml/signature/algorithms/canonicalization_base.rb +87 -0
  75. data/lib/moxml/signature/algorithms/digest_base.rb +51 -0
  76. data/lib/moxml/signature/algorithms/dsa_sha.rb +107 -0
  77. data/lib/moxml/signature/algorithms/ecdsa_sha.rb +125 -0
  78. data/lib/moxml/signature/algorithms/enveloped_signature_transform.rb +79 -0
  79. data/lib/moxml/signature/algorithms/exc_c14n_10.rb +23 -0
  80. data/lib/moxml/signature/algorithms/hmac_sha.rb +117 -0
  81. data/lib/moxml/signature/algorithms/inclusive_c14n_10.rb +21 -0
  82. data/lib/moxml/signature/algorithms/inclusive_c14n_11.rb +23 -0
  83. data/lib/moxml/signature/algorithms/rsa_pkcs1_sha.rb +56 -0
  84. data/lib/moxml/signature/algorithms/sha1.rb +13 -0
  85. data/lib/moxml/signature/algorithms/sha224.rb +13 -0
  86. data/lib/moxml/signature/algorithms/sha256.rb +13 -0
  87. data/lib/moxml/signature/algorithms/sha384.rb +13 -0
  88. data/lib/moxml/signature/algorithms/sha512.rb +13 -0
  89. data/lib/moxml/signature/algorithms/signature_method_base.rb +58 -0
  90. data/lib/moxml/signature/algorithms/transform_base.rb +40 -0
  91. data/lib/moxml/signature/algorithms.rb +113 -0
  92. data/lib/moxml/signature/errors.rb +99 -0
  93. data/lib/moxml/signature/key_extractor.rb +172 -0
  94. data/lib/moxml/signature/model/algorithm_method.rb +18 -0
  95. data/lib/moxml/signature/model/digest_method.rb +15 -0
  96. data/lib/moxml/signature/model/key/dsa_key_value.rb +28 -0
  97. data/lib/moxml/signature/model/key/ec_key_value.rb +22 -0
  98. data/lib/moxml/signature/model/key/rsa_key_value.rb +19 -0
  99. data/lib/moxml/signature/model/key/x509_data.rb +32 -0
  100. data/lib/moxml/signature/model/key/x509_digest.rb +19 -0
  101. data/lib/moxml/signature/model/key/x509_issuer_serial.rb +19 -0
  102. data/lib/moxml/signature/model/key_info.rb +21 -0
  103. data/lib/moxml/signature/model/key_value.rb +20 -0
  104. data/lib/moxml/signature/model/object_element.rb +18 -0
  105. data/lib/moxml/signature/model/reference.rb +22 -0
  106. data/lib/moxml/signature/model/signature.rb +21 -0
  107. data/lib/moxml/signature/model/signature_value.rb +16 -0
  108. data/lib/moxml/signature/model/signed_info.rb +20 -0
  109. data/lib/moxml/signature/model/transform.rb +16 -0
  110. data/lib/moxml/signature/model/transforms.rb +36 -0
  111. data/lib/moxml/signature/model.rb +35 -0
  112. data/lib/moxml/signature/parser.rb +270 -0
  113. data/lib/moxml/signature/reference_resolver.rb +79 -0
  114. data/lib/moxml/signature/reference_result.rb +22 -0
  115. data/lib/moxml/signature/serializer.rb +163 -0
  116. data/lib/moxml/signature/signer.rb +73 -0
  117. data/lib/moxml/signature/single_verification_result.rb +31 -0
  118. data/lib/moxml/signature/transform_pipeline.rb +106 -0
  119. data/lib/moxml/signature/verification_result.rb +27 -0
  120. data/lib/moxml/signature/verifier.rb +129 -0
  121. data/lib/moxml/signature.rb +94 -0
  122. data/lib/moxml/version.rb +1 -1
  123. data/lib/moxml/xml_emitter.rb +71 -0
  124. data/lib/moxml/xpath/compiler.rb +60 -7
  125. data/lib/moxml/xpath/parser.rb +22 -15
  126. data/lib/moxml.rb +5 -0
  127. data/reference-docs/w3c-xmldsig-bestpractices.md +216 -0
  128. data/reference-docs/w3c-xmldsig-core.md +400 -0
  129. data/spec/examples/readme_examples_spec.rb +0 -4
  130. data/spec/examples/xpath_examples_spec.rb +0 -11
  131. data/spec/fixtures/xmldsig/keys/rsa_private.pem +28 -0
  132. data/spec/fixtures/xmldsig/keys/rsa_public.pem +9 -0
  133. data/spec/fixtures/xmldsig/keys/rsa_ref.pem +15 -0
  134. data/spec/fixtures/xmldsig/keys/rsa_ref.pub +6 -0
  135. data/spec/fixtures/xmldsig/sign2-doc.xml +6 -0
  136. data/spec/fixtures/xmldsig/sign2-result.xml +25 -0
  137. data/spec/fixtures/xmldsig/sign3-result.xml +39 -0
  138. data/spec/integration/all_adapters_spec.rb +17 -0
  139. data/spec/integration/sax_parity_spec.rb +58 -0
  140. data/spec/integration/shared_examples/edge_cases.rb +0 -10
  141. data/spec/integration/shared_examples/integration_workflows.rb +0 -10
  142. data/spec/integration/shared_examples/node_wrappers/attribute_behavior.rb +98 -0
  143. data/spec/integration/shared_examples/node_wrappers/namespace_behavior.rb +26 -0
  144. data/spec/integration/shared_examples/node_wrappers/node_behavior.rb +0 -8
  145. data/spec/moxml/adapter/leptris_spec.rb +75 -0
  146. data/spec/moxml/adapter/ox_spec.rb +20 -6
  147. data/spec/moxml/adapter/platform_spec.rb +15 -2
  148. data/spec/moxml/adapter/shared_examples/adapter_contract.rb +180 -0
  149. data/spec/moxml/attribute_resolver_spec.rb +108 -0
  150. data/spec/moxml/c14n/api_spec.rb +117 -0
  151. data/spec/moxml/c14n/c14n_spec.rb +88 -0
  152. data/spec/moxml/c14n/comments_pis_spec.rb +65 -0
  153. data/spec/moxml/c14n/inclusive10_spec.rb +60 -0
  154. data/spec/moxml/c14n/namespace_edge_cases_spec.rb +88 -0
  155. data/spec/moxml/c14n/xml_attributes_spec.rb +54 -0
  156. data/spec/moxml/doctype_spec.rb +1 -1
  157. data/spec/moxml/entity_spec.rb +76 -0
  158. data/spec/moxml/node_spec.rb +104 -0
  159. data/spec/moxml/sax_entity_parity_spec.rb +358 -0
  160. data/spec/moxml/signature/algorithms/base64_transform_spec.rb +26 -0
  161. data/spec/moxml/signature/algorithms/digest_base_spec.rb +72 -0
  162. data/spec/moxml/signature/algorithms/dsa_sha_spec.rb +46 -0
  163. data/spec/moxml/signature/algorithms/ecdsa_sha_spec.rb +72 -0
  164. data/spec/moxml/signature/algorithms/enveloped_signature_transform_spec.rb +40 -0
  165. data/spec/moxml/signature/algorithms/hmac_sha_spec.rb +71 -0
  166. data/spec/moxml/signature/algorithms/rsa_pkcs1_sha_spec.rb +55 -0
  167. data/spec/moxml/signature/algorithms_spec.rb +76 -0
  168. data/spec/moxml/signature/cross_verify_spec.rb +64 -0
  169. data/spec/moxml/signature/edge_cases_spec.rb +284 -0
  170. data/spec/moxml/signature/fixtures_spec.rb +80 -0
  171. data/spec/moxml/signature/key_extractor_spec.rb +96 -0
  172. data/spec/moxml/signature/model_spec.rb +54 -0
  173. data/spec/moxml/signature/round_trip_spec.rb +113 -0
  174. data/spec/moxml/xml_emitter_spec.rb +64 -0
  175. data/spec/moxml/xpath/axes_spec.rb +72 -0
  176. data/spec/moxml/xpath/parser_spec.rb +6 -0
  177. data/spec/moxml/xpath_capabilities_spec.rb +5 -3
  178. data/spec/performance/benchmark_spec.rb +13 -6
  179. data/spec/performance/thread_safety_spec.rb +0 -4
  180. data/spec/spec_helper.rb +5 -1
  181. metadata +130 -2
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moxml
4
+ module Signature
5
+ module Algorithms
6
+ # Enveloped Signature Transform (W3C §6.6.4).
7
+ #
8
+ # Removes the containing ds:Signature element from the node-set so the
9
+ # signature does not include itself in its own digest calculation.
10
+ #
11
+ # Semantics during signing: `signature_element` is nil (the Signature
12
+ # has not been attached to the document yet), so the transform is a
13
+ # no-op — the document contains no Signature to exclude.
14
+ #
15
+ # Semantics during verification: `signature_element` is the Signature
16
+ # element being verified. The transform detaches it (and its
17
+ # descendants) from a deep copy of the input so the canonicalizer
18
+ # walks a tree without the Signature.
19
+ class EnvelopedSignatureTransform < TransformBase
20
+ identifier "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
21
+
22
+ def self.input_type; :nodeset; end
23
+ def self.output_type; :nodeset; end
24
+
25
+ def transform(input)
26
+ return input if @signature_element.nil?
27
+
28
+ # If the signature is not within the input subtree, no-op.
29
+ return input unless signature_within?(input)
30
+
31
+ working_copy = deep_copy(input)
32
+ remove_signature_within(working_copy)
33
+ working_copy
34
+ end
35
+
36
+ private
37
+
38
+ def signature_within?(input)
39
+ !find_signature_element(input).nil?
40
+ end
41
+
42
+ def remove_signature_within(node)
43
+ sig = find_signature_element(node)
44
+ sig&.remove
45
+ end
46
+
47
+ def find_signature_element(node)
48
+ return nil unless node
49
+ return node if signature_element?(node)
50
+
51
+ children = node.children if node.is_a?(::Moxml::Node)
52
+ children&.each do |child|
53
+ found = find_signature_element(child)
54
+ return found if found
55
+ end
56
+ nil
57
+ end
58
+
59
+ def signature_element?(node)
60
+ node.is_a?(::Moxml::Element) &&
61
+ node.name == "Signature" &&
62
+ node.namespace_uri == DSIG_NS
63
+ end
64
+
65
+ def deep_copy(input)
66
+ case input
67
+ when ::Moxml::Document
68
+ @context.parse(input.root.to_xml(indent: 0))
69
+ when ::Moxml::Element
70
+ wrapper = @context.parse("<__wrap__>#{input.to_xml(indent: 0)}</__wrap__>")
71
+ wrapper.root.children.first
72
+ else
73
+ input
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moxml
4
+ module Signature
5
+ module Algorithms
6
+ # Exclusive XML Canonicalization 1.0 (https://www.w3.org/TR/xml-exc-c14n/)
7
+ #
8
+ # Two registered URIs: one omits comments, one includes comments.
9
+ # The with_comments variant is detected from the URI suffix at
10
+ # instantiation time (see CanonicalizationBase#uri_has_comments?).
11
+ class ExcC14n10 < CanonicalizationBase
12
+ identifier "http://www.w3.org/2001/10/xml-exc-c14n#"
13
+ identifier "http://www.w3.org/2001/10/xml-exc-c14n#WithComments"
14
+
15
+ private
16
+
17
+ def engine
18
+ ::Moxml::C14n::Exclusive.new
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+
5
+ module Moxml
6
+ module Signature
7
+ module Algorithms
8
+ # HMAC per RFC 2104, bound to a digest.
9
+ #
10
+ # Per W3C XML Signature §4.4.2 and §6.3.1, if `HMACOutputLength` is
11
+ # specified, the output is truncated to that many bits and the
12
+ # truncation length MUST be at least max(hash_bits / 2, 80) bits.
13
+ class HmacSha < SignatureMethodBase
14
+ PAIRINGS = {
15
+ "http://www.w3.org/2000/09/xmldsig#hmac-sha1" => "SHA1",
16
+ "http://www.w3.org/2001/04/xmldsig-more#hmac-sha224" => "SHA224",
17
+ "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256" => "SHA256",
18
+ "http://www.w3.org/2001/04/xmldsig-more#hmac-sha384" => "SHA384",
19
+ "http://www.w3.org/2001/04/xmldsig-more#hmac-sha512" => "SHA512",
20
+ }.freeze
21
+
22
+ HASH_BITS = {
23
+ "SHA1" => 160,
24
+ "SHA224" => 224,
25
+ "SHA256" => 256,
26
+ "SHA384" => 384,
27
+ "SHA512" => 512,
28
+ }.freeze
29
+
30
+ MIN_TRUNCATION_BITS = 80
31
+
32
+ PAIRINGS.each_key { |uri| identifier uri }
33
+
34
+ # parameters: optional { hmac_output_length: Integer } (bits, multiple of 8)
35
+ def initialize(identifier_uri:, parameters: nil)
36
+ super(parameters)
37
+ @identifier_uri = identifier_uri
38
+ @truncation_bits = parameters&.dig(:hmac_output_length)
39
+ validate_truncation! if @truncation_bits
40
+ end
41
+
42
+ def compute_signature(data, key)
43
+ secret = coerce_key(key)
44
+ full = OpenSSL::HMAC.digest(digest_name, secret, data)
45
+ truncate(full)
46
+ end
47
+
48
+ def verify_signature(data, key, signature)
49
+ expected = compute_signature(data, key)
50
+ fixed_comparison(expected, signature)
51
+ end
52
+
53
+ private
54
+
55
+ def digest_name
56
+ PAIRINGS.fetch(@identifier_uri)
57
+ end
58
+
59
+ def hash_bits
60
+ HASH_BITS.fetch(digest_name)
61
+ end
62
+
63
+ def truncate(full_mac)
64
+ return full_mac unless @truncation_bits
65
+
66
+ unless (@truncation_bits % 8).zero?
67
+ raise SignatureError,
68
+ "HMACOutputLength (#{@truncation_bits}) must be a " \
69
+ "multiple of 8"
70
+ end
71
+
72
+ min = [hash_bits / 2, MIN_TRUNCATION_BITS].max
73
+ if @truncation_bits < min
74
+ raise SignatureError,
75
+ "HMACOutputLength (#{@truncation_bits}) below minimum " \
76
+ "of #{min} for #{digest_name}"
77
+ end
78
+
79
+ full_mac.byteslice(0, @truncation_bits / 8)
80
+ end
81
+
82
+ def validate_truncation!
83
+ unless (@truncation_bits % 8).zero?
84
+ raise SignatureError,
85
+ "HMACOutputLength (#{@truncation_bits}) must be a " \
86
+ "multiple of 8"
87
+ end
88
+
89
+ min = [hash_bits / 2, MIN_TRUNCATION_BITS].max
90
+ return if @truncation_bits >= min
91
+
92
+ raise SignatureError,
93
+ "HMACOutputLength (#{@truncation_bits}) below minimum " \
94
+ "of #{min} for #{digest_name}"
95
+ end
96
+
97
+ def coerce_key(key)
98
+ case key
99
+ when String then key
100
+ else
101
+ raise SignatureKeyError,
102
+ "HMAC signature method requires a String secret, " \
103
+ "got #{key.class}"
104
+ end
105
+ end
106
+
107
+ def fixed_comparison(expected, actual)
108
+ return false unless expected.bytesize == actual.bytesize
109
+
110
+ OpenSSL.fixed_length_secure_compare(expected, actual)
111
+ rescue ArgumentError
112
+ false
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moxml
4
+ module Signature
5
+ module Algorithms
6
+ # Canonical XML 1.0 (https://www.w3.org/TR/xml-c14n/)
7
+ #
8
+ # Implemented via the canon-derived C14n::Inclusive10 engine.
9
+ class InclusiveC14n10 < CanonicalizationBase
10
+ identifier "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"
11
+ identifier "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
12
+
13
+ private
14
+
15
+ def engine
16
+ ::Moxml::C14n::Inclusive10.new
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moxml
4
+ module Signature
5
+ module Algorithms
6
+ # Canonical XML 1.1 (https://www.w3.org/TR/xml-c14n11/)
7
+ #
8
+ # Currently delegates to Inclusive 1.0. Full 1.1 differences
9
+ # (XML 1.1 line-ending handling, notations, DTD entity refs)
10
+ # are documented in TODO.complete/05-c14n-engine.md.
11
+ class InclusiveC14n11 < CanonicalizationBase
12
+ identifier "http://www.w3.org/2006/12/xml-c14n11"
13
+ identifier "http://www.w3.org/2006/12/xml-c14n11#WithComments"
14
+
15
+ private
16
+
17
+ def engine
18
+ ::Moxml::C14n::Inclusive10.new
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+
5
+ module Moxml
6
+ module Signature
7
+ module Algorithms
8
+ # RSASSA-PKCS1-v1_5 per RFC 3447 §8.2, bound to a digest.
9
+ #
10
+ # One Ruby class registered under five URIs (sha1 / sha224 / sha256 /
11
+ # sha384 / sha512). When instantiated, the class is told which URI was
12
+ # resolved so it can pick the right digest.
13
+ class RsaPkcs1Sha < SignatureMethodBase
14
+ PAIRINGS = {
15
+ "http://www.w3.org/2000/09/xmldsig#rsa-sha1" => "SHA1",
16
+ "http://www.w3.org/2001/04/xmldsig-more#rsa-sha224" => "SHA224",
17
+ "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" => "SHA256",
18
+ "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384" => "SHA384",
19
+ "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512" => "SHA512",
20
+ }.freeze
21
+
22
+ PAIRINGS.each_key { |uri| identifier uri }
23
+
24
+ def initialize(identifier_uri:, parameters: nil)
25
+ super(nil)
26
+ @identifier_uri = identifier_uri
27
+ @parameters = parameters
28
+ end
29
+
30
+ def compute_signature(data, key)
31
+ rsa_key = coerce_key(key)
32
+ rsa_key.sign(digest_name, data)
33
+ end
34
+
35
+ def verify_signature(data, key, signature)
36
+ rsa_key = coerce_key(key)
37
+ rsa_key.verify(digest_name, signature, data)
38
+ end
39
+
40
+ private
41
+
42
+ def digest_name
43
+ PAIRINGS.fetch(@identifier_uri)
44
+ end
45
+
46
+ def coerce_key(key)
47
+ return key if key.is_a?(OpenSSL::PKey::RSA)
48
+
49
+ raise SignatureKeyError,
50
+ "RSA signature method requires OpenSSL::PKey::RSA, " \
51
+ "got #{key.class}"
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moxml
4
+ module Signature
5
+ module Algorithms
6
+ class SHA1 < DigestBase
7
+ identifier "http://www.w3.org/2000/09/xmldsig#sha1"
8
+ digest_bits 160
9
+ openssl_digest_name "SHA1"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moxml
4
+ module Signature
5
+ module Algorithms
6
+ class SHA224 < DigestBase
7
+ identifier "http://www.w3.org/2001/04/xmldsig-more#sha224"
8
+ digest_bits 224
9
+ openssl_digest_name "SHA224"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moxml
4
+ module Signature
5
+ module Algorithms
6
+ class SHA256 < DigestBase
7
+ identifier "http://www.w3.org/2001/04/xmlenc#sha256"
8
+ digest_bits 256
9
+ openssl_digest_name "SHA256"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moxml
4
+ module Signature
5
+ module Algorithms
6
+ class SHA384 < DigestBase
7
+ identifier "http://www.w3.org/2001/04/xmldsig-more#sha384"
8
+ digest_bits 384
9
+ openssl_digest_name "SHA384"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moxml
4
+ module Signature
5
+ module Algorithms
6
+ class SHA512 < DigestBase
7
+ identifier "http://www.w3.org/2001/04/xmlenc#sha512"
8
+ digest_bits 512
9
+ openssl_digest_name "SHA512"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moxml
4
+ module Signature
5
+ module Algorithms
6
+ # Base class for signature methods and MACs.
7
+ #
8
+ # Subclasses declare an identifier URI per W3C spec §6.4 / §6.3 and
9
+ # implement #compute_signature and #verify_signature.
10
+ #
11
+ # `key` is whatever OpenSSL expects (OpenSSL::PKey::RSA for RSA, a
12
+ # String secret for HMAC, etc.).
13
+ class SignatureMethodBase
14
+ class << self
15
+ attr_reader :identifier_uri, :digest_uri
16
+
17
+ def identifier(uri, digest_uri: nil)
18
+ @identifier_uri = uri
19
+ @digest_uri = digest_uri
20
+ Algorithms.register(:signature_method, uri, self)
21
+ end
22
+ end
23
+
24
+ # Optional constructor for parameterized methods (e.g. HMACOutputLength).
25
+ def initialize(parameters = nil)
26
+ @parameters = parameters
27
+ end
28
+
29
+ def sign(data, key)
30
+ compute_signature(data, key)
31
+ rescue OpenSSL::PKey::PKeyError => e
32
+ raise SigningError.new(
33
+ "signing failed: #{e.class}",
34
+ algorithm: self.class.identifier_uri,
35
+ )
36
+ end
37
+
38
+ def verify(data, key, signature)
39
+ verify_signature(data, key, signature)
40
+ rescue OpenSSL::PKey::PKeyError => e
41
+ raise VerificationError.new(
42
+ "verification raised: #{e.class}",
43
+ )
44
+ end
45
+
46
+ def compute_signature(_data, _key)
47
+ raise NotImplementedError,
48
+ "#{self.class} must implement #compute_signature"
49
+ end
50
+
51
+ def verify_signature(_data, _key, _signature)
52
+ raise NotImplementedError,
53
+ "#{self.class} must implement #verify_signature"
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moxml
4
+ module Signature
5
+ module Algorithms
6
+ # Base class for XML Signature transforms.
7
+ #
8
+ # Each transform declares input and output types (:octets or :nodeset)
9
+ # plus an identifier URI. The reference resolver chains them with
10
+ # default type conversion when types mismatch (octets → nodeset via
11
+ # XML parse; nodeset → octets via inclusive C14N 1.0).
12
+ class TransformBase
13
+ class << self
14
+ attr_reader :identifier_uri
15
+
16
+ def identifier(uri)
17
+ @identifier_uri = uri
18
+ Algorithms.register(:transform, uri, self)
19
+ end
20
+
21
+ def input_type; :octets; end
22
+ def output_type; :octets; end
23
+ end
24
+
25
+ # `parameters`: optional hash parsed from the ds:Transform element
26
+ # (e.g. { xpaths: [...] } for the XPath Filter transform).
27
+ def initialize(parameters: nil, context: nil, signature_element: nil)
28
+ @parameters = parameters || {}
29
+ @context = context
30
+ @signature_element = signature_element
31
+ end
32
+
33
+ def transform(_input)
34
+ raise NotImplementedError,
35
+ "#{self.class} must implement #transform"
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moxml
4
+ module Signature
5
+ # Open-closed registry of W3C XML Signature algorithms keyed by URI.
6
+ #
7
+ # Adding a new algorithm:
8
+ # 1. Subclass the relevant base (DigestBase, SignatureMethodBase, etc.)
9
+ # 2. Declare `identifier "http://..."` on the subclass.
10
+ # 3. Add an autoload entry below and a reference in `load_builtins!`.
11
+ #
12
+ # No edits to existing classes are required to add a new algorithm.
13
+ module Algorithms
14
+ CATEGORIES = %i[digest signature_method canonicalization transform].freeze
15
+
16
+ class << self
17
+ def registry
18
+ @registry ||= CATEGORIES.to_h { |c| [c, {}] }
19
+ end
20
+
21
+ def register(category, uri, klass)
22
+ validate_category!(category)
23
+ registry[category][uri] = klass
24
+ klass
25
+ end
26
+
27
+ def lookup(category, uri)
28
+ load_builtins! unless @builtins_loaded
29
+ registry[category][uri] ||
30
+ raise(UnknownAlgorithm.new(category, uri))
31
+ end
32
+
33
+ def registered?(category, uri)
34
+ load_builtins! unless @builtins_loaded
35
+ registry[category].key?(uri)
36
+ end
37
+
38
+ def [](category)
39
+ validate_category!(category)
40
+ registry[category]
41
+ end
42
+
43
+ # Forces autoload of every built-in algorithm class so that
44
+ # self-registration runs. Pure autoload — no `require` calls.
45
+ def load_builtins!
46
+ return if @builtins_loaded
47
+
48
+ # Digests
49
+ SHA1
50
+ SHA224
51
+ SHA256
52
+ SHA384
53
+ SHA512
54
+ # Signature methods
55
+ RsaPkcs1Sha
56
+ HmacSha
57
+ EcdsaSha
58
+ DsaSha
59
+ # Canonicalization
60
+ ExcC14n10
61
+ InclusiveC14n10
62
+ InclusiveC14n11
63
+ # Transforms
64
+ Base64Transform
65
+ EnvelopedSignatureTransform
66
+
67
+ @builtins_loaded = true
68
+ end
69
+
70
+ private
71
+
72
+ def validate_category!(category)
73
+ return if CATEGORIES.include?(category)
74
+
75
+ raise ArgumentError,
76
+ "unknown algorithm category #{category.inspect}; " \
77
+ "expected one of #{CATEGORIES.inspect}"
78
+ end
79
+ end
80
+
81
+ # Base classes — interfaces only.
82
+ autoload :DigestBase, "moxml/signature/algorithms/digest_base"
83
+ autoload :SignatureMethodBase,
84
+ "moxml/signature/algorithms/signature_method_base"
85
+ autoload :CanonicalizationBase,
86
+ "moxml/signature/algorithms/canonicalization_base"
87
+ autoload :TransformBase, "moxml/signature/algorithms/transform_base"
88
+
89
+ # Digests
90
+ autoload :SHA1, "moxml/signature/algorithms/sha1"
91
+ autoload :SHA224, "moxml/signature/algorithms/sha224"
92
+ autoload :SHA256, "moxml/signature/algorithms/sha256"
93
+ autoload :SHA384, "moxml/signature/algorithms/sha384"
94
+ autoload :SHA512, "moxml/signature/algorithms/sha512"
95
+
96
+ # Signature methods
97
+ autoload :RsaPkcs1Sha, "moxml/signature/algorithms/rsa_pkcs1_sha"
98
+ autoload :HmacSha, "moxml/signature/algorithms/hmac_sha"
99
+ autoload :EcdsaSha, "moxml/signature/algorithms/ecdsa_sha"
100
+ autoload :DsaSha, "moxml/signature/algorithms/dsa_sha"
101
+
102
+ # Canonicalization (delegates to Moxml::C14n engine)
103
+ autoload :ExcC14n10, "moxml/signature/algorithms/exc_c14n_10"
104
+ autoload :InclusiveC14n10, "moxml/signature/algorithms/inclusive_c14n_10"
105
+ autoload :InclusiveC14n11, "moxml/signature/algorithms/inclusive_c14n_11"
106
+
107
+ # Transforms
108
+ autoload :Base64Transform, "moxml/signature/algorithms/base64_transform"
109
+ autoload :EnvelopedSignatureTransform,
110
+ "moxml/signature/algorithms/enveloped_signature_transform"
111
+ end
112
+ end
113
+ end