Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ This will help prevent future XML signature wrapping attacks.
- RSA-SHA256 <http://www.w3.org/2001/04/xmldsig-more#rsa-sha256>
- RSA-SHA256 with MGF1 <http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1>
- RSA-SHA512 <http://www.w3.org/2001/04/xmldsig-more#rsa-sha512>
- ECDSA-SHA256 <http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256>
- ECDSA-SHA512 <http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512>

HMAC-SHA1 is also available but it is disabled by default

Expand Down
16 changes: 12 additions & 4 deletions example/example.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,17 @@ const dom = require("@xmldom/xmldom").DOMParser;
const SignedXml = require("xml-crypto").SignedXml;
const fs = require("fs");

function signXml(xml, xpath, key, dest) {
function signXml(xml, xpath, key, dest, cert) {
const sig = new SignedXml();
sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"
sig.signatureAlgorithm = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
sig.privateKey = fs.readFileSync(key);
sig.addReference(xpath);
sig.publicCert = fs.readFileSync(cert); // To populate KeyInfo, as an example
sig.addReference({
xpath,
digestAlgorithm: "http://www.w3.org/2001/04/xmlenc#sha256",
transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"],
});
sig.computeSignature(xml);
fs.writeFileSync(dest, sig.getSignedXml());
}
Expand All @@ -20,7 +27,8 @@ function validateXml(xml, key) {
doc,
)[0];
const sig = new SignedXml();
sig.publicCert = key;
sig.publicCert = fs.readFileSync(key); // Note since the XML has a KeyInfo, this cert is NOT doing anything!
// Validate the cert in `KeyInfo` on your own if that is your security model. See: <https://github.com/node-saml/xml-crypto/discussions/399>
sig.loadSignature(signature.toString());
const res = sig.checkSignature(xml);
if (!res) {
Expand All @@ -32,7 +40,7 @@ function validateXml(xml, key) {
const xml = "<library>" + "<book>" + "<name>Harry Potter</name>" + "</book>" + "</library>";

//sign an xml document
signXml(xml, "//*[local-name(.)='book']", "client.pem", "result.xml");
signXml(xml, "//*[local-name(.)='book']", "client.pem", "result.xml", "client_public.pem");

console.log("xml signed successfully");

Expand Down
56 changes: 56 additions & 0 deletions example/local_example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/* eslint-disable no-console */
// Run with `npm run example`, requires one-time `npm run build` to generate `/lib` code (and re-run if you update `/src`)

const select = require("xpath").select
const dom = require("@xmldom/xmldom").DOMParser;
const SignedXml = require("../").SignedXml;
const fs = require("fs");
Comment on lines +4 to +7

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Use package name import instead of relative path.

The import on line 6 uses a relative path (require("../")) rather than the package name. Example files should use require("xml-crypto") to demonstrate usage from an end-user's perspective, matching the pattern in example/example.js.

📦 Proposed fix
 const select = require("xpath").select
 const dom = require("@xmldom/xmldom").DOMParser;
-const SignedXml = require("../").SignedXml;
+const SignedXml = require("xml-crypto").SignedXml;
 const fs = require("fs");

Based on learnings: "Example files in the node-saml/xml-crypto repository should use require("xml-crypto") (the package name) rather than relative paths to build artifacts, since they demonstrate usage from an end-user's perspective."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const select = require("xpath").select
const dom = require("@xmldom/xmldom").DOMParser;
const SignedXml = require("../").SignedXml;
const fs = require("fs");
const select = require("xpath").select
const dom = require("@xmldom/xmldom").DOMParser;
const SignedXml = require("xml-crypto").SignedXml;
const fs = require("fs");
🤖 Prompt for AI Agents
In `@example/local_example.js` around lines 4 - 7, Replace the relative
require("../") that imports SignedXml with the package name
require("xml-crypto") so the example demonstrates end-user usage; locate the
SignedXml import in local_example.js (currently assigned to the SignedXml
variable) and update that require to "xml-crypto" while leaving other requires
(select, DOMParser, fs) unchanged.


function signXml(xml, xpath, key, dest, cert) {
const sig = new SignedXml();
sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"
sig.signatureAlgorithm = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
sig.privateKey = fs.readFileSync(__dirname + "/" + key);
sig.publicCert = fs.readFileSync(__dirname + "/" + cert); // To populate KeyInfo, as an example
sig.addReference({
xpath,
digestAlgorithm: "http://www.w3.org/2001/04/xmlenc#sha256",
transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"],
});
sig.computeSignature(xml);
fs.writeFileSync(__dirname + "/" + dest, sig.getSignedXml());
}

function validateXml(xml, key) {
const doc = new dom().parseFromString(xml);
const signature = select(
"/*/*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']",
doc,
)[0];
const sig = new SignedXml();
sig.publicCert = fs.readFileSync(__dirname + "/" + key); // Note since the XML has a KeyInfo, this cert is NOT doing anything!
// Validate the cert in `KeyInfo` on your own if that is your security model. See: <https://github.com/node-saml/xml-crypto/discussions/399>
sig.loadSignature(signature.toString());
const res = sig.checkSignature(xml);
if (!res) {
console.log(sig.validationErrors);
}
return res;
}

const xml = "<library>" + "<book>" + "<name>Harry Potter</name>" + "</book>" + "</library>";

//sign an xml document
signXml(xml, "//*[local-name(.)='book']", "client.pem", "result.xml", "client_public.pem");

console.log("xml signed successfully");

const signedXml = fs.readFileSync(__dirname + "/" + "result.xml").toString();
console.log("validating signature...");

//validate an xml document
if (validateXml(signedXml, "client_public.pem")) {
console.log("signature is valid");
} else {
console.log("signature not valid");
}
1 change: 1 addition & 0 deletions example/result.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<library><book Id="_0"><name>Harry Potter</name></book><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/><Reference URI="#_0"><Transforms><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></Transforms><DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><DigestValue>9d/ciWlVZkaJnJ3KBB5WY1H2Y8WRXPB2DquM0goT8jY=</DigestValue></Reference></SignedInfo><SignatureValue>uxmxGw2O3B6ylkhEXOaZd1Iupgy3sHtCoBTgbmSMSnHYOitiHXRdHjJGJdMG4EMSgItsB6k5gxrKeyQ/5LkwvMqSc0VRPXd9vavt0pYatqwWDO/r6WITLb0jzymJfNDJ4lr4OcqH4zBKX8Deb6EpS9L7S6OXNqd1vOZ0STMSSaM=</SignatureValue><KeyInfo><X509Data><X509Certificate>MIIBxDCCAW6gAwIBAgIQxUSXFzWJYYtOZnmmuOMKkjANBgkqhkiG9w0BAQQFADAWMRQwEgYDVQQDEwtSb290IEFnZW5jeTAeFw0wMzA3MDgxODQ3NTlaFw0zOTEyMzEyMzU5NTlaMB8xHTAbBgNVBAMTFFdTRTJRdWlja1N0YXJ0Q2xpZW50MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+L6aB9x928noY4+0QBsXnxkQE4quJl7c3PUPdVu7k9A02hRG481XIfWhrDY5i7OEB7KGW7qFJotLLeMec/UkKUwCgv3VvJrs2nE9xO3SSWIdNzADukYh+Cxt+FUU6tUkDeqg7dqwivOXhuOTRyOI3HqbWTbumaLdc8jufz2LhaQIDAQABo0swSTBHBgNVHQEEQDA+gBAS5AktBh0dTwCNYSHcFmRjoRgwFjEUMBIGA1UEAxMLUm9vdCBBZ2VuY3mCEAY3bACqAGSKEc+41KpcNfQwDQYJKoZIhvcNAQEEBQADQQAfIbnMPVYkNNfX1tG1F+qfLhHwJdfDUZuPyRPucWF5qkh6sSdWVBY5sT/txBnVJGziyO8DPYdu2fPMER8ajJfl</X509Certificate></X509Data></KeyInfo></Signature></library>
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
"prettier-format": "prettier --config .prettierrc.json --write .",
"prerelease": "git clean -xfd && npm ci && npm test",
"release": "release-it",
"test": "nyc mocha"
"test": "nyc mocha",
"example": "node ./example/local_example.js"
},
"dependencies": {
"@xmldom/is-dom-node": "^1.0.1",
Expand Down
60 changes: 60 additions & 0 deletions src/signature-algorithms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,36 @@ export class RsaSha256 implements SignatureAlgorithm {
};
}

export class EcdsaSha256 implements SignatureAlgorithm {
getSignature = createOptionalCallbackFunction(
(signedInfo: crypto.BinaryLike, privateKey: crypto.KeyLike): string => {
// Maybe the fix for ts-ignore below?
// const parsedPrivateKey = crypto.createPrivateKey(privateKey);
const signer = crypto.createSign("SHA256");
signer.update(signedInfo);
// @ts-ignore
const res = signer.sign({ key: privateKey, dsaEncoding: 'ieee-p1363' }, "base64");

return res;
},
);

verifySignature = createOptionalCallbackFunction(
(material: string, key: crypto.KeyLike, signatureValue: string): boolean => {
const publicKey = crypto.createPublicKey(key);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

potential for key confusion attacks i.e. rsa public key being passed here

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had looked into this, but the crypto functions seem to be validating this already. Do you mind demonstrating a key confusion attack?

@ahacker1-securesaml ahacker1-securesaml Feb 16, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's best practice to add checks (for this and the other signature-algorithms) that the verification key is the correct type (ec for this one). Currently there's no security vulnerability. If a rsa public key is passed in here, node:crypto will just verify with RSA (the key isn't converted to EC).

const verifier = crypto.createVerify("SHA256");
verifier.update(material);
const res = verifier.verify({ key: publicKey, dsaEncoding: 'ieee-p1363' }, signatureValue, "base64");

return res;
},
);

getAlgorithmName = () => {
return "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256";
};
}

export class RsaSha256Mgf1 implements SignatureAlgorithm {
getSignature = createOptionalCallbackFunction(
(signedInfo: crypto.BinaryLike, privateKey: crypto.KeyLike): string => {
Expand Down Expand Up @@ -126,6 +156,36 @@ export class RsaSha512 implements SignatureAlgorithm {
};
}

export class EcdsaSha512 implements SignatureAlgorithm {
getSignature = createOptionalCallbackFunction(
(signedInfo: crypto.BinaryLike, privateKey: crypto.KeyLike): string => {
// Maybe the fix for ts-ignore below?
// const parsedPrivateKey = crypto.createPrivateKey(privateKey);
const signer = crypto.createSign("SHA512");
signer.update(signedInfo);
// @ts-ignore
const res = signer.sign({ key: privateKey, dsaEncoding: 'ieee-p1363' }, "base64");

return res;
},
);

verifySignature = createOptionalCallbackFunction(
(material: string, key: crypto.KeyLike, signatureValue: string): boolean => {
const publicKey = crypto.createPublicKey(key);
const verifier = crypto.createVerify("SHA512");
verifier.update(material);
const res = verifier.verify({ key: publicKey, dsaEncoding: 'ieee-p1363' }, signatureValue, "base64");

return res;
},
);

getAlgorithmName = () => {
return "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512";
};
}

export class HmacSha1 implements SignatureAlgorithm {
getSignature = createOptionalCallbackFunction(
(signedInfo: crypto.BinaryLike, privateKey: crypto.KeyLike): string => {
Expand Down
2 changes: 2 additions & 0 deletions src/signed-xml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,10 @@ export class SignedXml {
SignatureAlgorithms: Record<SignatureAlgorithmType, new () => SignatureAlgorithm> = {
"http://www.w3.org/2000/09/xmldsig#rsa-sha1": signatureAlgorithms.RsaSha1,
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256": signatureAlgorithms.RsaSha256,
"http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256": signatureAlgorithms.EcdsaSha256,
"http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1": signatureAlgorithms.RsaSha256Mgf1,
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha512": signatureAlgorithms.RsaSha512,
"http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512": signatureAlgorithms.EcdsaSha512,
// Disabled by default due to key confusion concerns.
// 'http://www.w3.org/2000/09/xmldsig#hmac-sha1': SignatureAlgorithms.HmacSha1
};
Expand Down
2 changes: 2 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ export type HashAlgorithmType =
export type SignatureAlgorithmType =
| "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
| "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
| "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"
| "http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1"
| "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"
| "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512"
| "http://www.w3.org/2000/09/xmldsig#hmac-sha1"
| string;

Expand Down
153 changes: 153 additions & 0 deletions test/ecdsa-signatures.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import * as crypto from "crypto";
import * as fs from "fs";
import * as xmldom from "@xmldom/xmldom";
import * as xpath from "xpath";
import * as isDomNode from "@xmldom/is-dom-node";
import { expect } from "chai";
import { SignedXml } from "../src/index";

const signatureNamespace = "http://www.w3.org/2000/09/xmldsig#";
const canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#";
const digestAlgorithm = "http://www.w3.org/2001/04/xmlenc#sha256";
const payload = '<payload Id="payload">attacker-controlled</payload>';

function createSignedXml(signatureAlgorithm: string, sign: (signedInfo: string) => string): string {
const digestValue = crypto.createHash("sha256").update(payload).digest("base64");
const signedInfo =
`<SignedInfo xmlns="${signatureNamespace}">` +
`<CanonicalizationMethod Algorithm="${canonicalizationAlgorithm}"></CanonicalizationMethod>` +
`<SignatureMethod Algorithm="${signatureAlgorithm}"></SignatureMethod>` +
'<Reference URI="#payload"><Transforms>' +
`<Transform Algorithm="${canonicalizationAlgorithm}"></Transform></Transforms>` +
`<DigestMethod Algorithm="${digestAlgorithm}"></DigestMethod>` +
`<DigestValue>${digestValue}</DigestValue></Reference></SignedInfo>`;

return (
`<root>${payload}<Signature xmlns="${signatureNamespace}">${signedInfo}` +
`<SignatureValue>${sign(signedInfo)}</SignatureValue></Signature></root>`
);
}

function loadSignature(xml: string, publicCert: crypto.KeyLike): SignedXml {
const doc = new xmldom.DOMParser().parseFromString(xml);
const signature = xpath.select1(
`//*[local-name(.)='Signature' and namespace-uri(.)='${signatureNamespace}']`,
doc,
);
isDomNode.assertIsNodeLike(signature);
const verifier = new SignedXml({ publicCert });
verifier.loadSignature(signature);
return verifier;
}

function signWith(signatureAlgorithm: string): string {
const signer = new SignedXml({
privateKey: fs.readFileSync("./test/static/client_ecdsa.pem"),
signatureAlgorithm,
canonicalizationAlgorithm,
});
signer.addReference({
xpath: "//*[local-name(.)='x']",
digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1",
transforms: [canonicalizationAlgorithm],
});
signer.computeSignature('<root><x attr="value"></x></root>');
return signer.getSignedXml();
}

describe("ECDSA signatures", function () {
it("verifies the external ECDSA signature fixture", function () {
const xml = fs.readFileSync("./test/static/valid_signature_ecdsa.xml", "utf8");
const verifier = loadSignature(xml, fs.readFileSync("./test/static/ecdsa_external.pem"));

expect(verifier.checkSignature(xml)).to.be.true;
expect(verifier.getSignedReferences()).to.have.length(1);
});

for (const hash of ["sha256", "sha512"]) {
const signatureAlgorithm = `http://www.w3.org/2001/04/xmldsig-more#ecdsa-${hash}`;

describe(signatureAlgorithm, function () {
it("verifies a document signed by the library", function () {
const xml = signWith(signatureAlgorithm);
const verifier = loadSignature(
xml,
fs.readFileSync("./test/static/client_public_ecdsa.pem"),
);

expect(verifier.checkSignature(xml)).to.be.true;
});

it("rejects a signed document after its referenced content is modified", function () {
const xml = signWith(signatureAlgorithm);
const doc = new xmldom.DOMParser().parseFromString(xml);
const node = xpath.select1("//*[local-name(.)='x']", doc);
isDomNode.assertIsElementNode(node);
node.setAttribute("attr", "manipulatedValue");
const manipulatedXml = new xmldom.XMLSerializer().serializeToString(doc);
const verifier = loadSignature(
manipulatedXml,
fs.readFileSync("./test/static/client_public_ecdsa.pem"),
);

expect(verifier.checkSignature(manipulatedXml)).to.be.false;
});

it("does not expose signed references after rejecting a malformed signature", function () {
const xml = createSignedXml(signatureAlgorithm, () => "AA==");
const verifier = loadSignature(
xml,
fs.readFileSync("./test/static/client_public_ecdsa.pem"),
);

expect(() => verifier.checkSignature(xml)).to.throw();
expect(verifier.getSignedReferences()).to.deep.equal([]);
});

// XMLDSig 1.1 §6.4.3: https://www.w3.org/TR/xmldsig-core1/#sec-ECDSA
it("rejects an RSA private key instead of emitting an ECDSA-labeled RSA signature", function () {
const signer = new SignedXml({
privateKey: fs.readFileSync("./test/static/client.pem"),
signatureAlgorithm,
canonicalizationAlgorithm,
});
signer.addReference({
xpath: "//*[@Id='payload']",
digestAlgorithm,
transforms: [canonicalizationAlgorithm],
});

expect(() => signer.computeSignature(`<root>${payload}</root>`)).to.throw();
});

it("rejects an RSA signature whose SignatureMethod declares ECDSA", function () {
const privateKey = fs.readFileSync("./test/static/client.pem");
const publicCert = fs.readFileSync("./test/static/client_public.pem");
const sign = (signedInfo: string) =>
crypto.sign(hash, Buffer.from(signedInfo), privateKey).toString("base64");
const rsaXml = createSignedXml(`http://www.w3.org/2001/04/xmldsig-more#rsa-${hash}`, sign);
expect(loadSignature(rsaXml, publicCert).checkSignature(rsaXml)).to.be.true;

const xml = createSignedXml(signatureAlgorithm, sign);
const verifier = loadSignature(xml, publicCert);

expect(() => verifier.checkSignature(xml)).to.throw();
});

it("verifies an externally signed document using a public KeyObject", function () {
const privateKey = fs.readFileSync("./test/static/client_ecdsa.pem");
const publicCert = fs.readFileSync("./test/static/client_public_ecdsa.pem");
const xml = createSignedXml(signatureAlgorithm, (signedInfo) =>
crypto
.sign(hash, Buffer.from(signedInfo), { key: privateKey, dsaEncoding: "ieee-p1363" })
.toString("base64"),
);
expect(loadSignature(xml, publicCert).checkSignature(xml)).to.be.true;
const verifier = loadSignature(xml, crypto.createPublicKey(publicCert));

expect(verifier.checkSignature(xml)).to.be.true;
expect(verifier.getSignedReferences()).to.deep.equal([payload]);
});
});
}
});
5 changes: 5 additions & 0 deletions test/static/client_ecdsa.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIOg1FiE/iu8uoRXX3UvBs53JIEsjkcf9IbMpJsfkvG30oAoGCCqGSM49
AwEHoUQDQgAE69ImJGeiClnYW20zXK3L+w5q463+PN302fpmEDE/6xTEbG/KIxcA
d77nrzo5Iq4ve2SqL0Bk1Yxk2V/1f8t52g==
-----END EC PRIVATE KEY-----
13 changes: 13 additions & 0 deletions test/static/client_public_ecdsa.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
-----BEGIN CERTIFICATE-----
MIIB3zCCAYWgAwIBAgIUYjTFVRq9oJ9JsEdzs9GEp+Ro2nYwCgYIKoZIzj0EAwIw
RTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu
dGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNjAxMjcyMDA0MjhaFw0yNzAxMjIy
MDA0MjhaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYD
VQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwWTATBgcqhkjOPQIBBggqhkjO
PQMBBwNCAATr0iYkZ6IKWdhbbTNcrcv7Dmrjrf483fTZ+mYQMT/rFMRsb8ojFwB3
vuevOjkiri97ZKovQGTVjGTZX/V/y3nao1MwUTAdBgNVHQ4EFgQUKdQQ4ogzLU06
Gypz35quxaLJr50wHwYDVR0jBBgwFoAUKdQQ4ogzLU06Gypz35quxaLJr50wDwYD
VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNIADBFAiADI3VXNdnYMIIFlVLS6Ss2
E+tamOigyNvruaKT+0YiGQIhALYU9Dyu2fRRvULX7sBpv7Dxk/4ynUCcCTJ1L9SK
O9bJ
-----END CERTIFICATE-----
Loading