moxml 0.1.25 → 0.1.26

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 (115) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/release.yml +9 -0
  3. data/README.adoc +64 -0
  4. data/docs/signature/algorithms.md +108 -0
  5. data/docs/signature/architecture.md +148 -0
  6. data/docs/signature/c14n.md +110 -0
  7. data/docs/signature/examples.md +109 -0
  8. data/docs/signature/flows.md +132 -0
  9. data/docs/signature/quick-reference.md +115 -0
  10. data/docs/signature/security.md +143 -0
  11. data/examples/signature/auto_key_extraction.rb +24 -0
  12. data/examples/signature/enveloped_rsa.rb +45 -0
  13. data/examples/signature/hmac.rb +33 -0
  14. data/lib/moxml/c14n/attribute_handler.rb +71 -0
  15. data/lib/moxml/c14n/character_encoder.rb +29 -0
  16. data/lib/moxml/c14n/data_model.rb +145 -0
  17. data/lib/moxml/c14n/exclusive.rb +188 -0
  18. data/lib/moxml/c14n/inclusive_10.rb +42 -0
  19. data/lib/moxml/c14n/inclusive_11.rb +21 -0
  20. data/lib/moxml/c14n/namespace_context.rb +68 -0
  21. data/lib/moxml/c14n/namespace_handler.rb +71 -0
  22. data/lib/moxml/c14n/node.rb +42 -0
  23. data/lib/moxml/c14n/nodes/attribute_node.rb +50 -0
  24. data/lib/moxml/c14n/nodes/comment_node.rb +29 -0
  25. data/lib/moxml/c14n/nodes/element_node.rb +62 -0
  26. data/lib/moxml/c14n/nodes/namespace_node.rb +41 -0
  27. data/lib/moxml/c14n/nodes/processing_instruction_node.rb +30 -0
  28. data/lib/moxml/c14n/nodes/root_node.rb +22 -0
  29. data/lib/moxml/c14n/nodes/text_node.rb +29 -0
  30. data/lib/moxml/c14n/nodes.rb +17 -0
  31. data/lib/moxml/c14n/processor.rb +118 -0
  32. data/lib/moxml/c14n/writer.rb +121 -0
  33. data/lib/moxml/c14n/xml_base_handler.rb +140 -0
  34. data/lib/moxml/c14n.rb +96 -0
  35. data/lib/moxml/signature/algorithms/base64_transform.rb +40 -0
  36. data/lib/moxml/signature/algorithms/canonicalization_base.rb +87 -0
  37. data/lib/moxml/signature/algorithms/digest_base.rb +51 -0
  38. data/lib/moxml/signature/algorithms/dsa_sha.rb +107 -0
  39. data/lib/moxml/signature/algorithms/ecdsa_sha.rb +125 -0
  40. data/lib/moxml/signature/algorithms/enveloped_signature_transform.rb +79 -0
  41. data/lib/moxml/signature/algorithms/exc_c14n_10.rb +23 -0
  42. data/lib/moxml/signature/algorithms/hmac_sha.rb +117 -0
  43. data/lib/moxml/signature/algorithms/inclusive_c14n_10.rb +21 -0
  44. data/lib/moxml/signature/algorithms/inclusive_c14n_11.rb +23 -0
  45. data/lib/moxml/signature/algorithms/rsa_pkcs1_sha.rb +56 -0
  46. data/lib/moxml/signature/algorithms/sha1.rb +13 -0
  47. data/lib/moxml/signature/algorithms/sha224.rb +13 -0
  48. data/lib/moxml/signature/algorithms/sha256.rb +13 -0
  49. data/lib/moxml/signature/algorithms/sha384.rb +13 -0
  50. data/lib/moxml/signature/algorithms/sha512.rb +13 -0
  51. data/lib/moxml/signature/algorithms/signature_method_base.rb +58 -0
  52. data/lib/moxml/signature/algorithms/transform_base.rb +40 -0
  53. data/lib/moxml/signature/algorithms.rb +113 -0
  54. data/lib/moxml/signature/errors.rb +99 -0
  55. data/lib/moxml/signature/key_extractor.rb +172 -0
  56. data/lib/moxml/signature/model/algorithm_method.rb +18 -0
  57. data/lib/moxml/signature/model/digest_method.rb +15 -0
  58. data/lib/moxml/signature/model/key/dsa_key_value.rb +29 -0
  59. data/lib/moxml/signature/model/key/ec_key_value.rb +22 -0
  60. data/lib/moxml/signature/model/key/rsa_key_value.rb +19 -0
  61. data/lib/moxml/signature/model/key/x509_data.rb +32 -0
  62. data/lib/moxml/signature/model/key/x509_digest.rb +19 -0
  63. data/lib/moxml/signature/model/key/x509_issuer_serial.rb +19 -0
  64. data/lib/moxml/signature/model/key_info.rb +21 -0
  65. data/lib/moxml/signature/model/key_value.rb +20 -0
  66. data/lib/moxml/signature/model/object_element.rb +18 -0
  67. data/lib/moxml/signature/model/reference.rb +22 -0
  68. data/lib/moxml/signature/model/signature.rb +21 -0
  69. data/lib/moxml/signature/model/signature_value.rb +16 -0
  70. data/lib/moxml/signature/model/signed_info.rb +20 -0
  71. data/lib/moxml/signature/model/transform.rb +16 -0
  72. data/lib/moxml/signature/model/transforms.rb +36 -0
  73. data/lib/moxml/signature/model.rb +35 -0
  74. data/lib/moxml/signature/parser.rb +270 -0
  75. data/lib/moxml/signature/reference_resolver.rb +79 -0
  76. data/lib/moxml/signature/reference_result.rb +22 -0
  77. data/lib/moxml/signature/serializer.rb +163 -0
  78. data/lib/moxml/signature/signer.rb +73 -0
  79. data/lib/moxml/signature/single_verification_result.rb +31 -0
  80. data/lib/moxml/signature/transform_pipeline.rb +106 -0
  81. data/lib/moxml/signature/verification_result.rb +27 -0
  82. data/lib/moxml/signature/verifier.rb +129 -0
  83. data/lib/moxml/signature.rb +94 -0
  84. data/lib/moxml/version.rb +1 -1
  85. data/lib/moxml.rb +2 -0
  86. data/reference-docs/w3c-xmldsig-bestpractices.md +216 -0
  87. data/reference-docs/w3c-xmldsig-core.md +400 -0
  88. data/spec/fixtures/xmldsig/keys/rsa_private.pem +28 -0
  89. data/spec/fixtures/xmldsig/keys/rsa_public.pem +9 -0
  90. data/spec/fixtures/xmldsig/keys/rsa_ref.pem +15 -0
  91. data/spec/fixtures/xmldsig/keys/rsa_ref.pub +6 -0
  92. data/spec/fixtures/xmldsig/sign2-doc.xml +6 -0
  93. data/spec/fixtures/xmldsig/sign2-result.xml +25 -0
  94. data/spec/fixtures/xmldsig/sign3-result.xml +39 -0
  95. data/spec/moxml/c14n/api_spec.rb +117 -0
  96. data/spec/moxml/c14n/c14n_spec.rb +88 -0
  97. data/spec/moxml/c14n/comments_pis_spec.rb +65 -0
  98. data/spec/moxml/c14n/inclusive10_spec.rb +60 -0
  99. data/spec/moxml/c14n/namespace_edge_cases_spec.rb +88 -0
  100. data/spec/moxml/c14n/xml_attributes_spec.rb +54 -0
  101. data/spec/moxml/signature/algorithms/base64_transform_spec.rb +26 -0
  102. data/spec/moxml/signature/algorithms/digest_base_spec.rb +72 -0
  103. data/spec/moxml/signature/algorithms/dsa_sha_spec.rb +46 -0
  104. data/spec/moxml/signature/algorithms/ecdsa_sha_spec.rb +72 -0
  105. data/spec/moxml/signature/algorithms/enveloped_signature_transform_spec.rb +40 -0
  106. data/spec/moxml/signature/algorithms/hmac_sha_spec.rb +71 -0
  107. data/spec/moxml/signature/algorithms/rsa_pkcs1_sha_spec.rb +55 -0
  108. data/spec/moxml/signature/algorithms_spec.rb +76 -0
  109. data/spec/moxml/signature/cross_verify_spec.rb +64 -0
  110. data/spec/moxml/signature/edge_cases_spec.rb +284 -0
  111. data/spec/moxml/signature/fixtures_spec.rb +80 -0
  112. data/spec/moxml/signature/key_extractor_spec.rb +96 -0
  113. data/spec/moxml/signature/model_spec.rb +54 -0
  114. data/spec/moxml/signature/round_trip_spec.rb +113 -0
  115. metadata +111 -2
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moxml
4
+ module Signature
5
+ module Model
6
+ module Key
7
+ # dsig11:ECKeyValue — NamedCurve URI + PublicKey octets, or
8
+ # explicit ECParameters.
9
+ class ECKeyValue
10
+ attr_accessor :named_curve_uri, :public_key, :ec_parameters
11
+
12
+ def initialize(named_curve_uri: nil, public_key: nil,
13
+ ec_parameters: nil)
14
+ @named_curve_uri = named_curve_uri
15
+ @public_key = public_key
16
+ @ec_parameters = ec_parameters
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moxml
4
+ module Signature
5
+ module Model
6
+ module Key
7
+ # ds:RSAKeyValue — base64-encoded CryptoBinary Modulus + Exponent.
8
+ class RSAKeyValue
9
+ attr_accessor :modulus, :exponent
10
+
11
+ def initialize(modulus:, exponent:)
12
+ @modulus = modulus
13
+ @exponent = exponent
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moxml
4
+ module Signature
5
+ module Model
6
+ module Key
7
+ # ds:X509Data (spec §4.5.4) — container for certificate identifiers.
8
+ # May carry X509IssuerSerial, X509SubjectName, X509SKI, X509Certificate,
9
+ # X509CRL, and dsig11:X509Digest children, all describing the same key.
10
+ class X509Data
11
+ attr_accessor :issuer_serial, :subject_name, :subject_key_id,
12
+ :certificates, :crls, :digests
13
+
14
+ def initialize(issuer_serial: nil, subject_name: nil,
15
+ subject_key_id: nil, certificates: [],
16
+ crls: [], digests: [])
17
+ @issuer_serial = issuer_serial
18
+ @subject_name = subject_name
19
+ @subject_key_id = subject_key_id
20
+ @certificates = Array(certificates)
21
+ @crls = Array(crls)
22
+ @digests = Array(digests)
23
+ end
24
+
25
+ def first_certificate
26
+ certificates.first
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moxml
4
+ module Signature
5
+ module Model
6
+ module Key
7
+ # dsig11:X509Digest — algorithm URI + base64 digest of a cert.
8
+ class X509Digest
9
+ attr_accessor :algorithm, :digest
10
+
11
+ def initialize(algorithm:, digest:)
12
+ @algorithm = algorithm
13
+ @digest = digest
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moxml
4
+ module Signature
5
+ module Model
6
+ module Key
7
+ # ds:X509IssuerSerial — deprecated in favor of X509Digest.
8
+ class X509IssuerSerial
9
+ attr_accessor :issuer_name, :serial_number
10
+
11
+ def initialize(issuer_name:, serial_number:)
12
+ @issuer_name = issuer_name
13
+ @serial_number = serial_number
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moxml
4
+ module Signature
5
+ module Model
6
+ class KeyInfo
7
+ attr_accessor :id, :key_name, :key_value, :x509_data,
8
+ :raw_elements
9
+
10
+ def initialize(id: nil, key_name: nil, key_value: nil,
11
+ x509_data: nil, raw_elements: [])
12
+ @id = id
13
+ @key_name = key_name
14
+ @key_value = key_value
15
+ @x509_data = x509_data
16
+ @raw_elements = raw_elements
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moxml
4
+ module Signature
5
+ module Model
6
+ class KeyValue
7
+ attr_accessor :rsa_key_value, :dsa_key_value, :ec_key_value,
8
+ :raw_element
9
+
10
+ def initialize(rsa_key_value: nil, dsa_key_value: nil,
11
+ ec_key_value: nil, raw_element: nil)
12
+ @rsa_key_value = rsa_key_value
13
+ @dsa_key_value = dsa_key_value
14
+ @ec_key_value = ec_key_value
15
+ @raw_element = raw_element
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moxml
4
+ module Signature
5
+ module Model
6
+ class ObjectElement
7
+ attr_accessor :id, :mime_type, :encoding, :content
8
+
9
+ def initialize(id: nil, mime_type: nil, encoding: nil, content: nil)
10
+ @id = id
11
+ @mime_type = mime_type
12
+ @encoding = encoding
13
+ @content = content
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moxml
4
+ module Signature
5
+ module Model
6
+ class Reference
7
+ attr_accessor :id, :uri, :type, :transforms, :digest_method,
8
+ :digest_value
9
+
10
+ def initialize(id: nil, uri: nil, type: nil, transforms: nil,
11
+ digest_method: nil, digest_value: nil)
12
+ @id = id
13
+ @uri = uri
14
+ @type = type
15
+ @transforms = transforms
16
+ @digest_method = digest_method
17
+ @digest_value = digest_value
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moxml
4
+ module Signature
5
+ module Model
6
+ class Signature
7
+ attr_accessor :id, :signed_info, :signature_value,
8
+ :key_info, :objects
9
+
10
+ def initialize(id: nil, signed_info: nil, signature_value: nil,
11
+ key_info: nil, objects: [])
12
+ @id = id
13
+ @signed_info = signed_info
14
+ @signature_value = signature_value
15
+ @key_info = key_info
16
+ @objects = objects
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moxml
4
+ module Signature
5
+ module Model
6
+ class SignatureValue
7
+ attr_accessor :id, :value
8
+
9
+ def initialize(id: nil, value: nil)
10
+ @id = id
11
+ @value = value
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moxml
4
+ module Signature
5
+ module Model
6
+ class SignedInfo
7
+ attr_accessor :id, :canonicalization_method, :signature_method,
8
+ :references
9
+
10
+ def initialize(id: nil, canonicalization_method: nil,
11
+ signature_method: nil, references: [])
12
+ @id = id
13
+ @canonicalization_method = canonicalization_method
14
+ @signature_method = signature_method
15
+ @references = references
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moxml
4
+ module Signature
5
+ module Model
6
+ class Transform
7
+ attr_accessor :algorithm, :parameters
8
+
9
+ def initialize(algorithm:, parameters: {})
10
+ @algorithm = algorithm
11
+ @parameters = parameters
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moxml
4
+ module Signature
5
+ module Model
6
+ class Transforms
7
+ attr_accessor :transforms
8
+
9
+ def initialize(transforms: [])
10
+ @transforms = Array(transforms)
11
+ end
12
+
13
+ def <<(transform)
14
+ @transforms << transform
15
+ self
16
+ end
17
+
18
+ def each(&block)
19
+ @transforms.each(&block)
20
+ end
21
+
22
+ def empty?
23
+ @transforms.empty?
24
+ end
25
+
26
+ def size
27
+ @transforms.size
28
+ end
29
+
30
+ def length
31
+ @transforms.size
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moxml
4
+ module Signature
5
+ # Plain-Ruby-Object model of the W3C ds:Signature schema.
6
+ #
7
+ # Models carry data; they do not (de)serialize themselves. The Serializer
8
+ # walks a model and emits a Moxml document; the Parser walks a Moxml
9
+ # document and constructs a model. This separation keeps the data shape
10
+ # and the wire shape independent.
11
+ module Model
12
+ autoload :Signature, "moxml/signature/model/signature"
13
+ autoload :SignedInfo, "moxml/signature/model/signed_info"
14
+ autoload :Reference, "moxml/signature/model/reference"
15
+ autoload :Transforms, "moxml/signature/model/transforms"
16
+ autoload :Transform, "moxml/signature/model/transform"
17
+ autoload :DigestMethod, "moxml/signature/model/digest_method"
18
+ autoload :AlgorithmMethod, "moxml/signature/model/algorithm_method"
19
+ autoload :SignatureValue, "moxml/signature/model/signature_value"
20
+ autoload :KeyInfo, "moxml/signature/model/key_info"
21
+ autoload :KeyValue, "moxml/signature/model/key_value"
22
+ autoload :ObjectElement, "moxml/signature/model/object_element"
23
+
24
+ module Key
25
+ autoload :X509Data, "moxml/signature/model/key/x509_data"
26
+ autoload :X509IssuerSerial,
27
+ "moxml/signature/model/key/x509_issuer_serial"
28
+ autoload :X509Digest, "moxml/signature/model/key/x509_digest"
29
+ autoload :RSAKeyValue, "moxml/signature/model/key/rsa_key_value"
30
+ autoload :DSAKeyValue, "moxml/signature/model/key/dsa_key_value"
31
+ autoload :ECKeyValue, "moxml/signature/model/key/ec_key_value"
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,270 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+
5
+ module Moxml
6
+ module Signature
7
+ # Translates a Moxml::Document containing ds:Signature into a Model::Signature.
8
+ #
9
+ # Robust against namespace prefix variations (ds:, dsig:, default ns).
10
+ class Parser
11
+ DS = { "ds" => DSIG_NS }.freeze
12
+
13
+ attr_reader :context
14
+
15
+ def initialize(context:)
16
+ @context = context
17
+ end
18
+
19
+ # `signature_element`: a Moxml::Element whose name is Signature in the
20
+ # xmldsig# namespace.
21
+ def parse(signature_element)
22
+ Model::Signature.new(
23
+ id: signature_element["Id"],
24
+ signed_info: parse_signed_info(at_child(signature_element, "SignedInfo")),
25
+ signature_value: parse_signature_value(at_child(signature_element, "SignatureValue")),
26
+ key_info: parse_key_info(at_child(signature_element, "KeyInfo")),
27
+ objects: [],
28
+ )
29
+ end
30
+
31
+ private
32
+
33
+ def parse_signed_info(elem)
34
+ return nil unless elem
35
+
36
+ Model::SignedInfo.new(
37
+ id: elem["Id"],
38
+ canonicalization_method: parse_algorithm_method(at_child(elem, "CanonicalizationMethod")),
39
+ signature_method: parse_algorithm_method(at_child(elem, "SignatureMethod")),
40
+ references: at_children(elem, "Reference").map { |r| parse_reference(r) },
41
+ )
42
+ end
43
+
44
+ def parse_algorithm_method(elem)
45
+ return nil unless elem
46
+
47
+ parameters = {}
48
+ hmac_len = at_child(elem, "HMACOutputLength")
49
+ if hmac_len
50
+ length = begin
51
+ Integer(hmac_len.text.strip)
52
+ rescue StandardError
53
+ nil
54
+ end
55
+ parameters[:hmac_output_length] = length if length
56
+ end
57
+
58
+ Model::AlgorithmMethod.new(
59
+ algorithm: elem["Algorithm"],
60
+ parameters: parameters,
61
+ )
62
+ end
63
+
64
+ def parse_reference(elem)
65
+ return nil unless elem
66
+
67
+ transforms_elem = at_child(elem, "Transforms")
68
+ Model::Reference.new(
69
+ id: elem["Id"],
70
+ uri: elem["URI"],
71
+ type: elem["Type"],
72
+ transforms: parse_transforms(transforms_elem),
73
+ digest_method: parse_digest_method(at_child(elem, "DigestMethod")),
74
+ digest_value: at_child(elem, "DigestValue")&.text || "",
75
+ )
76
+ end
77
+
78
+ def parse_transforms(elem)
79
+ return nil unless elem
80
+
81
+ transforms = at_children(elem, "Transform").map do |t|
82
+ xpath_children = at_children(t, "XPath").map(&:text)
83
+ Model::Transform.new(
84
+ algorithm: t["Algorithm"],
85
+ parameters: xpath_children.empty? ? {} : { xpaths: xpath_children },
86
+ )
87
+ end
88
+ Model::Transforms.new(transforms: transforms)
89
+ end
90
+
91
+ def parse_digest_method(elem)
92
+ return nil unless elem
93
+
94
+ Model::DigestMethod.new(algorithm: elem["Algorithm"])
95
+ end
96
+
97
+ def parse_signature_value(elem)
98
+ return nil unless elem
99
+
100
+ text = (elem.text || "").gsub(/\s+/, "")
101
+ value = text.empty? ? nil : Base64.strict_decode64(text)
102
+ Model::SignatureValue.new(id: elem["Id"], value: value)
103
+ rescue ArgumentError => e
104
+ raise MalformedSignatureError.new(
105
+ "SignatureValue is not valid base64: #{e.message}",
106
+ )
107
+ end
108
+
109
+ def parse_key_info(elem)
110
+ return nil unless elem
111
+
112
+ key_name_el = at_child(elem, "KeyName")
113
+ x509_data_el = at_child(elem, "X509Data")
114
+ key_value_el = at_child(elem, "KeyValue")
115
+
116
+ Model::KeyInfo.new(
117
+ id: elem["Id"],
118
+ key_name: key_name_el&.text,
119
+ x509_data: parse_x509_data(x509_data_el),
120
+ key_value: parse_key_value(key_value_el),
121
+ raw_elements: raw_key_info_children(elem),
122
+ )
123
+ end
124
+
125
+ def parse_x509_data(elem)
126
+ return nil unless elem
127
+
128
+ certificates = at_children(elem, "X509Certificate").map do |cert_el|
129
+ strip_base64(cert_el.text)
130
+ end
131
+ issuer_serial_el = at_child(elem, "X509IssuerSerial")
132
+ subject_name_el = at_child(elem, "X509SubjectName")
133
+ ski_el = at_child(elem, "X509SKI")
134
+ crl_els = at_children(elem, "X509CRL")
135
+ # dsig11:X509Digest is in a different namespace; look it up loosely.
136
+ digest_els = elem.children.select do |c|
137
+ c.is_a?(::Moxml::Element) &&
138
+ c.name == "X509Digest" &&
139
+ c.namespace_uri == DSIG11_NS
140
+ end
141
+
142
+ Model::Key::X509Data.new(
143
+ issuer_serial: parse_x509_issuer_serial(issuer_serial_el),
144
+ subject_name: subject_name_el&.text,
145
+ subject_key_id: strip_base64(ski_el&.text),
146
+ certificates: certificates,
147
+ crls: crl_els.map { |e| strip_base64(e.text) },
148
+ digests: digest_els.map { |e| parse_x509_digest(e) },
149
+ )
150
+ end
151
+
152
+ def parse_x509_issuer_serial(elem)
153
+ return nil unless elem
154
+
155
+ issuer = at_child(elem, "X509IssuerName")
156
+ serial = at_child(elem, "X509SerialNumber")
157
+ return nil unless issuer && serial
158
+
159
+ Model::Key::X509IssuerSerial.new(
160
+ issuer_name: issuer.text,
161
+ serial_number: serial.text,
162
+ )
163
+ end
164
+
165
+ def parse_x509_digest(elem)
166
+ return nil unless elem
167
+
168
+ Model::Key::X509Digest.new(
169
+ algorithm: elem["Algorithm"],
170
+ digest: strip_base64(elem.text),
171
+ )
172
+ end
173
+
174
+ def parse_key_value(elem)
175
+ return nil unless elem
176
+
177
+ rsa_el = at_child(elem, "RSAKeyValue")
178
+ dsa_el = at_child(elem, "DSAKeyValue")
179
+ ec_el = elem.children.find do |c|
180
+ c.is_a?(::Moxml::Element) &&
181
+ c.name == "ECKeyValue" &&
182
+ c.namespace_uri == DSIG11_NS
183
+ end
184
+
185
+ Model::KeyValue.new(
186
+ rsa_key_value: parse_rsa_key_value(rsa_el),
187
+ dsa_key_value: parse_dsa_key_value(dsa_el),
188
+ ec_key_value: parse_ec_key_value(ec_el),
189
+ )
190
+ end
191
+
192
+ def parse_rsa_key_value(elem)
193
+ return nil unless elem
194
+
195
+ Model::Key::RSAKeyValue.new(
196
+ modulus: text_of(elem, "Modulus"),
197
+ exponent: text_of(elem, "Exponent"),
198
+ )
199
+ end
200
+
201
+ def parse_dsa_key_value(elem)
202
+ return nil unless elem
203
+
204
+ Model::Key::DSAKeyValue.new(
205
+ p: text_of(elem, "P"),
206
+ q: text_of(elem, "Q"),
207
+ g: text_of(elem, "G"),
208
+ y: text_of(elem, "Y"),
209
+ j: text_of(elem, "J"),
210
+ seed: text_of(elem, "Seed"),
211
+ pgen_counter: text_of(elem, "PgenCounter"),
212
+ )
213
+ end
214
+
215
+ def parse_ec_key_value(elem)
216
+ return nil unless elem
217
+
218
+ named_curve_el = elem.children.find do |c|
219
+ c.is_a?(::Moxml::Element) &&
220
+ c.name == "NamedCurve" &&
221
+ c.namespace_uri == DSIG11_NS
222
+ end
223
+ public_key_el = elem.children.find do |c|
224
+ c.is_a?(::Moxml::Element) &&
225
+ c.name == "PublicKey" &&
226
+ c.namespace_uri == DSIG11_NS
227
+ end
228
+
229
+ Model::Key::ECKeyValue.new(
230
+ named_curve_uri: named_curve_el&.[]("URI"),
231
+ public_key: strip_base64(public_key_el&.text),
232
+ )
233
+ end
234
+
235
+ def text_of(parent, local_name)
236
+ elem = at_child(parent, local_name)
237
+ elem&.text
238
+ end
239
+
240
+ def strip_base64(text)
241
+ return nil if text.nil?
242
+
243
+ text.to_s.gsub(/\s+/, "")
244
+ end
245
+
246
+ def raw_key_info_children(elem)
247
+ # Children we don't model explicitly are preserved for round-trip
248
+ # fidelity (PGPData, SPKIData, MgmtData, dsig11:KeyInfoReference,
249
+ # dsig11:DEREncodedKeyValue, xenc:*).
250
+ elem.children.grep(::Moxml::Element).to_a
251
+ end
252
+
253
+ def at_child(parent, local_name)
254
+ parent.children.find do |c|
255
+ c.is_a?(::Moxml::Element) &&
256
+ c.name == local_name &&
257
+ c.namespace_uri == DSIG_NS
258
+ end
259
+ end
260
+
261
+ def at_children(parent, local_name)
262
+ parent.children.select do |c|
263
+ c.is_a?(::Moxml::Element) &&
264
+ c.name == local_name &&
265
+ c.namespace_uri == DSIG_NS
266
+ end
267
+ end
268
+ end
269
+ end
270
+ end