moxml 0.1.24 → 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.
- checksums.yaml +4 -4
- data/.github/workflows/docs.yml +1 -1
- data/.github/workflows/opal.yml +18 -1
- data/.github/workflows/rake.yml +32 -3
- data/.github/workflows/release.yml +9 -0
- data/.github/workflows/round-trip.yml +72 -9
- data/.gitmodules +6 -0
- data/.rubocop_todo.yml +2 -0
- data/Gemfile +9 -1
- data/README.adoc +188 -0
- data/Rakefile +116 -4
- data/docs/signature/algorithms.md +108 -0
- data/docs/signature/architecture.md +148 -0
- data/docs/signature/c14n.md +110 -0
- data/docs/signature/examples.md +109 -0
- data/docs/signature/flows.md +132 -0
- data/docs/signature/quick-reference.md +115 -0
- data/docs/signature/security.md +143 -0
- data/examples/signature/auto_key_extraction.rb +24 -0
- data/examples/signature/enveloped_rsa.rb +45 -0
- data/examples/signature/hmac.rb +33 -0
- data/lib/compat/opal/moxml_boot.rb +4 -0
- data/lib/moxml/adapter.rb +5 -3
- data/lib/moxml/c14n/attribute_handler.rb +71 -0
- data/lib/moxml/c14n/character_encoder.rb +29 -0
- data/lib/moxml/c14n/data_model.rb +145 -0
- data/lib/moxml/c14n/exclusive.rb +188 -0
- data/lib/moxml/c14n/inclusive_10.rb +42 -0
- data/lib/moxml/c14n/inclusive_11.rb +21 -0
- data/lib/moxml/c14n/namespace_context.rb +68 -0
- data/lib/moxml/c14n/namespace_handler.rb +71 -0
- data/lib/moxml/c14n/node.rb +42 -0
- data/lib/moxml/c14n/nodes/attribute_node.rb +50 -0
- data/lib/moxml/c14n/nodes/comment_node.rb +29 -0
- data/lib/moxml/c14n/nodes/element_node.rb +62 -0
- data/lib/moxml/c14n/nodes/namespace_node.rb +41 -0
- data/lib/moxml/c14n/nodes/processing_instruction_node.rb +30 -0
- data/lib/moxml/c14n/nodes/root_node.rb +22 -0
- data/lib/moxml/c14n/nodes/text_node.rb +29 -0
- data/lib/moxml/c14n/nodes.rb +17 -0
- data/lib/moxml/c14n/processor.rb +118 -0
- data/lib/moxml/c14n/writer.rb +121 -0
- data/lib/moxml/c14n/xml_base_handler.rb +140 -0
- data/lib/moxml/c14n.rb +96 -0
- data/lib/moxml/config.rb +1 -1
- data/lib/moxml/signature/algorithms/base64_transform.rb +40 -0
- data/lib/moxml/signature/algorithms/canonicalization_base.rb +87 -0
- data/lib/moxml/signature/algorithms/digest_base.rb +51 -0
- data/lib/moxml/signature/algorithms/dsa_sha.rb +107 -0
- data/lib/moxml/signature/algorithms/ecdsa_sha.rb +125 -0
- data/lib/moxml/signature/algorithms/enveloped_signature_transform.rb +79 -0
- data/lib/moxml/signature/algorithms/exc_c14n_10.rb +23 -0
- data/lib/moxml/signature/algorithms/hmac_sha.rb +117 -0
- data/lib/moxml/signature/algorithms/inclusive_c14n_10.rb +21 -0
- data/lib/moxml/signature/algorithms/inclusive_c14n_11.rb +23 -0
- data/lib/moxml/signature/algorithms/rsa_pkcs1_sha.rb +56 -0
- data/lib/moxml/signature/algorithms/sha1.rb +13 -0
- data/lib/moxml/signature/algorithms/sha224.rb +13 -0
- data/lib/moxml/signature/algorithms/sha256.rb +13 -0
- data/lib/moxml/signature/algorithms/sha384.rb +13 -0
- data/lib/moxml/signature/algorithms/sha512.rb +13 -0
- data/lib/moxml/signature/algorithms/signature_method_base.rb +58 -0
- data/lib/moxml/signature/algorithms/transform_base.rb +40 -0
- data/lib/moxml/signature/algorithms.rb +113 -0
- data/lib/moxml/signature/errors.rb +99 -0
- data/lib/moxml/signature/key_extractor.rb +172 -0
- data/lib/moxml/signature/model/algorithm_method.rb +18 -0
- data/lib/moxml/signature/model/digest_method.rb +15 -0
- data/lib/moxml/signature/model/key/dsa_key_value.rb +29 -0
- data/lib/moxml/signature/model/key/ec_key_value.rb +22 -0
- data/lib/moxml/signature/model/key/rsa_key_value.rb +19 -0
- data/lib/moxml/signature/model/key/x509_data.rb +32 -0
- data/lib/moxml/signature/model/key/x509_digest.rb +19 -0
- data/lib/moxml/signature/model/key/x509_issuer_serial.rb +19 -0
- data/lib/moxml/signature/model/key_info.rb +21 -0
- data/lib/moxml/signature/model/key_value.rb +20 -0
- data/lib/moxml/signature/model/object_element.rb +18 -0
- data/lib/moxml/signature/model/reference.rb +22 -0
- data/lib/moxml/signature/model/signature.rb +21 -0
- data/lib/moxml/signature/model/signature_value.rb +16 -0
- data/lib/moxml/signature/model/signed_info.rb +20 -0
- data/lib/moxml/signature/model/transform.rb +16 -0
- data/lib/moxml/signature/model/transforms.rb +36 -0
- data/lib/moxml/signature/model.rb +35 -0
- data/lib/moxml/signature/parser.rb +270 -0
- data/lib/moxml/signature/reference_resolver.rb +79 -0
- data/lib/moxml/signature/reference_result.rb +22 -0
- data/lib/moxml/signature/serializer.rb +163 -0
- data/lib/moxml/signature/signer.rb +73 -0
- data/lib/moxml/signature/single_verification_result.rb +31 -0
- data/lib/moxml/signature/transform_pipeline.rb +106 -0
- data/lib/moxml/signature/verification_result.rb +27 -0
- data/lib/moxml/signature/verifier.rb +129 -0
- data/lib/moxml/signature.rb +94 -0
- data/lib/moxml/version.rb +1 -1
- data/lib/moxml.rb +2 -0
- data/moxml.gemspec +1 -1
- data/reference-docs/w3c-xmldsig-bestpractices.md +216 -0
- data/reference-docs/w3c-xmldsig-core.md +400 -0
- data/spec/fixtures/xmldsig/keys/rsa_private.pem +28 -0
- data/spec/fixtures/xmldsig/keys/rsa_public.pem +9 -0
- data/spec/fixtures/xmldsig/keys/rsa_ref.pem +15 -0
- data/spec/fixtures/xmldsig/keys/rsa_ref.pub +6 -0
- data/spec/fixtures/xmldsig/sign2-doc.xml +6 -0
- data/spec/fixtures/xmldsig/sign2-result.xml +25 -0
- data/spec/fixtures/xmldsig/sign3-result.xml +39 -0
- data/spec/moxml/adapter/platform_spec.rb +64 -46
- data/spec/moxml/c14n/api_spec.rb +117 -0
- data/spec/moxml/c14n/c14n_spec.rb +88 -0
- data/spec/moxml/c14n/comments_pis_spec.rb +65 -0
- data/spec/moxml/c14n/inclusive10_spec.rb +60 -0
- data/spec/moxml/c14n/namespace_edge_cases_spec.rb +88 -0
- data/spec/moxml/c14n/xml_attributes_spec.rb +54 -0
- data/spec/moxml/opal_oga_adapter_spec.rb +14 -0
- data/spec/moxml/opal_oga_features_spec.rb +212 -0
- data/spec/moxml/opal_oga_smoke_spec.rb +35 -0
- data/spec/moxml/signature/algorithms/base64_transform_spec.rb +26 -0
- data/spec/moxml/signature/algorithms/digest_base_spec.rb +72 -0
- data/spec/moxml/signature/algorithms/dsa_sha_spec.rb +46 -0
- data/spec/moxml/signature/algorithms/ecdsa_sha_spec.rb +72 -0
- data/spec/moxml/signature/algorithms/enveloped_signature_transform_spec.rb +40 -0
- data/spec/moxml/signature/algorithms/hmac_sha_spec.rb +71 -0
- data/spec/moxml/signature/algorithms/rsa_pkcs1_sha_spec.rb +55 -0
- data/spec/moxml/signature/algorithms_spec.rb +76 -0
- data/spec/moxml/signature/cross_verify_spec.rb +64 -0
- data/spec/moxml/signature/edge_cases_spec.rb +284 -0
- data/spec/moxml/signature/fixtures_spec.rb +80 -0
- data/spec/moxml/signature/key_extractor_spec.rb +96 -0
- data/spec/moxml/signature/model_spec.rb +54 -0
- data/spec/moxml/signature/round_trip_spec.rb +113 -0
- data/spec/moxml/xpath/functions/node_functions_spec.rb +1 -1
- data/spec/spec_helper.rb +1 -1
- metadata +115 -2
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<?xml version="1.0"?>
|
|
2
|
+
<Envelope xmlns="urn:envelope">
|
|
3
|
+
<Data>
|
|
4
|
+
Hello, World!
|
|
5
|
+
</Data>
|
|
6
|
+
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
|
|
7
|
+
<SignedInfo>
|
|
8
|
+
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
|
|
9
|
+
<SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
|
|
10
|
+
<Reference>
|
|
11
|
+
<Transforms>
|
|
12
|
+
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
|
|
13
|
+
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
|
|
14
|
+
</Transforms>
|
|
15
|
+
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
|
|
16
|
+
<DigestValue>Gx8CGUsbi2qvBLd15VCmwELbDMND8F4vY3jPOc7/FJ0=</DigestValue>
|
|
17
|
+
</Reference>
|
|
18
|
+
</SignedInfo>
|
|
19
|
+
<SignatureValue>T2c7nqOw55P8hcP1qhvfPCwOSEAuo8HstZf9shlrggcarxfgWTKhA6UdrF4McfrS
|
|
20
|
+
XtcgHA7zy0Yzd2cgeGkKA2jgI+9QRhoQsifOMuI55sE5r+fpBs+goaxC57gmcBXj
|
|
21
|
+
XnuwIiWf7nfpF4hYZ841HzYd2HcpQKPTdbhvZUprvx8=</SignatureValue>
|
|
22
|
+
<KeyInfo>
|
|
23
|
+
<KeyName>test</KeyName>
|
|
24
|
+
</KeyInfo>
|
|
25
|
+
</Signature></Envelope>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<?xml version="1.0"?>
|
|
2
|
+
<Envelope xmlns="urn:envelope">
|
|
3
|
+
<Data>
|
|
4
|
+
Hello, World!
|
|
5
|
+
</Data>
|
|
6
|
+
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
|
|
7
|
+
<SignedInfo>
|
|
8
|
+
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
|
|
9
|
+
<SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
|
|
10
|
+
<Reference>
|
|
11
|
+
<Transforms>
|
|
12
|
+
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
|
|
13
|
+
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
|
|
14
|
+
</Transforms>
|
|
15
|
+
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
|
|
16
|
+
<DigestValue>Gx8CGUsbi2qvBLd15VCmwELbDMND8F4vY3jPOc7/FJ0=</DigestValue>
|
|
17
|
+
</Reference>
|
|
18
|
+
</SignedInfo>
|
|
19
|
+
<SignatureValue>TGJ9fCzjppp3LgG4fiBJx+0R34wRa7il9XKKZ+kkOAdKkcW0PIAYKmjn0Tn8krGd
|
|
20
|
+
Gw6qtFFqjdohXfhkKmajXAFunEtd3J0kHFkf3obIwRB1qdsYmKXVFxUx3GqcIlph
|
|
21
|
+
vt9v/9FC12JAxwAiJXHuY2xN5uo3xSDER4+tCCy3/AI=</SignatureValue>
|
|
22
|
+
<KeyInfo>
|
|
23
|
+
<X509Data>
|
|
24
|
+
<X509Certificate>MIICLzCCAZgCCQCVuhhQ38rw0TANBgkqhkiG9w0BAQUFADBbMQswCQYDVQQGEwJV
|
|
25
|
+
UzEQMA4GA1UECAwHR2VvcmdpYTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ
|
|
26
|
+
dHkgTHRkMRcwFQYDVQQDDA53d3cuZ29vZ2xlLmNvbTAgFw0xMzA1MjUxODQwMDRa
|
|
27
|
+
GA8zMDEyMDkyNTE4NDAwNFowWzELMAkGA1UEBhMCVVMxEDAOBgNVBAgMB0dlb3Jn
|
|
28
|
+
aWExITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEXMBUGA1UEAwwO
|
|
29
|
+
d3d3Lmdvb2dsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALE4oSql
|
|
30
|
+
eymfHtzOeY86WyvfsjZmaz2XnIo9dzZsK71yMEKkgvXQnnYy9pK0NaYcG0B0hcii
|
|
31
|
+
3fqGBiHMkZY2BOGWwCC/wOmJCzLq9q6caPWUs71Zko+h59LaqV93vzDmZaXYfFoQ
|
|
32
|
+
gSVEWpEpCSo560x0mSuLnJYdQQzZ/L6xvxZ1AgMBAAEwDQYJKoZIhvcNAQEFBQAD
|
|
33
|
+
gYEATyK/RlfpohUVimgFkycTF2hyusjctseXoZDCctgg/STMsL8iA0P9YB6k91GC
|
|
34
|
+
kWpwevuiwarD1MfSUV6goPINFkIBvfK+5R9lpHaTqqs615z8T9R5VJgaLcFe3tWd
|
|
35
|
+
7oq3V2q5Nl6MrZfXj2N07qe6/9zfdauxYO26vAEKCvIkbMo=
|
|
36
|
+
</X509Certificate>
|
|
37
|
+
</X509Data>
|
|
38
|
+
</KeyInfo>
|
|
39
|
+
</Signature></Envelope>
|
|
@@ -2,69 +2,87 @@
|
|
|
2
2
|
|
|
3
3
|
require "spec_helper"
|
|
4
4
|
|
|
5
|
-
RSpec.describe Moxml::Adapter
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
it "uses the AVAILABLE_ADAPTERS constant under MRI" do
|
|
12
|
-
expect(described_class.platform_adapters).to eq(Moxml::Adapter::AVAILABLE_ADAPTERS)
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
RSpec.describe Moxml::Adapter, ".available?" do
|
|
17
|
-
it "returns true for :oga" do
|
|
18
|
-
expect(described_class.available?(:oga)).to be true
|
|
19
|
-
end
|
|
5
|
+
RSpec.describe Moxml::Adapter do
|
|
6
|
+
describe ".platform_adapters" do
|
|
7
|
+
it "includes all known adapters under MRI" do
|
|
8
|
+
expect(described_class.platform_adapters).to include(:nokogiri, :oga,
|
|
9
|
+
:rexml, :ox)
|
|
10
|
+
end
|
|
20
11
|
|
|
21
|
-
|
|
22
|
-
|
|
12
|
+
it "uses the AVAILABLE_ADAPTERS constant under MRI" do
|
|
13
|
+
expect(described_class.platform_adapters).to eq(Moxml::Adapter::AVAILABLE_ADAPTERS)
|
|
14
|
+
end
|
|
23
15
|
end
|
|
24
16
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
.and_return(Moxml::Adapter::OPAL_AVAILABLE_ADAPTERS)
|
|
17
|
+
describe ".available?" do
|
|
18
|
+
it "returns true for :oga" do
|
|
19
|
+
expect(described_class.available?(:oga)).to be true
|
|
29
20
|
end
|
|
30
21
|
|
|
31
|
-
it "returns
|
|
32
|
-
expect(described_class.available?(:nokogiri)).to be
|
|
22
|
+
it "returns true for :nokogiri under MRI" do
|
|
23
|
+
expect(described_class.available?(:nokogiri)).to be true
|
|
33
24
|
end
|
|
34
25
|
|
|
35
|
-
|
|
36
|
-
|
|
26
|
+
context "when Opal platform adapters are in effect" do
|
|
27
|
+
before do
|
|
28
|
+
allow(described_class).to receive(:platform_adapters)
|
|
29
|
+
.and_return(Moxml::Adapter::OPAL_AVAILABLE_ADAPTERS)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "returns false for :nokogiri" do
|
|
33
|
+
expect(described_class.available?(:nokogiri)).to be false
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "returns true for :oga" do
|
|
37
|
+
expect(described_class.available?(:oga)).to be true
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it "returns true for :rexml" do
|
|
41
|
+
expect(described_class.available?(:rexml)).to be true
|
|
42
|
+
end
|
|
37
43
|
end
|
|
38
44
|
end
|
|
39
|
-
end
|
|
40
45
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
describe ".load" do
|
|
47
|
+
context "when Opal platform adapters are in effect" do
|
|
48
|
+
before do
|
|
49
|
+
allow(described_class).to receive(:platform_adapters)
|
|
50
|
+
.and_return(Moxml::Adapter::OPAL_AVAILABLE_ADAPTERS)
|
|
51
|
+
end
|
|
47
52
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
it "raises AdapterError for :nokogiri" do
|
|
54
|
+
expect { described_class.load(:nokogiri) }.to raise_error(
|
|
55
|
+
Moxml::AdapterError, /not available on this platform/
|
|
56
|
+
)
|
|
57
|
+
end
|
|
52
58
|
end
|
|
53
59
|
end
|
|
54
|
-
end
|
|
55
60
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
61
|
+
describe "OPAL_AVAILABLE_ADAPTERS" do
|
|
62
|
+
it "lists :oga as the primary Opal adapter, with :rexml as opt-in" do
|
|
63
|
+
expect(Moxml::Adapter::OPAL_AVAILABLE_ADAPTERS).to eq(%i[oga rexml])
|
|
64
|
+
end
|
|
59
65
|
end
|
|
60
|
-
end
|
|
61
66
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
67
|
+
describe "CONST_NAME_MAP" do
|
|
68
|
+
it "maps :headed_ox to HeadedOx" do
|
|
69
|
+
expect(Moxml::Adapter::CONST_NAME_MAP[:headed_ox]).to eq("HeadedOx")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it "falls back to capitalize for unmapped adapters" do
|
|
73
|
+
expect(Moxml::Adapter::CONST_NAME_MAP[:nokogiri]).to be_nil
|
|
74
|
+
end
|
|
65
75
|
end
|
|
66
76
|
|
|
67
|
-
|
|
68
|
-
|
|
77
|
+
describe "default adapter / available list invariants" do
|
|
78
|
+
it "DEFAULT_ADAPTER is a member of AVAILABLE_ADAPTERS" do
|
|
79
|
+
expect(Moxml::Adapter::AVAILABLE_ADAPTERS)
|
|
80
|
+
.to include(Moxml::Config::DEFAULT_ADAPTER)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "OPAL_DEFAULT_ADAPTER is a member of OPAL_AVAILABLE_ADAPTERS" do
|
|
84
|
+
expect(Moxml::Adapter::OPAL_AVAILABLE_ADAPTERS)
|
|
85
|
+
.to include(Moxml::Config::OPAL_DEFAULT_ADAPTER)
|
|
86
|
+
end
|
|
69
87
|
end
|
|
70
88
|
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require "moxml/c14n"
|
|
5
|
+
|
|
6
|
+
RSpec.describe "Moxml::C14n API" do
|
|
7
|
+
let(:ctx) { Moxml.new(:nokogiri) }
|
|
8
|
+
|
|
9
|
+
describe ".canonicalize (algorithm selector)" do
|
|
10
|
+
it "defaults to inclusive C14N 1.0" do
|
|
11
|
+
doc = ctx.parse("<root xmlns:foo='urn:foo'><foo:a/></root>")
|
|
12
|
+
default_result = Moxml::C14n.canonicalize(doc.root)
|
|
13
|
+
incl10_result = Moxml::C14n.canonicalize(doc.root, algorithm: :inclusive10)
|
|
14
|
+
expect(default_result).to eq(incl10_result)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "raises ArgumentError for unknown algorithm" do
|
|
18
|
+
expect do
|
|
19
|
+
Moxml::C14n.canonicalize("<x/>", algorithm: :bogus)
|
|
20
|
+
end.to raise_error(ArgumentError, /unknown C14N algorithm/)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "accepts inclusive_11 algorithm" do
|
|
24
|
+
doc = ctx.parse("<root/>")
|
|
25
|
+
result = Moxml::C14n.canonicalize(doc.root, algorithm: :inclusive11)
|
|
26
|
+
expect(result).to eq("<root></root>")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "accepts exclusive_10 algorithm" do
|
|
30
|
+
doc = ctx.parse("<root xmlns:foo='urn:foo'><foo:a/></root>")
|
|
31
|
+
result = Moxml::C14n.canonicalize(doc.root, algorithm: :exclusive10)
|
|
32
|
+
# Exclusive renders ns where visibly used (on foo:a), not on root
|
|
33
|
+
expect(result).to include('<foo:a xmlns:foo="urn:foo">')
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
describe ".equivalent?" do
|
|
38
|
+
it "returns true for byte-identical inputs" do
|
|
39
|
+
expect(Moxml::C14n).to be_equivalent("<root>x</root>", "<root>x</root>")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "returns true when only attribute whitespace differs" do
|
|
43
|
+
# Whitespace inside the tag is not significant — both parse identically.
|
|
44
|
+
a = %(<root attr="value"/>)
|
|
45
|
+
b = %(<root attr="value" />)
|
|
46
|
+
expect(Moxml::C14n).to be_equivalent(a, b)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "returns false when text whitespace differs (text IS significant)" do
|
|
50
|
+
a = "<root><child>a</child> </root>"
|
|
51
|
+
b = "<root> <child>a</child></root>"
|
|
52
|
+
expect(Moxml::C14n).not_to be_equivalent(a, b)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "returns true when only attribute order differs" do
|
|
56
|
+
a = %(<root a="1" b="2"/>)
|
|
57
|
+
b = %(<root b="2" a="1"/>)
|
|
58
|
+
expect(Moxml::C14n).to be_equivalent(a, b)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it "returns true when only namespace prefix differs" do
|
|
62
|
+
a = "<root xmlns:foo='urn:foo'><foo:a/></root>"
|
|
63
|
+
b = "<root xmlns:bar='urn:foo'><bar:a/></root>"
|
|
64
|
+
# Inclusive canonical form: prefix is part of qname → not equivalent
|
|
65
|
+
expect(Moxml::C14n).not_to be_equivalent(a, b)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it "returns false for different content" do
|
|
69
|
+
expect(Moxml::C14n).not_to be_equivalent("<root>a</root>", "<root>b</root>")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it "respects algorithm argument" do
|
|
73
|
+
a = "<root xmlns:foo='urn:foo'><foo:a/></root>"
|
|
74
|
+
b = "<root><a xmlns:foo='urn:foo'/></root>"
|
|
75
|
+
# Inclusive: ns on root vs on child → different
|
|
76
|
+
expect(Moxml::C14n).not_to be_equivalent(a, b, algorithm: :inclusive10)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "accepts Moxml::Node inputs" do
|
|
80
|
+
doc1 = ctx.parse("<root/>")
|
|
81
|
+
doc2 = ctx.parse("<root></root>")
|
|
82
|
+
expect(Moxml::C14n).to be_equivalent(doc1.root, doc2.root)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
describe "convenience methods" do
|
|
87
|
+
it ".canonicalize_inclusive10 matches default" do
|
|
88
|
+
doc = ctx.parse("<root/>")
|
|
89
|
+
expect(Moxml::C14n.canonicalize_inclusive10(doc.root))
|
|
90
|
+
.to eq(Moxml::C14n.canonicalize(doc.root))
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it ".canonicalize_inclusive11 produces output" do
|
|
94
|
+
doc = ctx.parse("<root/>")
|
|
95
|
+
expect(Moxml::C14n.canonicalize_inclusive11(doc.root)).to eq("<root></root>")
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it ".canonicalize_exclusive accepts inclusive_namespaces" do
|
|
99
|
+
doc = ctx.parse("<root xmlns:foo='urn:foo'/>")
|
|
100
|
+
result = Moxml::C14n.canonicalize_exclusive(doc.root, inclusive_namespaces: ["foo"])
|
|
101
|
+
# Even though foo is not visibly used, inclusive list forces render
|
|
102
|
+
expect(result).to include('xmlns:foo="urn:foo"')
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
describe "escape helpers (backward compat)" do
|
|
107
|
+
it ".escape_text escapes & < >" do
|
|
108
|
+
expect(Moxml::C14n.escape_text("a & b < c > d"))
|
|
109
|
+
.to eq("a & b < c > d")
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it ".escape_attribute escapes quotes and whitespace" do
|
|
113
|
+
expect(Moxml::C14n.escape_attribute(%(a"b\tc\nd)))
|
|
114
|
+
.to eq(%(a"b	c
d))
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require "moxml/signature"
|
|
5
|
+
|
|
6
|
+
RSpec.describe "Moxml C14N engine" do
|
|
7
|
+
describe Moxml::C14n::Exclusive do
|
|
8
|
+
let(:ctx) { Moxml.new(:nokogiri) }
|
|
9
|
+
let(:canon) { described_class.new }
|
|
10
|
+
|
|
11
|
+
it "renders a simple element without namespaces" do
|
|
12
|
+
doc = ctx.parse("<root>hello</root>")
|
|
13
|
+
expect(canon.canonicalize(doc.root)).to eq("<root>hello</root>")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "preserves nested element structure" do
|
|
17
|
+
doc = ctx.parse("<root><child>text</child></root>")
|
|
18
|
+
expect(canon.canonicalize(doc.root)).to eq("<root><child>text</child></root>")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "renders declared namespaces" do
|
|
22
|
+
doc = ctx.parse(<<~XML.strip)
|
|
23
|
+
<root xmlns:foo="http://example.com/foo"><foo:child>x</foo:child></root>
|
|
24
|
+
XML
|
|
25
|
+
result = canon.canonicalize(doc.root)
|
|
26
|
+
# Exclusive C14N renders the namespace on the element where it's visibly
|
|
27
|
+
# used (foo:child), not on ancestors that don't use it.
|
|
28
|
+
expect(result).to include('<foo:child xmlns:foo="http://example.com/foo">')
|
|
29
|
+
expect(result).to include("</foo:child>")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "excludes ancestor-inherited namespaces when not visibly used (exclusive)" do
|
|
33
|
+
doc = ctx.parse(<<~XML.strip)
|
|
34
|
+
<outer xmlns:unused="http://example.com/unused"><inner>text</inner></outer>
|
|
35
|
+
XML
|
|
36
|
+
inner = doc.at_xpath("//inner")
|
|
37
|
+
result = canon.canonicalize(inner)
|
|
38
|
+
expect(result).to eq("<inner>text</inner>")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it "escapes special characters in text content" do
|
|
42
|
+
doc = ctx.parse("<root>a<b>c&d</root>")
|
|
43
|
+
expect(canon.canonicalize(doc.root)).to eq("<root>a<b>c&d</root>")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it "escapes special characters in attribute values" do
|
|
47
|
+
doc = ctx.parse(%(<root attr="a&b"c"/>))
|
|
48
|
+
expect(canon.canonicalize(doc.root))
|
|
49
|
+
.to include(%(attr="a&b"c"))
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "sorts attributes by namespace URI then local name" do
|
|
53
|
+
doc = ctx.parse(%(<root b="2" a="1" c="3"/>))
|
|
54
|
+
result = canon.canonicalize(doc.root)
|
|
55
|
+
expect(result).to include('a="1" b="2" c="3"')
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it "omits comments by default" do
|
|
59
|
+
doc = ctx.parse("<root>a<!-- comment -->b</root>")
|
|
60
|
+
expect(canon.canonicalize(doc.root)).to eq("<root>ab</root>")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it "includes comments when with_comments is true" do
|
|
64
|
+
doc = ctx.parse("<root>a<!-- comment -->b</root>")
|
|
65
|
+
expect(canon.canonicalize(doc.root, with_comments: true))
|
|
66
|
+
.to eq("<root>a<!-- comment -->b</root>")
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
describe ".escape_text" do
|
|
71
|
+
it "escapes &, <, >" do
|
|
72
|
+
expect(Moxml::C14n.escape_text("a & b < c > d"))
|
|
73
|
+
.to eq("a & b < c > d")
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it "escapes bare CR as 
" do
|
|
77
|
+
expect(Moxml::C14n.escape_text("a\rb"))
|
|
78
|
+
.to eq("a
b")
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
describe ".escape_attribute" do
|
|
83
|
+
it "escapes quotes and tabs/newlines" do
|
|
84
|
+
expect(Moxml::C14n.escape_attribute(%(a"b\tc\nd)))
|
|
85
|
+
.to eq(%(a"b	c
d))
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require "moxml/c14n"
|
|
5
|
+
|
|
6
|
+
# Edge cases for comment and processing-instruction handling per
|
|
7
|
+
# W3C C14N 1.0 §2.6, §2.7.
|
|
8
|
+
RSpec.describe "C14N comments and PIs" do
|
|
9
|
+
let(:ctx) { Moxml.new(:nokogiri) }
|
|
10
|
+
|
|
11
|
+
describe "comments inside elements" do
|
|
12
|
+
it "omits comments by default" do
|
|
13
|
+
doc = ctx.parse("<root>a<!-- c -->b</root>")
|
|
14
|
+
expect(Moxml::C14n.canonicalize(doc.root)).to eq("<root>ab</root>")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "includes comments when with_comments: true" do
|
|
18
|
+
doc = ctx.parse("<root>a<!-- c -->b</root>")
|
|
19
|
+
expect(Moxml::C14n.canonicalize(doc.root, with_comments: true))
|
|
20
|
+
.to eq("<root>a<!-- c -->b</root>")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "preserves comment content verbatim" do
|
|
24
|
+
doc = ctx.parse("<root><!-- & <stuff> --></root>")
|
|
25
|
+
expect(Moxml::C14n.canonicalize(doc.root, with_comments: true))
|
|
26
|
+
.to eq("<root><!-- & <stuff> --></root>")
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
describe "processing instructions" do
|
|
31
|
+
it "always renders PIs (not affected by with_comments)" do
|
|
32
|
+
doc = ctx.parse("<root><?pi data?>x</root>")
|
|
33
|
+
expect(Moxml::C14n.canonicalize(doc.root))
|
|
34
|
+
.to eq("<root><?pi data?>x</root>")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "renders PI without data when data is empty" do
|
|
38
|
+
doc = ctx.parse("<root><?pi?>x</root>")
|
|
39
|
+
expect(Moxml::C14n.canonicalize(doc.root))
|
|
40
|
+
.to eq("<root><?pi?>x</root>")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it "preserves PI target and data verbatim" do
|
|
44
|
+
doc = ctx.parse(%(<root><?xml-stylesheet href="a.css" type="text/css"?>x</root>))
|
|
45
|
+
expect(Moxml::C14n.canonicalize(doc.root))
|
|
46
|
+
.to include(%(<?xml-stylesheet href="a.css" type="text/css"?>))
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
describe "PIs at document level" do
|
|
51
|
+
# TODO.c14n/09: Document-level PIs (outside the document element) are
|
|
52
|
+
# not yet rendered correctly. The moxml PI parser mis-parses document-level
|
|
53
|
+
# PIs, and the DataModel.from_document path drops them. Fixing requires
|
|
54
|
+
# adapter-level work and is tracked separately.
|
|
55
|
+
it "renders PIs outside the document element (KNOWN LIMITATION)" do
|
|
56
|
+
skip "document-level PI rendering not yet implemented"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
describe "comments at document level" do
|
|
61
|
+
it "renders document-level comments when with_comments: true (KNOWN LIMITATION)" do
|
|
62
|
+
skip "document-level comment rendering not yet implemented"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require "moxml/signature"
|
|
5
|
+
|
|
6
|
+
RSpec.describe Moxml::C14n::Inclusive10 do
|
|
7
|
+
let(:ctx) { Moxml.new(:nokogiri) }
|
|
8
|
+
let(:canon) { described_class.new }
|
|
9
|
+
|
|
10
|
+
it "renders a simple element" do
|
|
11
|
+
doc = ctx.parse("<root>hello</root>")
|
|
12
|
+
expect(canon.canonicalize(doc.root)).to eq("<root>hello</root>")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "attracts ancestor namespaces at the apex (the key inclusive behavior)" do
|
|
16
|
+
xml = <<~XML.strip
|
|
17
|
+
<outer xmlns:foo="http://example.com/foo" xmlns:bar="http://example.com/bar">
|
|
18
|
+
<inner x="1"><foo:child>hello</foo:child></inner>
|
|
19
|
+
</outer>
|
|
20
|
+
XML
|
|
21
|
+
doc = ctx.parse(xml)
|
|
22
|
+
inner = doc.at_xpath("//*[local-name()='inner']")
|
|
23
|
+
result = canon.canonicalize(inner)
|
|
24
|
+
# Both inherited namespaces are rendered on <inner>, even though
|
|
25
|
+
# <inner> itself does not visibly use them. This is the inclusive
|
|
26
|
+
# "ancestor attraction" behavior.
|
|
27
|
+
expect(result).to include('xmlns:bar="http://example.com/bar"')
|
|
28
|
+
expect(result).to include('xmlns:foo="http://example.com/foo"')
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "renders each namespace declaration only once per chain" do
|
|
32
|
+
xml = <<~XML.strip
|
|
33
|
+
<root xmlns:a="http://example.com/a">
|
|
34
|
+
<child xmlns:a="http://example.com/a"><grand/></child>
|
|
35
|
+
</root>
|
|
36
|
+
XML
|
|
37
|
+
doc = ctx.parse(xml)
|
|
38
|
+
result = canon.canonicalize(doc.root)
|
|
39
|
+
expect(result.scan('xmlns:a="http://example.com/a"').count).to eq(1)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "re-renders a namespace when re-declared with a different URI" do
|
|
43
|
+
xml = <<~XML.strip
|
|
44
|
+
<root xmlns:a="http://example.com/a">
|
|
45
|
+
<child xmlns:a="http://example.com/other"><grand/></child>
|
|
46
|
+
</root>
|
|
47
|
+
XML
|
|
48
|
+
doc = ctx.parse(xml)
|
|
49
|
+
result = canon.canonicalize(doc.root)
|
|
50
|
+
expect(result.scan("xmlns:a=").count).to eq(2)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it "renders the default namespace at apex when inherited" do
|
|
54
|
+
xml = '<outer xmlns="http://example.com"><inner/></outer>'
|
|
55
|
+
doc = ctx.parse(xml)
|
|
56
|
+
inner = doc.at_xpath("//*[local-name()='inner']")
|
|
57
|
+
result = canon.canonicalize(inner)
|
|
58
|
+
expect(result).to include('xmlns="http://example.com"')
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require "moxml/c14n"
|
|
5
|
+
|
|
6
|
+
# Edge cases for namespace rendering per W3C C14N 1.0 §2.3 / 1.1 §2.3.
|
|
7
|
+
# These cover the bugs that historically plague C14N implementations.
|
|
8
|
+
RSpec.describe "C14N namespace rendering" do
|
|
9
|
+
let(:ctx) { Moxml.new(:nokogiri) }
|
|
10
|
+
|
|
11
|
+
describe "default namespace" do
|
|
12
|
+
it "renders xmlns= declaration when default ns is set" do
|
|
13
|
+
doc = ctx.parse(%(<root xmlns="urn:foo"/>))
|
|
14
|
+
result = Moxml::C14n.canonicalize(doc.root)
|
|
15
|
+
expect(result).to eq('<root xmlns="urn:foo"></root>')
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "renders xmlns='' when transitioning to empty default" do
|
|
19
|
+
doc = ctx.parse(%(<a xmlns="urn:foo"><b xmlns=""><c/></b></a>))
|
|
20
|
+
result = Moxml::C14n.canonicalize(doc.root)
|
|
21
|
+
# <b> has empty default; <a> has urn:foo → xmlns="" appears
|
|
22
|
+
expect(result).to include('xmlns=""')
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "does not render xmlns when default ns is empty everywhere" do
|
|
26
|
+
doc = ctx.parse(%(<root><child/></root>))
|
|
27
|
+
result = Moxml::C14n.canonicalize(doc.root)
|
|
28
|
+
expect(result).to eq("<root><child></child></root>")
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
describe "prefix redeclaration" do
|
|
33
|
+
it "renders both declarations when URI changes" do
|
|
34
|
+
doc = ctx.parse(%(<a xmlns:x="urn:1"><b xmlns:x="urn:2"/></a>))
|
|
35
|
+
result = Moxml::C14n.canonicalize(doc.root)
|
|
36
|
+
expect(result.scan("xmlns:x=").length).to eq(2)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "renders only once when URI is unchanged" do
|
|
40
|
+
doc = ctx.parse(%(<a xmlns:x="urn:1"><b xmlns:x="urn:1"/></a>))
|
|
41
|
+
result = Moxml::C14n.canonicalize(doc.root)
|
|
42
|
+
expect(result.scan("xmlns:x=").length).to eq(1)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
describe "xml namespace" do
|
|
47
|
+
it "does not render xmlns:xml declaration" do
|
|
48
|
+
doc = ctx.parse(%(<root xmlns:xml="http://www.w3.org/XML/1998/namespace"/>))
|
|
49
|
+
result = Moxml::C14n.canonicalize(doc.root)
|
|
50
|
+
expect(result).not_to include("xmlns:xml=")
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
describe "namespace sorting" do
|
|
55
|
+
it "sorts xmlns declarations by prefix" do
|
|
56
|
+
doc = ctx.parse(%(<root xmlns:z="urn:z" xmlns:a="urn:a"/>))
|
|
57
|
+
result = Moxml::C14n.canonicalize(doc.root)
|
|
58
|
+
# a should appear before z
|
|
59
|
+
expect(result.index("xmlns:a=")).to be < result.index("xmlns:z=")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it "renders default namespace first" do
|
|
63
|
+
doc = ctx.parse(%(<root xmlns="urn:default" xmlns:a="urn:a"/>))
|
|
64
|
+
result = Moxml::C14n.canonicalize(doc.root)
|
|
65
|
+
# Default (xmlns=) before prefixed
|
|
66
|
+
expect(result.index("xmlns=")).to be < result.index("xmlns:a=")
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
describe "deeply nested inheritance" do
|
|
71
|
+
it "renders each prefix at the level it is first declared" do
|
|
72
|
+
xml = <<~XML.strip
|
|
73
|
+
<root xmlns:a="urn:a">
|
|
74
|
+
<level1 xmlns:b="urn:b">
|
|
75
|
+
<level2 xmlns:c="urn:c">
|
|
76
|
+
<apex/>
|
|
77
|
+
</level2>
|
|
78
|
+
</level1>
|
|
79
|
+
</root>
|
|
80
|
+
XML
|
|
81
|
+
doc = ctx.parse(xml)
|
|
82
|
+
result = Moxml::C14n.canonicalize(doc.root)
|
|
83
|
+
# Inclusive: all in-scope ns render on apex. But we're canonicalizing
|
|
84
|
+
# the whole tree, so each ns renders at its declaration point.
|
|
85
|
+
expect(result.scan("xmlns:").length).to eq(3)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|