Showing preview only (564K chars total). Download the full file or copy to clipboard to get everything.
Repository: yaronn/xml-crypto
Branch: master
Commit: b673581ba070
Files: 118
Total size: 528.7 KB
Directory structure:
gitextract_8drkv3mt/
├── .eslintignore
├── .eslintrc.json
├── .gitattributes
├── .github/
│ ├── dependabot.yml
│ └── workflows/
│ ├── ci.yml
│ └── codeql-analysis.yml
├── .gitignore
├── .grenrc.js
├── .markdownlint.json
├── .mocharc.json
├── .npmignore
├── .nycrc.json
├── .prettierignore
├── .prettierrc.json
├── .release-it.json
├── .vscode/
│ └── settings.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── example/
│ ├── client.pem
│ ├── client_public.pem
│ └── example.js
├── package.json
├── src/
│ ├── c14n-canonicalization.ts
│ ├── enveloped-signature.ts
│ ├── exclusive-canonicalization.ts
│ ├── hash-algorithms.ts
│ ├── index.ts
│ ├── signature-algorithms.ts
│ ├── signed-xml.ts
│ ├── types.ts
│ └── utils.ts
├── test/
│ ├── c14n-non-exclusive-unit-tests.spec.ts
│ ├── c14nWithComments-unit-tests.spec.ts
│ ├── canonicalization-unit-tests.spec.ts
│ ├── document-tests.spec.ts
│ ├── hmac-tests.spec.ts
│ ├── key-info-tests.spec.ts
│ ├── saml-response-tests.spec.ts
│ ├── signature-integration-tests.spec.ts
│ ├── signature-object-tests.spec.ts
│ ├── signature-unit-tests.spec.ts
│ ├── static/
│ │ ├── client.pem
│ │ ├── client_bundle.pem
│ │ ├── client_public.der
│ │ ├── client_public.pem
│ │ ├── empty_uri.pem
│ │ ├── feide_public.pem
│ │ ├── hmac-foobar.key
│ │ ├── hmac.key
│ │ ├── hmac_signature.xml
│ │ ├── id_with_quotes.xml
│ │ ├── idp_certificate.pem
│ │ ├── idp_private_key.pem
│ │ ├── integration/
│ │ │ ├── expectedVerify.xml
│ │ │ └── expectedVerifyComplex.xml
│ │ ├── invalid_saml_no_signed_info.xml
│ │ ├── invalid_saml_sha256_rsa_mgf1.xml
│ │ ├── invalid_signature - changed content.xml
│ │ ├── invalid_signature - hash.xml
│ │ ├── invalid_signature - non existing reference.xml
│ │ ├── invalid_signature - signature value.xml
│ │ ├── invalid_signature - wsu - changed content.xml
│ │ ├── invalid_signature - wsu - hash.xml
│ │ ├── invalid_signature - wsu - invalid signature value.xml
│ │ ├── invalid_signature - wsu - non existing reference.xml
│ │ ├── invalid_signature_without_transforms_element.xml
│ │ ├── keyinfo - pretty-printed.xml
│ │ ├── keyinfo.pem
│ │ ├── saml_external_ns.pem
│ │ ├── saml_external_ns.xml
│ │ ├── saml_multiple_signed_info_nodes.xml
│ │ ├── saml_wrapped_signed_info_node.xml
│ │ ├── signature_with_inclusivenamespaces.pem
│ │ ├── signature_with_inclusivenamespaces.xml
│ │ ├── signature_with_inclusivenamespaces_lines.xml
│ │ ├── signature_with_inclusivenamespaces_lines_windows.xml
│ │ ├── unsigned_saml_response.xml
│ │ ├── valid_saml.xml
│ │ ├── valid_saml_sha256_rsa_mgf1.xml
│ │ ├── valid_saml_signature_wrapping.xml
│ │ ├── valid_saml_with_digest_comment.xml
│ │ ├── valid_saml_withcomments.xml
│ │ ├── valid_signature wsu.xml
│ │ ├── valid_signature.xml
│ │ ├── valid_signature_utf8.xml
│ │ ├── valid_signature_with_lowercase_id_attribute.xml
│ │ ├── valid_signature_with_reference_keyInfo.xml
│ │ ├── valid_signature_with_root_level_sig_namespace.xml
│ │ ├── valid_signature_with_unused_prefixes.xml
│ │ ├── valid_signature_with_whitespace_in_digestvalue.xml
│ │ ├── valid_signature_without_transforms_element.xml
│ │ ├── windows_store_certificate.pem
│ │ ├── windows_store_signature.xml
│ │ ├── wsfederation_metadata.pem
│ │ └── wsfederation_metadata.xml
│ ├── utils-tests.spec.ts
│ ├── validators/
│ │ ├── XmlCryptoJava/
│ │ │ ├── pom.xml
│ │ │ └── src/
│ │ │ └── test/
│ │ │ ├── java/
│ │ │ │ └── org/
│ │ │ │ └── nodejs/
│ │ │ │ └── xmlcrypto/
│ │ │ │ └── HMACTest.java
│ │ │ └── resources/
│ │ │ └── log4j.xml
│ │ └── XmlCryptoUtilities/
│ │ ├── XmlCryptoUtilities/
│ │ │ ├── Program.cs
│ │ │ ├── Properties/
│ │ │ │ └── AssemblyInfo.cs
│ │ │ ├── ValidateSignature.csproj
│ │ │ ├── bin/
│ │ │ │ └── Debug/
│ │ │ │ ├── ClientPrivate.pfx
│ │ │ │ ├── Example.xml
│ │ │ │ ├── XmlCryptoUtilities.pdb
│ │ │ │ ├── XmlCryptoUtilities.vshost.exe.manifest
│ │ │ │ └── signedExample.xml
│ │ │ ├── obj/
│ │ │ │ └── x86/
│ │ │ │ └── Debug/
│ │ │ │ ├── DesignTimeResolveAssemblyReferencesInput.cache
│ │ │ │ ├── ValidateSignature.csproj.FileListAbsolute.txt
│ │ │ │ └── XmlCryptoUtilities.pdb
│ │ │ ├── program-repro-misc-validation-and-canon.cs
│ │ │ └── utilities.cs
│ │ ├── XmlCryptoUtilities.sln
│ │ └── XmlCryptoUtilities.suo
│ └── wsfed-metadata-tests.spec.ts
├── tsconfig.eslint.json
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .eslintignore
================================================
================================================
FILE: .eslintrc.json
================================================
{
"env": {
"browser": false,
"node": true,
"mocha": true,
"es2020": true
},
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint", "deprecation"],
"parserOptions": {
"project": "./tsconfig.eslint.json",
"ext": ".ts"
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"rules": {
"no-console": "error",
"no-prototype-builtins": "error",
"one-var": ["error", "never"],
"no-duplicate-imports": "error",
"no-use-before-define": "error",
"curly": "error",
"eqeqeq": ["error", "smart"],
"no-var": "error",
"prefer-const": "error",
"prefer-template": "error",
"deprecation/deprecation": "error",
"@typescript-eslint/no-non-null-assertion": "error",
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/no-this-alias": "error"
}
}
================================================
FILE: .gitattributes
================================================
# Git to autodetect text files and normalise their line endings to LF when they are checked into your repository.
* text=auto
# git won't try to convert this files
*.pem -text
*.der -text
test/static/**/*.xml -text
# These files are text and should be normalized (Convert crlf => lf)
*.gitattributes text
.gitignore text
================================================
FILE: .github/dependabot.yml
================================================
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "npm" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "monthly"
# Always increase the version requirement
# to match the new version.
versioning-strategy: increase
- package-ecosystem: "github-actions" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "monthly"
================================================
FILE: .github/workflows/ci.yml
================================================
name: Test Status
on:
workflow_dispatch:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
test:
name: Test Code
env:
CI: true
strategy:
matrix:
os: [ubuntu-latest]
node-version: [16, 18, 20]
experimental: [false]
include:
- os: ubuntu-latest
node-version: latest
experimental: true
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.experimental }}
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }} on ${{ matrix.os }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm test
- run: npm update
- run: npm ci
- run: npm test
- name: Codecov
uses: codecov/codecov-action@v3.1.4
with:
verbose: true
lint:
name: Lint Code
env:
CI: true
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "lts/*"
- run: |
npm ci
npm run lint
================================================
FILE: .github/workflows/codeql-analysis.yml
================================================
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [master]
pull_request:
# The branches below must be a subset of the branches above
branches: [master]
schedule:
- cron: "21 12 * * 1"
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: ["javascript"]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v3
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
================================================
FILE: .gitignore
================================================
lib/
node_modules/
p.js
p.txt
*.svclog
.DS_Store
*.*~
*.swp
npm-debug.log
/test/validators/XmlCryptoJava/target/
.eslintcache
.nyc_output/
coverage/
.idea
================================================
FILE: .grenrc.js
================================================
module.exports = {
dataSource: "prs",
prefix: "",
onlyMilestones: false,
ignoreTagsWith: [],
ignoreLabels: [],
tags: "all",
groupBy: {
"Major Changes": ["semver-major", "breaking-change"],
"Minor Changes": ["semver-minor", "enhancement", "new-feature"],
Dependencies: ["dependencies"],
"Bug Fixes": ["semver-patch", "bug", "security"],
Documentation: ["documentation"],
"Technical Tasks": ["chore"],
Other: ["..."],
},
changelogFilename: "CHANGELOG.md",
username: "node-saml",
repo: "xml-crypto",
template: {
issue: function (placeholders) {
const parts = [
"-",
placeholders.labels,
placeholders.name,
`[${placeholders.text}](${placeholders.url})`,
];
return parts
.filter((_) => _)
.join(" ")
.replace(" ", " ");
},
release: function (placeholders) {
placeholders.body = placeholders.body.replace(
"*No changelog for this release.*",
"\n_No changelog for this release._",
);
return `## ${placeholders.release} (${placeholders.date})\n${placeholders.body}`;
},
group: function (placeholders) {
const iconMap = {
Enhancements: "🚀",
"Minor Changes": "🚀",
"Bug Fixes": "🐛",
Documentation: "📚",
"Technical Tasks": "⚙️",
"Major Changes": "💣",
Dependencies: "🔗",
};
const icon = iconMap[placeholders.heading] || "🙈";
return "\n### " + icon + " " + placeholders.heading + "\n";
},
},
};
================================================
FILE: .markdownlint.json
================================================
{
"MD013": false,
"MD024": false
}
================================================
FILE: .mocharc.json
================================================
{
"diff": true,
"extension": "spec.ts",
"package": "./package.json",
"recursive": true,
"reporter": "spec",
"require": ["choma", "ts-node/register"],
"spec": "test/*.spec.ts",
"watch-files": "test/*.spec.ts",
"colors": true
}
================================================
FILE: .npmignore
================================================
test/
example/
================================================
FILE: .nycrc.json
================================================
{
"extends": "@istanbuljs/nyc-config-typescript",
"all": true,
"check-coverage": false,
"reporter": ["lcov", "text"],
"include": ["src"]
}
================================================
FILE: .prettierignore
================================================
# Ignore artifacts:
lib
node_modules
package-lock.json
.eslintcache
.prettierignore
test/static/*
test/validators/*
.nyc_output/*
coverage/*
================================================
FILE: .prettierrc.json
================================================
{
"printWidth": 100
}
================================================
FILE: .release-it.json
================================================
{
"github": {
"release": true,
"releaseName": "v${version}"
},
"hooks": {
"after:bump": "npm run changelog"
}
}
================================================
FILE: .vscode/settings.json
================================================
{
"cSpell.words": [
"Canonicalization",
"canonicalize",
"canonicalized",
"codecov",
"feide",
"HMAC",
"posteb",
"preeb",
"reserialization",
"stricttextualmsg",
"wsfederation",
"wssecurity",
"xades"
]
}
================================================
FILE: CHANGELOG.md
================================================
# Changelog
## 6.0.0 (2024-01-26)
### 💣 Major Changes
- [**breaking-change**] Set `getCertFromKeyInfo` to `noop` [#445](https://github.com/node-saml/xml-crypto/pull/445)
### 🔗 Dependencies
- [**dependencies**] [**github_actions**] Bump github/codeql-action from 2 to 3 [#434](https://github.com/node-saml/xml-crypto/pull/434)
### 📚 Documentation
- [**documentation**] Chore: Update README.md [#432](https://github.com/node-saml/xml-crypto/pull/432)
---
## v5.1.1 (2024-01-17)
### 🐛 Bug Fixes
- [**bug**] fix: template literal [#443](https://github.com/node-saml/xml-crypto/pull/443)
---
## v5.1.0 (2024-01-07)
### 🚀 Minor Changes
- [**enhancement**] Enhance derToPem to support XML pretty-print [#439](https://github.com/node-saml/xml-crypto/pull/439)
### 🔗 Dependencies
- [**dependencies**] [**javascript**] Bump @typescript-eslint/parser from 6.13.0 to 6.18.1 [#442](https://github.com/node-saml/xml-crypto/pull/442)
- [**dependencies**] [**javascript**] Bump @typescript-eslint/eslint-plugin from 6.13.0 to 6.18.1 [#441](https://github.com/node-saml/xml-crypto/pull/441)
- [**dependencies**] [**javascript**] Bump follow-redirects from 1.15.3 to 1.15.4 [#440](https://github.com/node-saml/xml-crypto/pull/440)
- [**dependencies**] [**javascript**] Bump eslint from 8.54.0 to 8.56.0 [#436](https://github.com/node-saml/xml-crypto/pull/436)
- [**dependencies**] [**javascript**] Bump @types/node from 16.18.65 to 16.18.69 [#435](https://github.com/node-saml/xml-crypto/pull/435)
- [**dependencies**] [**javascript**] Bump release-it from 16.2.1 to 16.3.0 [#428](https://github.com/node-saml/xml-crypto/pull/428)
---
## v5.0.0 (2023-11-27)
### 💣 Major Changes
- [**breaking-change**] Mark `getKeyInfo()` private as it has no public consumers [#412](https://github.com/node-saml/xml-crypto/pull/412)
- [**breaking-change**] Remove the default for `getKeyInfoContent` forcing a consumer to choose [#411](https://github.com/node-saml/xml-crypto/pull/411)
- [**documentation**] [**breaking-change**] Remove default for transformation algorithm [#410](https://github.com/node-saml/xml-crypto/pull/410)
- [**breaking-change**] Remove default for signature algorithm [#408](https://github.com/node-saml/xml-crypto/pull/408)
- [**breaking-change**] Remove default for digest algorithm [#406](https://github.com/node-saml/xml-crypto/pull/406)
- [**breaking-change**] Remove default canonicalization algorithm [#405](https://github.com/node-saml/xml-crypto/pull/405)
- [**chore**] [**breaking-change**] Improve code clarity; remove unused functions [#397](https://github.com/node-saml/xml-crypto/pull/397)
- [**breaking-change**] Move validation messages to each reference [#396](https://github.com/node-saml/xml-crypto/pull/396)
- [**breaking-change**] Make references accessible only via get/set [#395](https://github.com/node-saml/xml-crypto/pull/395)
- [**chore**] [**breaking-change**] Reduce public interface by making some methods private [#394](https://github.com/node-saml/xml-crypto/pull/394)
- [**chore**] [**breaking-change**] Update build to support Node@16 [#385](https://github.com/node-saml/xml-crypto/pull/385)
### 🚀 Minor Changes
- [**enhancement**] Add support for directly querying a node to see if it has passed validation [#389](https://github.com/node-saml/xml-crypto/pull/389)
- [**enhancement**] Add method for checking if element is signed [#368](https://github.com/node-saml/xml-crypto/pull/368)
### 🔗 Dependencies
- [**dependencies**] [**javascript**] Bump @typescript-eslint/eslint-plugin from 5.62.0 to 6.13.0 [#422](https://github.com/node-saml/xml-crypto/pull/422)
- [**dependencies**] [**javascript**] Bump @prettier/plugin-xml from 3.2.1 to 3.2.2 [#423](https://github.com/node-saml/xml-crypto/pull/423)
- [**dependencies**] [**javascript**] Bump @types/mocha from 10.0.2 to 10.0.6 [#421](https://github.com/node-saml/xml-crypto/pull/421)
- [**dependencies**] [**javascript**] Bump @types/chai from 4.3.6 to 4.3.11 [#419](https://github.com/node-saml/xml-crypto/pull/419)
- [**dependencies**] [**javascript**] Bump prettier from 3.0.3 to 3.1.0 [#418](https://github.com/node-saml/xml-crypto/pull/418)
- [**dependencies**] [**javascript**] Bump typescript from 5.2.2 to 5.3.2 [#415](https://github.com/node-saml/xml-crypto/pull/415)
- [**dependencies**] [**javascript**] Bump eslint from 8.51.0 to 8.54.0 [#414](https://github.com/node-saml/xml-crypto/pull/414)
- [**dependencies**] [**github_actions**] Bump actions/setup-node from 3 to 4 [#413](https://github.com/node-saml/xml-crypto/pull/413)
- [**dependencies**] [**javascript**] Bump @babel/traverse from 7.22.4 to 7.23.2 [#407](https://github.com/node-saml/xml-crypto/pull/407)
- [**dependencies**] [**github_actions**] Bump actions/checkout from 3 to 4 [#392](https://github.com/node-saml/xml-crypto/pull/392)
- [**dependencies**] [**javascript**] Bump eslint-plugin-deprecation from 1.4.1 to 2.0.0 [#390](https://github.com/node-saml/xml-crypto/pull/390)
- [**dependencies**] [**javascript**] Bump typescript from 5.1.6 to 5.2.2 [#383](https://github.com/node-saml/xml-crypto/pull/383)
- [**dependencies**] [**javascript**] Bump eslint-config-prettier from 8.8.0 to 9.0.0 [#381](https://github.com/node-saml/xml-crypto/pull/381)
- [**dependencies**] Update dependencies; move to @xmldom-scoped is-dom-node package [#402](https://github.com/node-saml/xml-crypto/pull/402)
### 🐛 Bug Fixes
- [**bug**] Ensure the X509Certificate tag is properly prefixed [#377](https://github.com/node-saml/xml-crypto/pull/377)
- [**bug**] Fix transform processing regression [#379](https://github.com/node-saml/xml-crypto/pull/379)
- [**bug**] Enforce consistent transform processing [#380](https://github.com/node-saml/xml-crypto/pull/380)
### 📚 Documentation
- [**documentation**] Clarify use of <KeyInfo /> in signature validation [#401](https://github.com/node-saml/xml-crypto/pull/401)
### ⚙️ Technical Tasks
- [**chore**] Use is-dom-node for DOM node checking and narrowing [#388](https://github.com/node-saml/xml-crypto/pull/388)
- [**chore**] Improve and simplify validation logic [#373](https://github.com/node-saml/xml-crypto/pull/373)
- [**chore**] Add additional type checking [#369](https://github.com/node-saml/xml-crypto/pull/369)
---
## v4.1.0 (2023-07-28)
### 💣 Major Changes
- [**bug**] [**breaking-change**] Fix `pemToDer()` return type [#364](https://github.com/node-saml/xml-crypto/pull/364)
### ⚙️ Technical Tasks
- [**chore**] Improve exported typings [#367](https://github.com/node-saml/xml-crypto/pull/367)
- [**chore**] Use stricter typing in tests [#366](https://github.com/node-saml/xml-crypto/pull/366)
- [**chore**] Consistently reference `xmldom` [#365](https://github.com/node-saml/xml-crypto/pull/365)
- [**chore**] Rename `findChilds()` to `findChildren()` [#363](https://github.com/node-saml/xml-crypto/pull/363)
---
## v4.0.1 (2023-07-22)
### 🐛 Bug Fixes
- [**bug**] Use correct type for options [#360](https://github.com/node-saml/xml-crypto/pull/360)
- [**bug**] Fix validationErrors type [#361](https://github.com/node-saml/xml-crypto/pull/361)
---
## v4.0.0 (2023-07-21)
### 💣 Major Changes
- [**documentation**] [**breaking-change**] Expand the options, move idmode into options, fix types [#323](https://github.com/node-saml/xml-crypto/pull/323)
- [**breaking-change**] Refactor classes into their own files [#318](https://github.com/node-saml/xml-crypto/pull/318)
- [**breaking-change**] Prefer ES6 classes to prototypical inheritance [#316](https://github.com/node-saml/xml-crypto/pull/316)
- [**breaking-change**] Rename `signingCert` -> `publicCert` and `signingKey` -> `privateKey` [#315](https://github.com/node-saml/xml-crypto/pull/315)
- [**semver-major**] [**breaking-change**] Add support for <X509Certificate /> in <KeyInfo />; remove `KeyInfoProvider` [#301](https://github.com/node-saml/xml-crypto/pull/301)
- [**semver-major**] Target an LTS version of Node [#299](https://github.com/node-saml/xml-crypto/pull/299)
### 🚀 Minor Changes
- [**enhancement**] Exports C14nCanonicalization, ExclusiveCanonicalization [#336](https://github.com/node-saml/xml-crypto/pull/336)
### 🔗 Dependencies
- [**dependencies**] [**javascript**] Bump @xmldom/xmldom from 0.8.8 to 0.8.10 [#358](https://github.com/node-saml/xml-crypto/pull/358)
- [**dependencies**] [**javascript**] Bump @typescript-eslint/eslint-plugin from 5.60.1 to 5.62.0 [#357](https://github.com/node-saml/xml-crypto/pull/357)
- [**dependencies**] [**javascript**] Bump @prettier/plugin-xml from 2.2.0 to 3.1.1 [#356](https://github.com/node-saml/xml-crypto/pull/356)
- [**dependencies**] [**javascript**] Bump prettier from 2.8.8 to 3.0.0 [#350](https://github.com/node-saml/xml-crypto/pull/350)
- [**dependencies**] [**javascript**] Bump release-it from 15.11.0 to 16.1.3 [#352](https://github.com/node-saml/xml-crypto/pull/352)
- [**dependencies**] [**javascript**] Bump prettier-plugin-packagejson from 2.4.3 to 2.4.5 [#353](https://github.com/node-saml/xml-crypto/pull/353)
- [**dependencies**] [**javascript**] Bump @typescript-eslint/parser from 5.60.1 to 5.62.0 [#354](https://github.com/node-saml/xml-crypto/pull/354)
- [**dependencies**] [**javascript**] Bump typescript from 5.1.5 to 5.1.6 [#351](https://github.com/node-saml/xml-crypto/pull/351)
- [**dependencies**] [**javascript**] Bump word-wrap from 1.2.3 to 1.2.4 [#348](https://github.com/node-saml/xml-crypto/pull/348)
- [**dependencies**] [**javascript**] Bump eslint from 8.42.0 to 8.45.0 [#344](https://github.com/node-saml/xml-crypto/pull/344)
- [**dependencies**] Update gren for better support for branches [#340](https://github.com/node-saml/xml-crypto/pull/340)
- [**dependencies**] [**github_actions**] Bump codecov/codecov-action from 3.1.1 to 3.1.4 [#290](https://github.com/node-saml/xml-crypto/pull/290)
### 🐛 Bug Fixes
- [**bug**] Fix issue in case when namespace has no prefix [#330](https://github.com/node-saml/xml-crypto/pull/330)
- [**bug**] Use correct path for code coverage reports [#302](https://github.com/node-saml/xml-crypto/pull/302)
### ⚙️ Technical Tasks
- [**chore**] Enforce eslint `no-unused-vars` [#349](https://github.com/node-saml/xml-crypto/pull/349)
- [**chore**] Enforce eslint `deprecation` [#347](https://github.com/node-saml/xml-crypto/pull/347)
- [**chore**] Enforce eslint `prefer-template` [#346](https://github.com/node-saml/xml-crypto/pull/346)
- [**chore**] Enforce eslint `no-this-alias` [#345](https://github.com/node-saml/xml-crypto/pull/345)
- [**chore**] Convert this project to TypeScript [#325](https://github.com/node-saml/xml-crypto/pull/325)
- [**chore**] Rename files in preparation for TS migration [#343](https://github.com/node-saml/xml-crypto/pull/343)
- [**chore**] Don't force `master` branch when generating changelog [#342](https://github.com/node-saml/xml-crypto/pull/342)
- [**chore**] Enforce eslint `no-unused-vars` [#322](https://github.com/node-saml/xml-crypto/pull/322)
- [**chore**] Enforce eslint `no-prototype-builtins` [#321](https://github.com/node-saml/xml-crypto/pull/321)
- [**chore**] Enforce eslint `eqeqeq` rule [#320](https://github.com/node-saml/xml-crypto/pull/320)
- [**chore**] Update types [#319](https://github.com/node-saml/xml-crypto/pull/319)
- [**chore**] Adjust code to pass eslint `prefer-const` [#312](https://github.com/node-saml/xml-crypto/pull/312)
- [**chore**] Adjust code to pass eslint `no-var` [#311](https://github.com/node-saml/xml-crypto/pull/311)
- [**chore**] Adjust code to pass eslint `curly` [#310](https://github.com/node-saml/xml-crypto/pull/310)
- [**chore**] Adjust code to pass eslint `one-var` [#309](https://github.com/node-saml/xml-crypto/pull/309)
- [**chore**] Typings [#295](https://github.com/node-saml/xml-crypto/pull/295)
- [**chore**] Lint code for new linting rules [#300](https://github.com/node-saml/xml-crypto/pull/300)
- [**chore**] Make linting rules more strict [#293](https://github.com/node-saml/xml-crypto/pull/293)
- [**chore**] Replace Nodeunit with Mocha [#294](https://github.com/node-saml/xml-crypto/pull/294)
---
## v3.1.0 (2023-06-05)
### 🚀 Minor Changes
- [**enhancement**] Add support for appending attributes to KeyInfo element [#285](https://github.com/node-saml/xml-crypto/pull/285)
- [**enhancement**] Use inclusiveNamespacesPrefixList to generate InclusiveNamespaces [#284](https://github.com/node-saml/xml-crypto/pull/284)
- [**enhancement**] build: add release-it to facilitate builds [#275](https://github.com/node-saml/xml-crypto/pull/275)
- [**enhancement**] [**documentation**] feat: add type declaration [#277](https://github.com/node-saml/xml-crypto/pull/277)
- [**enhancement**] make FileKeyInfo extensible for compatibility with TypeScript [#273](https://github.com/node-saml/xml-crypto/pull/273)
- [**enhancement**] Updated getKeyInfo function with actual implementation [#249](https://github.com/node-saml/xml-crypto/pull/249)
### 🔗 Dependencies
- [**dependencies**] Update dependencies [#296](https://github.com/node-saml/xml-crypto/pull/296)
- [**dependencies**] Bump minimatch from 3.0.4 to 3.1.2 [#276](https://github.com/node-saml/xml-crypto/pull/276)
- [**dependencies**] [**javascript**] Bump qs from 6.5.2 to 6.5.3 [#271](https://github.com/node-saml/xml-crypto/pull/271)
### 📚 Documentation
- [**documentation**] [**chore**] Adjust references for `node-saml` organization [#298](https://github.com/node-saml/xml-crypto/pull/298)
### ⚙️ Technical Tasks
- [**chore**] Force CI to run on every PR [#286](https://github.com/node-saml/xml-crypto/pull/286)
- [**chore**] Format code [#289](https://github.com/node-saml/xml-crypto/pull/289)
- [**chore**] Lint code [#288](https://github.com/node-saml/xml-crypto/pull/288)
- [**chore**] Add support for linting [#287](https://github.com/node-saml/xml-crypto/pull/287)
---
## v3.0.1 (2022-10-31)
### 🔗 Dependencies
- [**dependencies**] [**javascript**] Bump ajv and har-validator [#266](https://github.com/node-saml/xml-crypto/pull/266)
- [**dependencies**] [**javascript**] Bump yargs-parser and tap [#257](https://github.com/node-saml/xml-crypto/pull/257)
- [**dependencies**] [**javascript**] Bump minimist and tap [#264](https://github.com/node-saml/xml-crypto/pull/264)
---
## v3.0.0 (2022-10-13)
### 🔗 Dependencies
- [**dependencies**] [**javascript**] Bump @xmldom/xmldom from 0.7.0 to 0.8.3 [#261](https://github.com/node-saml/xml-crypto/pull/261)
- [**dependencies**] [**javascript**] Bump handlebars from 4.0.11 to 4.7.7 [#247](https://github.com/node-saml/xml-crypto/pull/247)
- [**dependencies**] [**javascript**] Bump lodash from 4.17.10 to 4.17.21 [#248](https://github.com/node-saml/xml-crypto/pull/248)
- [**dependencies**] [**javascript**] Bump hosted-git-info from 2.6.0 to 2.8.9 [#246](https://github.com/node-saml/xml-crypto/pull/246)
- [**dependencies**] [**javascript**] Bump ejs from 2.6.1 to 3.1.7 [#244](https://github.com/node-saml/xml-crypto/pull/244)
- [**dependencies**] [**javascript**] Bump path-parse from 1.0.5 to 1.0.7 [#245](https://github.com/node-saml/xml-crypto/pull/245)
### ⚙️ Technical Tasks
- [**chore**] build(ci): test on later node versions [#251](https://github.com/node-saml/xml-crypto/pull/251)
---
## v2.1.4 (2022-07-08)
### 🐛 Bug Fixes
- [**bug**] Correct behavior for XML canonicalization with namespaces and nested elements [#242](https://github.com/node-saml/xml-crypto/pull/242)
---
## v2.1.3 (2021-08-20)
### 🔗 Dependencies
- [**dependencies**] [**javascript**] [**security**] Update xmldom to 0.7.0 [#236](https://github.com/node-saml/xml-crypto/pull/236)
- [**dependencies**] [**java**] Bump commons-io from 2.4 to 2.7 in /test/validators/XmlCryptoJava [#229](https://github.com/node-saml/xml-crypto/pull/229)
---
## v2.1.2 (2021-04-19)
_No changelog for this release._
---
## v2.1.1 (2021-03-16)
_No changelog for this release._
---
## v2.1.0 (2021-03-15)
### 🔗 Dependencies
- [**dependencies**] [**javascript**] Bump xmldom from 0.1.27 to 0.5.0 [#225](https://github.com/node-saml/xml-crypto/pull/225)
- [**dependencies**] [**java**] Bump junit from 4.12 to 4.13.1 in /test/validators/XmlCryptoJava [#217](https://github.com/node-saml/xml-crypto/pull/217)
### 🐛 Bug Fixes
- [**bug**] fix for #201 [#218](https://github.com/node-saml/xml-crypto/pull/218)
### ⚙️ Technical Tasks
- [**chore**] Don't pull the example folder into the module build [#220](https://github.com/node-saml/xml-crypto/pull/220)
---
## v2.0.0 (2020-10-05)
_No changelog for this release._
---
## v1.5.3 (2020-04-14)
### 🚀 Minor Changes
- [**enhancement**] Async response for built in algo sign/verify [#209](https://github.com/node-saml/xml-crypto/pull/209)
---
## v1.5.2 (2020-04-13)
_No changelog for this release._
---
## v1.5.1 (2020-04-13)
### 🐛 Bug Fixes
- [**bug**] Test suites of other projects (mocha) that include v1.5.0 fail [#207](https://github.com/node-saml/xml-crypto/pull/207)
---
## v1.5.0 (2020-04-12)
### 🚀 Minor Changes
- [**enhancement**] Add callback options to sign/verify asynchronously [#206](https://github.com/node-saml/xml-crypto/pull/206)
---
## v1.4.1 (2020-04-03)
### 🔗 Dependencies
- [**dependencies**] Bump js-yaml from 3.12.0 to 3.13.1 [#205](https://github.com/node-saml/xml-crypto/pull/205)
### 🐛 Bug Fixes
- [**bug**] validation instruction typo [#192](https://github.com/node-saml/xml-crypto/pull/192)
- [**bug**] Fixes line end and white space normalization. [#196](https://github.com/node-saml/xml-crypto/pull/196)
---
## v1.4.0 (2019-04-26)
### 🐛 Bug Fixes
- [**bug**] Fix canon xml being computed differently when signing, than when verifying [#183](https://github.com/node-saml/xml-crypto/pull/183)
---
## v1.3.0 (2019-03-23)
### 🐛 Bug Fixes
- [**bug**] Xml enc c14# inclusivenamespace fixes [#179](https://github.com/node-saml/xml-crypto/pull/179)
---
## v1.2.0 (2019-02-26)
### 🐛 Bug Fixes
- [**bug**] Accept existing xml prefixes to avoid adding to signature [#171](https://github.com/node-saml/xml-crypto/pull/171)
---
## v1.1.4 (2019-02-11)
### 🐛 Bug Fixes
- [**bug**] fix for enveloped signatures [#174](https://github.com/node-saml/xml-crypto/pull/174)
---
## v1.1.3 (2019-02-10)
### 🐛 Bug Fixes
- [**bug**] Update signed-xml.js [#172](https://github.com/node-saml/xml-crypto/pull/172)
---
## v1.1.2 (2019-01-28)
_No changelog for this release._
---
## v1.1.1 (2019-01-01)
_No changelog for this release._
---
## v1.1.0 (2019-01-01)
_No changelog for this release._
---
## v1.0.2 (2018-11-08)
### 🐛 Bug Fixes
- [**bug**] Bugfix: a namespace in the inclusive namespace list should be treated… [#163](https://github.com/node-saml/xml-crypto/pull/163)
---
## v1.0.1 (2018-09-10)
_No changelog for this release._
---
## v1.0.0 (2018-09-10)
### 🔗 Dependencies
- [**dependencies**] Addresses issue #235 by upgrading xmldom version to 0.1.27 [#155](https://github.com/node-saml/xml-crypto/pull/155)
### 🐛 Bug Fixes
- [**bug**] Decode DigestValue for validation [#160](https://github.com/node-saml/xml-crypto/pull/160)
- [**bug**] Patch for non exclusive c14n [#157](https://github.com/node-saml/xml-crypto/pull/157)
- [**bug**] Merge changes from datagovsg fork [#161](https://github.com/node-saml/xml-crypto/pull/161)
---
## v0.9.0 (2017-02-26)
_No changelog for this release._
---
## 0.9.0 (2017-02-26)
### 🚀 Minor Changes
- [**enhancement**] Separate namespaces with same prefix but different URI [#117](https://github.com/node-saml/xml-crypto/pull/117)
### 🐛 Bug Fixes
- [**bug**] Implement transform: 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315' [#116](https://github.com/node-saml/xml-crypto/pull/116)
---
## v0.8.5 (2016-12-08)
### 🚀 Minor Changes
- [**enhancement**] Add possible id attribute 'id' [#121](https://github.com/node-saml/xml-crypto/pull/121)
### 📚 Documentation
- [**documentation**] Update license field to npm recommendation [#119](https://github.com/node-saml/xml-crypto/pull/119)
- [**documentation**] Fix author field format [#120](https://github.com/node-saml/xml-crypto/pull/120)
- [**documentation**] Remove namespace-breaking reserialization of signature from example in README [#105](https://github.com/node-saml/xml-crypto/pull/105)
---
## v0.8.4 (2016-03-12)
_No changelog for this release._
---
## v0.8.3 (2016-03-06)
_No changelog for this release._
---
## v0.8.2 (2015-12-13)
_No changelog for this release._
---
## v0.8.1 (2015-10-15)
_No changelog for this release._
---
## v0.8.0 (2015-10-03)
_No changelog for this release._
---
## V1 (2013-07-20)
_No changelog for this release._
================================================
FILE: LICENSE
================================================
(The MIT License)
Copyright (c) Yaron Naveh <yaronn01@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
# xml-crypto

[](https://github.com/prettier/prettier)
[](https://codecov.io/gh/node-saml/xml-crypto)
[](https://deepscan.io/dashboard#view=project&tid=17569&pid=30525&bid=981134)
[](https://nodei.co/npm/xml-crypto)
## Sponsors
 [workos](https://github.com/workos)
 [stytchauth](https://github.com/stytchauth)
## Upgrading
The `.getReferences()` AND the `.references` APIs are deprecated.
Please do not attempt to access them. The content in them should be treated as unsigned.
Instead, we strongly encourage users to migrate to the `.getSignedReferences()` API. See the [Verifying XML document](#verifying-xml-documents) section
We understand that this may take a lot of efforts to migrate, feel free to ask for help.
This will help prevent future XML signature wrapping attacks.
## Supported Algorithms
### Canonicalization and Transformation Algorithms
- Canonicalization <http://www.w3.org/TR/2001/REC-xml-c14n-20010315>
- Canonicalization with comments <http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments>
- Exclusive Canonicalization <http://www.w3.org/2001/10/xml-exc-c14n#>
- Exclusive Canonicalization with comments <http://www.w3.org/2001/10/xml-exc-c14n#WithComments>
- Enveloped Signature transform <http://www.w3.org/2000/09/xmldsig#enveloped-signature>
### Hashing Algorithms
- SHA1 digests <http://www.w3.org/2000/09/xmldsig#sha1>
- SHA256 digests <http://www.w3.org/2001/04/xmlenc#sha256>
- SHA512 digests <http://www.w3.org/2001/04/xmlenc#sha512>
### Signature Algorithms
- RSA-SHA1 <http://www.w3.org/2000/09/xmldsig#rsa-sha1>
- 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>
HMAC-SHA1 is also available but it is disabled by default
- HMAC-SHA1 <http://www.w3.org/2000/09/xmldsig#hmac-sha1>
to enable HMAC-SHA1, call `enableHMAC()` on your instance of `SignedXml`.
This will enable HMAC and disable digital signature algorithms. Due to key
confusion issues, it is risky to have both HMAC-based and public key digital
signature algorithms enabled at same time.
[You are able to extend xml-crypto with custom algorithms.](#customizing-algorithms)
## Signing Xml documents
When signing a xml document you can pass the following options to the `SignedXml` constructor to customize the signature process:
- `privateKey` - **[required]** a `Buffer` or pem encoded `String` containing your private key
- `publicCert` - **[optional]** a `Buffer` or pem encoded `String` containing your public key
- `signatureAlgorithm` - **[required]** one of the supported [signature algorithms](#signature-algorithms). Ex: `sign.signatureAlgorithm = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"`
- `canonicalizationAlgorithm` - **[required]** one of the supported [canonicalization algorithms](#canonicalization-and-transformation-algorithms). Ex: `sign.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#WithComments"`
Use this code:
```javascript
var SignedXml = require("xml-crypto").SignedXml,
fs = require("fs");
var xml = "<library>" + "<book>" + "<name>Harry Potter</name>" + "</book>" + "</library>";
var sig = new SignedXml({ privateKey: fs.readFileSync("client.pem") });
sig.addReference({
xpath: "//*[local-name(.)='book']",
digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1",
transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"],
});
sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#";
sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
sig.computeSignature(xml);
fs.writeFileSync("signed.xml", sig.getSignedXml());
```
The result will be:
```xml
<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/2000/09/xmldsig#rsa-sha1" />
<Reference URI="#_0">
<Transforms>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<DigestValue>cdiS43aFDQMnb3X8yaIUej3+z9Q=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>vhWzpQyIYuncHUZV9W...[long base64 removed]...</SignatureValue>
</Signature>
</library>
```
Note:
If you set the `publicCert` and the `getKeyInfoContent` properties, a `<KeyInfo></KeyInfo>` element with the public certificate will be generated in the signature:
```xml
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
...[signature info removed]...
</SignedInfo>
<SignatureValue>vhWzpQyIYuncHUZV9W...[long base64 removed]...</SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate>MIIGYjCCBJagACCBN...[long base64 removed]...</X509Certificate>
</X509Data>
</KeyInfo>
</Signature>
```
For `getKeyInfoContent`, a default implementation `SignedXml.getKeyInfoContent` is available.
To customize this see [customizing algorithms](#customizing-algorithms) for an example.
## Verifying Xml documents
When verifying a xml document you can pass the following options to the `SignedXml` constructor to customize the verify process:
- `publicCert` - **[optional]** your certificate as a string, a string of multiple certs in PEM format, or a Buffer
- `privateKey` - **[optional]** your private key as a string or a Buffer - used for verifying symmetrical signatures (HMAC)
The certificate that will be used to check the signature will first be determined by calling `this.getCertFromKeyInfo()`, which function you can customize as you see fit. If that returns `null`, then `publicCert` is used. If that is `null`, then `privateKey` is used (for symmetrical signing applications).
Example:
```javascript
new SignedXml({
publicCert: client_public_pem,
getCertFromKeyInfo: () => null,
});
```
You can use any dom parser you want in your code (or none, depending on your usage). This sample uses [xmldom](https://github.com/xmldom/xmldom), so you should install it first:
```shell
npm install @xmldom/xmldom
```
Example:
```javascript
var select = require("xml-crypto").xpath,
dom = require("@xmldom/xmldom").DOMParser,
SignedXml = require("xml-crypto").SignedXml,
fs = require("fs");
var xml = fs.readFileSync("signed.xml").toString();
var doc = new dom().parseFromString(xml);
// DO NOT attempt to parse whatever data object you have here in `doc`
// and then use it to verify the signature. This can lead to security issues.
// i.e. BAD: parseAssertion(doc),
// good: see below
var signature = select(
doc,
"//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']",
)[0];
var sig = new SignedXml({ publicCert: fs.readFileSync("client_public.pem") });
sig.loadSignature(signature);
try {
var res = sig.checkSignature(xml);
} catch (ex) {
console.log(ex);
}
```
In order to protect from some attacks we must check the content we want to use is the one that has been signed:
```javascript
if (!res) {
throw "Invalid Signature";
}
// good: The XML Signature has been verified, meaning some subset of XML is verified.
var signedBytes = sig.getSignedReferences();
var authenticatedDoc = new dom().parseFromString(signedBytes[0]); // Take the first signed reference
// It is now safe to load SAML, obtain the assertion XML, or do whatever else is needed.
// Be sure to only use authenticated data.
let signedAssertionNode = extractAssertion(authenticatedDoc);
let parsedAssertion = parseAssertion(signedAssertionNode);
return parsedAssertion; // This the correctly verified signed Assertion
// BAD example: DO not use the .getReferences() API.
```
Note:
The xml-crypto api requires you to supply it separately the xml signature ("<Signature>...</Signature>", in loadSignature) and the signed xml (in checkSignature). The signed xml may or may not contain the signature in it, but you are still required to supply the signature separately.
### Caring for Implicit transform
If you fail to verify signed XML, then one possible cause is that there are some hidden implicit transforms(#).
(#) Normalizing XML document to be verified. i.e. remove extra space within a tag, sorting attributes, importing namespace declared in ancestor nodes, etc.
The reason for these implicit transform might come from [complex xml signature specification](https://www.w3.org/TR/2002/REC-xmldsig-core-20020212),
which makes XML developers confused and then leads to incorrect implementation for signing XML document.
If you keep failing verification, it is worth trying to guess such a hidden transform and specify it to the option as below:
```javascript
var options = {
implicitTransforms: ["http://www.w3.org/TR/2001/REC-xml-c14n-20010315"],
publicCert: fs.readFileSync("client_public.pem"),
};
var sig = new SignedXml(options);
sig.loadSignature(signature);
var res = sig.checkSignature(xml);
```
You might find it difficult to guess such transforms, but there are typical transforms you can try.
- <http://www.w3.org/TR/2001/REC-xml-c14n-20010315>
- <http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments>
- <http://www.w3.org/2001/10/xml-exc-c14n#>
- <http://www.w3.org/2001/10/xml-exc-c14n#WithComments>
## API
### xpath
See [xpath.js](https://github.com/yaronn/xpath.js) for usage. Note that this is actually using
[another library](https://github.com/goto100/xpath) as the underlying implementation.
### SignedXml
The `SignedXml` constructor provides an abstraction for sign and verify xml documents. The object is constructed using `new SignedXml(options?: SignedXmlOptions)` where the possible options are:
- `idMode` - default `null` - if the value of `wssecurity` is passed it will create/validate id's with the ws-security namespace.
- `idAttribute` - string - default `Id` or `ID` or `id` - the name of the attribute that contains the id of the element
- `privateKey` - string or Buffer - default `null` - the private key to use for signing
- `publicCert` - string or Buffer - default `null` - the public certificate to use for verifying
- `signatureAlgorithm` - string - the signature algorithm to use
- `canonicalizationAlgorithm` - string - default `undefined` - the canonicalization algorithm to use
- `inclusiveNamespacesPrefixList` - string - default `null` - a list of namespace prefixes to include during canonicalization
- `implicitTransforms` - string[] - default `[]` - a list of implicit transforms to use during verification
- `keyInfoAttributes` - object - default `{}` - a hash of attributes and values `attrName: value` to add to the KeyInfo node
- `getKeyInfoContent` - function - default `SignedXml.getKeyInfoContent` - a function that returns the content of the KeyInfo node
- `getCertFromKeyInfo` - function - default `noop` - a function that returns the certificate from the `<KeyInfo />` node
#### API
A `SignedXml` object provides the following methods:
To sign xml documents:
- `addReference({ xpath, transforms, digestAlgorithm, id, type })` - adds a reference to a xml element where:
- `xpath` - a string containing a XPath expression referencing a xml element
- `transforms` - an array of [transform algorithms](#canonicalization-and-transformation-algorithms), the referenced element will be transformed for each value in the array
- `digestAlgorithm` - one of the supported [hashing algorithms](#hashing-algorithms)
- `id` - an optional `Id` attribute to add to the reference element
- `type` - the optional `Type` attribute to add to the reference element (represented as a URI)
- `computeSignature(xml, [options])` - compute the signature of the given xml where:
- `xml` - a string containing a xml document
- `options` - an object with the following properties:
- `prefix` - adds this value as a prefix for the generated signature tags
- `attrs` - a hash of attributes and values `attrName: value` to add to the signature root node
- `location` - customize the location of the signature, pass an object with a `reference` key which should contain a XPath expression to a reference node, an `action` key which should contain one of the following values: `append`, `prepend`, `before`, `after`
- `existingPrefixes` - A hash of prefixes and namespaces `prefix: namespace` that shouldn't be in the signature because they already exist in the xml
- `getSignedXml()` - returns the original xml document with the signature in it, **must be called only after `computeSignature`**
- `getSignatureXml()` - returns just the signature part, **must be called only after `computeSignature`**
- `getOriginalXmlWithIds()` - returns the original xml with Id attributes added on relevant elements (required for validation), **must be called only after `computeSignature`**
To verify xml documents:
- `loadSignature(signatureXml)` - loads the signature where:
- `signatureXml` - a string or node object (like an [xmldom](https://github.com/xmldom/xmldom) node) containing the xml representation of the signature
- `checkSignature(xml)` - validates the given xml document and returns `true` if the validation was successful
## Customizing Algorithms
The following sample shows how to sign a message using custom algorithms.
First import some modules:
```javascript
var SignedXml = require("xml-crypto").SignedXml,
fs = require("fs");
```
Now define the extension point you want to implement. You can choose one or more.
To determine the inclusion and contents of a `<KeyInfo />` element, the function
`this.getKeyInfoContent()` is called. There is a default implementation of this. If you wish to change
this implementation, provide your own function assigned to the property `this.getKeyInfoContent`. If you prefer to use the default implementation, assign `SignedXml.getKeyInfoContent` to `this.getKeyInfoContent` If
there are no attributes and no contents to the `<KeyInfo />` element, it won't be included in the
generated XML.
To specify custom attributes on `<KeyInfo />`, add the properties to the `.keyInfoAttributes` property.
A custom hash algorithm is used to calculate digests. Implement it if you want a hash other than the built-in methods.
```javascript
function MyDigest() {
this.getHash = function (xml) {
return "the base64 hash representation of the given xml string";
};
this.getAlgorithmName = function () {
return "http://myDigestAlgorithm";
};
}
```
A custom signing algorithm.
```javascript
function MySignatureAlgorithm() {
/*sign the given SignedInfo using the key. return base64 signature value*/
this.getSignature = function (signedInfo, privateKey) {
return "signature of signedInfo as base64...";
};
this.getAlgorithmName = function () {
return "http://mySigningAlgorithm";
};
}
```
Custom transformation algorithm.
```javascript
function MyTransformation() {
/*given a node (from the xmldom module) return its canonical representation (as string)*/
this.process = function (node) {
//you should apply your transformation before returning
return node.toString();
};
this.getAlgorithmName = function () {
return "http://myTransformation";
};
}
```
Custom canonicalization is actually the same as custom transformation. It is applied on the SignedInfo rather than on references.
```javascript
function MyCanonicalization() {
/*given a node (from the xmldom module) return its canonical representation (as string)*/
this.process = function (node) {
//you should apply your transformation before returning
return "< x/>";
};
this.getAlgorithmName = function () {
return "http://myCanonicalization";
};
}
```
Now you need to register the new algorithms:
```javascript
/*register all the custom algorithms*/
signedXml.CanonicalizationAlgorithms["http://MyTransformation"] = MyTransformation;
signedXml.CanonicalizationAlgorithms["http://MyCanonicalization"] = MyCanonicalization;
signedXml.HashAlgorithms["http://myDigestAlgorithm"] = MyDigest;
signedXml.SignatureAlgorithms["http://mySigningAlgorithm"] = MySignatureAlgorithm;
```
Now do the signing. Note how we configure the signature to use the above algorithms:
```javascript
function signXml(xml, xpath, key, dest) {
var options = {
publicCert: fs.readFileSync("my_public_cert.pem", "latin1"),
privateKey: fs.readFileSync(key),
/*configure the signature object to use the custom algorithms*/
signatureAlgorithm: "http://mySignatureAlgorithm",
canonicalizationAlgorithm: "http://MyCanonicalization",
};
var sig = new SignedXml(options);
sig.addReference({
xpath: "//*[local-name(.)='x']",
transforms: ["http://MyTransformation"],
digestAlgorithm: "http://myDigestAlgorithm",
});
sig.addReference({
xpath,
transforms: ["http://MyTransformation"],
digestAlgorithm: "http://myDigestAlgorithm",
});
sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#";
sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
sig.computeSignature(xml);
fs.writeFileSync(dest, sig.getSignedXml());
}
var xml = "<library>" + "<book>" + "<name>Harry Potter</name>" + "</book>";
("</library>");
signXml(xml, "//*[local-name(.)='book']", "client.pem", "result.xml");
```
You can always look at the actual code as a sample.
## Asynchronous signing and verification
If the private key is not stored locally, and you wish to use a signing server or Hardware Security Module (HSM) to sign documents, you can create a custom signing algorithm that uses an asynchronous callback.
```javascript
function AsyncSignatureAlgorithm() {
this.getSignature = function (signedInfo, privateKey, callback) {
var signer = crypto.createSign("RSA-SHA1");
signer.update(signedInfo);
var res = signer.sign(privateKey, "base64");
//Do some asynchronous things here
callback(null, res);
};
this.getAlgorithmName = function () {
return "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
};
}
var sig = new SignedXml({ signatureAlgorithm: "http://asyncSignatureAlgorithm" });
sig.SignatureAlgorithms["http://asyncSignatureAlgorithm"] = AsyncSignatureAlgorithm;
sig.signatureAlgorithm = "http://asyncSignatureAlgorithm";
sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#";
sig.computeSignature(xml, opts, function (err) {
var signedResponse = sig.getSignedXml();
});
```
The function `sig.checkSignature` may also use a callback if asynchronous verification is needed.
## X.509 / Key formats
Xml-Crypto internally relies on node's crypto module. This means pem encoded certificates are supported. So to sign an xml use key.pem that looks like this (only the beginning of the key content is shown):
```text
-----BEGIN PRIVATE KEY-----
MIICdwIBADANBgkqhkiG9w0...
-----END PRIVATE KEY-----
```
And for verification use key_public.pem:
```text
-----BEGIN CERTIFICATE-----
MIIBxDCCAW6gAwIBAgIQxUSX...
-----END CERTIFICATE-----
```
### Converting .pfx certificates to pem
If you have .pfx certificates you can convert them to .pem using [openssl](http://www.openssl.org/):
```shell
openssl pkcs12 -in c:\certs\yourcert.pfx -out c:\certs\cag.pem
```
Then you could use the result as is for the purpose of signing. For the purpose of validation open the resulting .pem with a text editor and copy from -----BEGIN CERTIFICATE----- to -----END CERTIFICATE----- (including) to a new text file and save it as .pem.
## Examples
### how to sign a root node (_coming soon_)
### how to add a prefix for the signature
Use the `prefix` option when calling `computeSignature` to add a prefix to the signature.
```javascript
var SignedXml = require("xml-crypto").SignedXml,
fs = require("fs");
var xml = "<library>" + "<book>" + "<name>Harry Potter</name>" + "</book>" + "</library>";
var sig = new SignedXml({ privateKey: fs.readFileSync("client.pem") });
sig.addReference({
xpath: "//*[local-name(.)='book']",
digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1",
transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"],
});
sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#";
sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
sig.computeSignature(xml, {
prefix: "ds",
});
```
### how to specify the location of the signature
Use the `location` option when calling `computeSignature` to move the signature around.
Set `action` to one of the following:
- append(default) - append to the end of the xml document
- prepend - prepend to the xml document
- before - prepend to a specific node (use the `referenceNode` property)
- after - append to specific node (use the `referenceNode` property)
```javascript
const SignedXml = require("xml-crypto").SignedXml;
const fs = require("fs");
const xml = "<library>" + "<book>" + "<name>Harry Potter</name>" + "</book>" + "</library>";
const sig = new SignedXml({ privateKey: fs.readFileSync("client.pem") });
sig.addReference({
xpath: "//*[local-name(.)='book']",
digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1",
transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"],
});
sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#";
sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
sig.computeSignature(xml, {
location: { reference: "//*[local-name(.)='book']", action: "after" }, // This will place the signature after the book element
});
```
### How to add custom Objects to the signature
Use the `objects` option when creating a SignedXml instance to add custom Objects to the signature.
```javascript
const SignedXml = require("xml-crypto").SignedXml;
const fs = require("fs");
const xml = "<library>" + "<book>" + "<name>Harry Potter</name>" + "</book>" + "</library>";
const sig = new SignedXml({
privateKey: fs.readFileSync("client.pem"),
canonicalizationAlgorithm: "http://www.w3.org/2001/10/xml-exc-c14n#",
signatureAlgorithm: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
objects: [
{
content: "<TestObject>Test data in Object</TestObject>",
attributes: {
Id: "Object1",
MimeType: "text/xml",
},
},
],
});
// Add a reference to the Object element
sig.addReference({
xpath: "//*[@Id='Object1']",
digestAlgorithm: "http://www.w3.org/2001/04/xmlenc#sha256",
transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"],
});
sig.computeSignature(xml);
fs.writeFileSync("signed.xml", sig.getSignedXml());
```
### more examples (_coming soon_)
## Development
The testing framework we use is [Mocha](https://github.com/mochajs/mocha) with [Chai](https://github.com/chaijs/chai) as the assertion framework.
To run tests use:
```shell
npm test
```
## Sponsors
 [Short-io](https://github.com/Short-io)
 [RideAmigosCorp](https://github.com/RideAmigosCorp)
## License
This project is licensed under the [MIT License](http://opensource.org/licenses/MIT). See the [LICENSE](LICENSE) file for more info.
================================================
FILE: example/client.pem
================================================
-----BEGIN PRIVATE KEY-----
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAL4vpoH3H3byehjj
7RAGxefGRATiq4mXtzc9Q91W7uT0DTaFEbjzVch9aGsNjmLs4QHsoZbuoUmi0st4
x5z9SQpTAKC/dW8muzacT3E7dJJYh03MAO6RiH4LG34VRTq1SQN6qDt2rCK85eG4
5NHI4jceptZNu6Zot1zyO5/PYuFpAgMBAAECgYAhspeyF3M/xB7WIixy1oBiXMLY
isESFAumgfhwU2LotkVRD6rgNl1QtMe3kCNWa9pCWQcYkxeI0IzA+JmFu2shVvoR
oL7eV4VCe1Af33z24E46+cY5grxNhHt/LyCnZKcitvCcrzXExUc5n6KngX0mMKgk
W7skZDwsnKzhyUV8wQJBAN2bQMeASQVOqdfqBdFgC/NPnKY2cuDi6h659QN1l+kg
X3ywdZ7KKftJo1G9l45SN9YpkyEd9zEO6PMFaufJvZUCQQDbtAWxk0i8BT3UTNWC
T/9bUQROPcGZagwwnRFByX7gpmfkf1ImIvbWVXSpX68/IjbjSkTw1nj/Yj1NwFZ0
nxeFAkEAzPhRpXVBlPgaXkvlz7AfvY+wW4hXHyyi0YK8XdPBi25XA5SPZiylQfjt
Z6iN6qSfYqYXoPT/c0/QJR+orvVJNQJBANhRPNXljVTK2GDCseoXd/ZiI5ohxg+W
UaA/1fDvQsRQM7TQA4NXI7BO/YmSk4rW1jIeOxjiIspY4MFAIh+7UL0CQFL6zTg6
wfeMlEZzvgqwCGoLuvTnqtvyg45z7pfcrg2cHdgCXIy9kErcjwGiu6BOevEA1qTW
Rk+bv0tknWvcz/s=
-----END PRIVATE KEY-----
================================================
FILE: example/client_public.pem
================================================
-----BEGIN CERTIFICATE-----
MIIBxDCCAW6gAwIBAgIQxUSXFzWJYYtOZnmmuOMKkjANBgkqhkiG9w0BAQQFADAW
MRQwEgYDVQQDEwtSb290IEFnZW5jeTAeFw0wMzA3MDgxODQ3NTlaFw0zOTEyMzEy
MzU5NTlaMB8xHTAbBgNVBAMTFFdTRTJRdWlja1N0YXJ0Q2xpZW50MIGfMA0GCSqG
SIb3DQEBAQUAA4GNADCBiQKBgQC+L6aB9x928noY4+0QBsXnxkQE4quJl7c3PUPd
Vu7k9A02hRG481XIfWhrDY5i7OEB7KGW7qFJotLLeMec/UkKUwCgv3VvJrs2nE9x
O3SSWIdNzADukYh+Cxt+FUU6tUkDeqg7dqwivOXhuOTRyOI3HqbWTbumaLdc8juf
z2LhaQIDAQABo0swSTBHBgNVHQEEQDA+gBAS5AktBh0dTwCNYSHcFmRjoRgwFjEU
MBIGA1UEAxMLUm9vdCBBZ2VuY3mCEAY3bACqAGSKEc+41KpcNfQwDQYJKoZIhvcN
AQEEBQADQQAfIbnMPVYkNNfX1tG1F+qfLhHwJdfDUZuPyRPucWF5qkh6sSdWVBY5
sT/txBnVJGziyO8DPYdu2fPMER8ajJfl
-----END CERTIFICATE-----
================================================
FILE: example/example.js
================================================
/* eslint-disable no-console */
const select = require("xml-crypto").xpath;
const dom = require("@xmldom/xmldom").DOMParser;
const SignedXml = require("xml-crypto").SignedXml;
const fs = require("fs");
function signXml(xml, xpath, key, dest) {
const sig = new SignedXml();
sig.privateKey = fs.readFileSync(key);
sig.addReference(xpath);
sig.computeSignature(xml);
fs.writeFileSync(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 = key;
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");
console.log("xml signed successfully");
const signedXml = fs.readFileSync("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");
}
================================================
FILE: package.json
================================================
{
"name": "xml-crypto",
"version": "6.0.0",
"private": false,
"description": "Xml digital signature and encryption library for Node.js",
"keywords": [
"xml",
"digital signature",
"xml encryption",
"x.509 certificate"
],
"repository": {
"type": "git",
"url": "https://github.com/node-saml/xml-crypto.git"
},
"license": "MIT",
"author": "Yaron Naveh <yaronn01@gmail.com> (http://webservices20.blogspot.com/)",
"contributors": [
"LoneRifle <LoneRifle@users.noreply.github.com>",
"Chris Barth <chrisjbarth@hotmail.com>"
],
"main": "./lib",
"files": [
"lib",
"LICENSE",
"README.md"
],
"scripts": {
"build": "npx tsc",
"changelog": "gren changelog --override --generate",
"lint": "eslint \"{src,test}/*.ts\" --cache && npm run prettier-check",
"lint:fix": "eslint --fix \"{src,test}/*.ts\" && npm run prettier-format",
"prepare": "tsc",
"prettier-check": "prettier --config .prettierrc.json --check .",
"prettier-format": "prettier --config .prettierrc.json --write .",
"prerelease": "git clean -xfd && npm ci && npm test",
"release": "release-it",
"test": "nyc mocha"
},
"dependencies": {
"@xmldom/is-dom-node": "^1.0.1",
"@xmldom/xmldom": "^0.8.10",
"xpath": "^0.0.33"
},
"devDependencies": {
"@cjbarth/github-release-notes": "^4.2.0",
"@istanbuljs/nyc-config-typescript": "^1.0.2",
"@prettier/plugin-xml": "^3.2.2",
"@types/chai": "^4.3.11",
"@types/mocha": "^10.0.6",
"@types/node": "^16.18.69",
"@typescript-eslint/eslint-plugin": "^6.18.1",
"@typescript-eslint/parser": "^6.18.1",
"chai": "^4.3.10",
"choma": "^1.2.1",
"ejs": "^3.1.9",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-deprecation": "^2.0.0",
"lcov": "^1.16.0",
"mocha": "^10.2.0",
"nyc": "^15.1.0",
"prettier": "^3.1.0",
"prettier-plugin-packagejson": "^2.4.6",
"release-it": "^16.3.0",
"source-map-support": "^0.5.21",
"ts-node": "^10.9.1",
"typescript": "^5.3.2"
},
"engines": {
"node": ">=16"
}
}
================================================
FILE: src/c14n-canonicalization.ts
================================================
import type {
CanonicalizationOrTransformationAlgorithm,
CanonicalizationOrTransformationAlgorithmProcessOptions,
NamespacePrefix,
RenderedNamespace,
} from "./types";
import * as utils from "./utils";
import * as isDomNode from "@xmldom/is-dom-node";
export class C14nCanonicalization implements CanonicalizationOrTransformationAlgorithm {
protected includeComments = false;
constructor() {
this.includeComments = false;
}
attrCompare(a, b) {
if (!a.namespaceURI && b.namespaceURI) {
return -1;
}
if (!b.namespaceURI && a.namespaceURI) {
return 1;
}
const left = a.namespaceURI + a.localName;
const right = b.namespaceURI + b.localName;
if (left === right) {
return 0;
} else if (left < right) {
return -1;
} else {
return 1;
}
}
nsCompare(a, b) {
const attr1 = a.prefix;
const attr2 = b.prefix;
if (attr1 === attr2) {
return 0;
}
return attr1.localeCompare(attr2);
}
renderAttrs(node) {
let i;
let attr;
const attrListToRender: Attr[] = [];
if (isDomNode.isCommentNode(node)) {
return this.renderComment(node);
}
if (node.attributes) {
for (i = 0; i < node.attributes.length; ++i) {
attr = node.attributes[i];
//ignore namespace definition attributes
if (attr.name.indexOf("xmlns") === 0) {
continue;
}
attrListToRender.push(attr);
}
}
attrListToRender.sort(this.attrCompare);
const res = attrListToRender.map((attr) => {
return ` ${attr.name}="${utils.encodeSpecialCharactersInAttribute(attr.value)}"`;
});
return res.join("");
}
/**
* Create the string of all namespace declarations that should appear on this element
*
* @param node The node we now render
* @param prefixesInScope The prefixes defined on this node parents which are a part of the output set
* @param defaultNs The current default namespace
* @param defaultNsForPrefix
* @param ancestorNamespaces Import ancestor namespaces if it is specified
* @api private
*/
renderNs(
node: Element,
prefixesInScope: string[],
defaultNs: string,
defaultNsForPrefix: string,
ancestorNamespaces: NamespacePrefix[],
): RenderedNamespace {
let i;
let attr;
const res: string[] = [];
let newDefaultNs = defaultNs;
const nsListToRender: { prefix: string; namespaceURI: string }[] = [];
const currNs = node.namespaceURI || "";
//handle the namespace of the node itself
if (node.prefix) {
if (prefixesInScope.indexOf(node.prefix) === -1) {
nsListToRender.push({
prefix: node.prefix,
namespaceURI: node.namespaceURI || defaultNsForPrefix[node.prefix],
});
prefixesInScope.push(node.prefix);
}
} else if (defaultNs !== currNs) {
//new default ns
newDefaultNs = node.namespaceURI || "";
res.push(' xmlns="', newDefaultNs, '"');
}
//handle the attributes namespace
if (node.attributes) {
for (i = 0; i < node.attributes.length; ++i) {
attr = node.attributes[i];
//handle all prefixed attributes that are included in the prefix list and where
//the prefix is not defined already. New prefixes can only be defined by `xmlns:`.
if (attr.prefix === "xmlns" && prefixesInScope.indexOf(attr.localName) === -1) {
nsListToRender.push({ prefix: attr.localName, namespaceURI: attr.value });
prefixesInScope.push(attr.localName);
}
//handle all prefixed attributes that are not xmlns definitions and where
//the prefix is not defined already
if (
attr.prefix &&
prefixesInScope.indexOf(attr.prefix) === -1 &&
attr.prefix !== "xmlns" &&
attr.prefix !== "xml"
) {
nsListToRender.push({ prefix: attr.prefix, namespaceURI: attr.namespaceURI });
prefixesInScope.push(attr.prefix);
}
}
}
if (utils.isArrayHasLength(ancestorNamespaces)) {
// Remove namespaces which are already present in nsListToRender
for (const ancestorNamespace of ancestorNamespaces) {
let alreadyListed = false;
for (const nsToRender of nsListToRender) {
if (
nsToRender.prefix === ancestorNamespace.prefix &&
nsToRender.namespaceURI === ancestorNamespace.namespaceURI
) {
alreadyListed = true;
}
}
if (!alreadyListed) {
nsListToRender.push(ancestorNamespace);
}
}
}
nsListToRender.sort(this.nsCompare);
//render namespaces
res.push(
...nsListToRender.map((attr) => {
if (attr.prefix) {
return ` xmlns:${attr.prefix}="${attr.namespaceURI}"`;
}
return ` xmlns="${attr.namespaceURI}"`;
}),
);
return { rendered: res.join(""), newDefaultNs };
}
/**
* @param node Node
*/
processInner(node, prefixesInScope, defaultNs, defaultNsForPrefix, ancestorNamespaces) {
if (isDomNode.isCommentNode(node)) {
return this.renderComment(node);
}
if (node.data) {
return utils.encodeSpecialCharactersInText(node.data);
}
if (isDomNode.isElementNode(node)) {
let i;
let pfxCopy;
const ns = this.renderNs(
node,
prefixesInScope,
defaultNs,
defaultNsForPrefix,
ancestorNamespaces,
);
const res = ["<", node.tagName, ns.rendered, this.renderAttrs(node), ">"];
for (i = 0; i < node.childNodes.length; ++i) {
pfxCopy = prefixesInScope.slice(0);
res.push(
this.processInner(node.childNodes[i], pfxCopy, ns.newDefaultNs, defaultNsForPrefix, []),
);
}
res.push("</", node.tagName, ">");
return res.join("");
}
throw new Error(`Unable to canonicalize node type: ${node.nodeType}`);
}
// Thanks to deoxxa/xml-c14n for comment renderer
renderComment(node: Comment) {
if (!this.includeComments) {
return "";
}
const isOutsideDocument = node.ownerDocument === node.parentNode;
let isBeforeDocument = false;
let isAfterDocument = false;
if (isOutsideDocument) {
let nextNode: ChildNode | null = node;
let previousNode: ChildNode | null = node;
while (nextNode !== null) {
if (nextNode === node.ownerDocument.documentElement) {
isBeforeDocument = true;
break;
}
nextNode = nextNode.nextSibling;
}
while (previousNode !== null) {
if (previousNode === node.ownerDocument.documentElement) {
isAfterDocument = true;
break;
}
previousNode = previousNode.previousSibling;
}
}
const afterDocument = isAfterDocument ? "\n" : "";
const beforeDocument = isBeforeDocument ? "\n" : "";
const encodedText = utils.encodeSpecialCharactersInText(node.data);
return `${afterDocument}<!--${encodedText}-->${beforeDocument}`;
}
/**
* Perform canonicalization of the given node
*
* @param node
* @api public
*/
process(node: Node, options: CanonicalizationOrTransformationAlgorithmProcessOptions): string {
options = options || {};
const defaultNs = options.defaultNs || "";
const defaultNsForPrefix = options.defaultNsForPrefix || {};
const ancestorNamespaces = options.ancestorNamespaces || [];
const prefixesInScope: string[] = [];
for (let i = 0; i < ancestorNamespaces.length; i++) {
prefixesInScope.push(ancestorNamespaces[i].prefix);
}
const res = this.processInner(
node,
prefixesInScope,
defaultNs,
defaultNsForPrefix,
ancestorNamespaces,
);
return res;
}
getAlgorithmName() {
return "http://www.w3.org/TR/2001/REC-xml-c14n-20010315";
}
}
/**
* Add c14n#WithComments here (very simple subclass)
*/
export class C14nCanonicalizationWithComments extends C14nCanonicalization {
constructor() {
super();
this.includeComments = true;
}
getAlgorithmName() {
return "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments";
}
}
================================================
FILE: src/enveloped-signature.ts
================================================
import * as xpath from "xpath";
import * as isDomNode from "@xmldom/is-dom-node";
import type {
CanonicalizationOrTransformationAlgorithm,
CanonicalizationOrTransformationAlgorithmProcessOptions,
CanonicalizationOrTransformAlgorithmType,
} from "./types";
export class EnvelopedSignature implements CanonicalizationOrTransformationAlgorithm {
protected includeComments = false;
constructor() {
this.includeComments = false;
}
process(node: Node, options: CanonicalizationOrTransformationAlgorithmProcessOptions): Node {
if (null == options.signatureNode) {
const signature = xpath.select1(
"./*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']",
node,
);
if (isDomNode.isNodeLike(signature) && signature.parentNode) {
signature.parentNode.removeChild(signature);
}
return node;
}
const signatureNode = options.signatureNode;
const expectedSignatureValue = xpath.select1(
".//*[local-name(.)='SignatureValue']/text()",
signatureNode,
);
if (isDomNode.isTextNode(expectedSignatureValue)) {
const expectedSignatureValueData = expectedSignatureValue.data;
const signatures = xpath.select(
".//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']",
node,
);
for (const nodeSignature of Array.isArray(signatures) ? signatures : []) {
const signatureValue = xpath.select1(
".//*[local-name(.)='SignatureValue']/text()",
nodeSignature,
);
if (isDomNode.isTextNode(signatureValue)) {
const signatureValueData = signatureValue.data;
if (expectedSignatureValueData === signatureValueData) {
if (nodeSignature.parentNode) {
nodeSignature.parentNode.removeChild(nodeSignature);
}
}
}
}
}
return node;
}
getAlgorithmName(): CanonicalizationOrTransformAlgorithmType {
return "http://www.w3.org/2000/09/xmldsig#enveloped-signature";
}
}
================================================
FILE: src/exclusive-canonicalization.ts
================================================
import type {
CanonicalizationOrTransformationAlgorithm,
CanonicalizationOrTransformationAlgorithmProcessOptions,
NamespacePrefix,
} from "./types";
import * as utils from "./utils";
import * as isDomNode from "@xmldom/is-dom-node";
function isPrefixInScope(prefixesInScope, prefix, namespaceURI) {
let ret = false;
prefixesInScope.forEach(function (pf) {
if (pf.prefix === prefix && pf.namespaceURI === namespaceURI) {
ret = true;
}
});
return ret;
}
export class ExclusiveCanonicalization implements CanonicalizationOrTransformationAlgorithm {
protected includeComments = false;
constructor() {
this.includeComments = false;
}
attrCompare(a, b) {
if (!a.namespaceURI && b.namespaceURI) {
return -1;
}
if (!b.namespaceURI && a.namespaceURI) {
return 1;
}
const left = a.namespaceURI + a.localName;
const right = b.namespaceURI + b.localName;
if (left === right) {
return 0;
} else if (left < right) {
return -1;
} else {
return 1;
}
}
nsCompare(a, b) {
const attr1 = a.prefix;
const attr2 = b.prefix;
if (attr1 === attr2) {
return 0;
}
return attr1.localeCompare(attr2);
}
renderAttrs(node) {
let i;
let attr;
const res: string[] = [];
const attrListToRender: Attr[] = [];
if (isDomNode.isCommentNode(node)) {
return this.renderComment(node);
}
if (node.attributes) {
for (i = 0; i < node.attributes.length; ++i) {
attr = node.attributes[i];
//ignore namespace definition attributes
if (attr.name.indexOf("xmlns") === 0) {
continue;
}
attrListToRender.push(attr);
}
}
attrListToRender.sort(this.attrCompare);
for (attr of attrListToRender) {
res.push(" ", attr.name, '="', utils.encodeSpecialCharactersInAttribute(attr.value), '"');
}
return res.join("");
}
/**
* Create the string of all namespace declarations that should appear on this element
*
* @param {Node} node. The node we now render
* @param {Array} prefixesInScope. The prefixes defined on this node
* parents which are a part of the output set
* @param {String} defaultNs. The current default namespace
* @return {String}
* @api private
*/
renderNs(
node,
prefixesInScope,
defaultNs,
defaultNsForPrefix,
inclusiveNamespacesPrefixList: string[],
) {
let i;
let attr;
const res: string[] = [];
let newDefaultNs = defaultNs;
const nsListToRender: NamespacePrefix[] = [];
const currNs = node.namespaceURI || "";
//handle the namespaceof the node itself
if (node.prefix) {
if (
!isPrefixInScope(
prefixesInScope,
node.prefix,
node.namespaceURI || defaultNsForPrefix[node.prefix],
)
) {
nsListToRender.push({
prefix: node.prefix,
namespaceURI: node.namespaceURI || defaultNsForPrefix[node.prefix],
});
prefixesInScope.push({
prefix: node.prefix,
namespaceURI: node.namespaceURI || defaultNsForPrefix[node.prefix],
});
}
} else if (defaultNs !== currNs) {
//new default ns
newDefaultNs = node.namespaceURI;
res.push(' xmlns="', newDefaultNs, '"');
}
//handle the attributes namespace
if (node.attributes) {
for (i = 0; i < node.attributes.length; ++i) {
attr = node.attributes[i];
//handle all prefixed attributes that are included in the prefix list and where
//the prefix is not defined already
if (
attr.prefix &&
!isPrefixInScope(prefixesInScope, attr.localName, attr.value) &&
inclusiveNamespacesPrefixList.indexOf(attr.localName) >= 0
) {
nsListToRender.push({ prefix: attr.localName, namespaceURI: attr.value });
prefixesInScope.push({ prefix: attr.localName, namespaceURI: attr.value });
}
//handle all prefixed attributes that are not xmlns definitions and where
//the prefix is not defined already
if (
attr.prefix &&
!isPrefixInScope(prefixesInScope, attr.prefix, attr.namespaceURI) &&
attr.prefix !== "xmlns" &&
attr.prefix !== "xml"
) {
nsListToRender.push({ prefix: attr.prefix, namespaceURI: attr.namespaceURI });
prefixesInScope.push({ prefix: attr.prefix, namespaceURI: attr.namespaceURI });
}
}
}
nsListToRender.sort(this.nsCompare);
//render namespaces
for (const p of nsListToRender) {
res.push(" xmlns:", p.prefix, '="', p.namespaceURI, '"');
}
return { rendered: res.join(""), newDefaultNs: newDefaultNs };
}
/**
* @param node Node
*/
processInner(
node,
prefixesInScope,
defaultNs,
defaultNsForPrefix,
inclusiveNamespacesPrefixList: string[],
) {
if (isDomNode.isCommentNode(node)) {
return this.renderComment(node);
}
if (node.data) {
return utils.encodeSpecialCharactersInText(node.data);
}
if (isDomNode.isElementNode(node)) {
let i;
let pfxCopy;
const ns = this.renderNs(
node,
prefixesInScope,
defaultNs,
defaultNsForPrefix,
inclusiveNamespacesPrefixList,
);
const res = ["<", node.tagName, ns.rendered, this.renderAttrs(node), ">"];
for (i = 0; i < node.childNodes.length; ++i) {
pfxCopy = prefixesInScope.slice(0);
res.push(
this.processInner(
node.childNodes[i],
pfxCopy,
ns.newDefaultNs,
defaultNsForPrefix,
inclusiveNamespacesPrefixList,
),
);
}
res.push("</", node.tagName, ">");
return res.join("");
}
throw new Error(`Unable to exclusive canonicalize node type: ${node.nodeType}`);
}
// Thanks to deoxxa/xml-c14n for comment renderer
renderComment(node: Comment) {
if (!this.includeComments) {
return "";
}
const isOutsideDocument = node.ownerDocument === node.parentNode;
let isBeforeDocument = false;
let isAfterDocument = false;
if (isOutsideDocument) {
let nextNode: ChildNode | null = node;
let previousNode: ChildNode | null = node;
while (nextNode != null) {
if (nextNode === node.ownerDocument.documentElement) {
isBeforeDocument = true;
break;
}
nextNode = nextNode.nextSibling;
}
while (previousNode != null) {
if (previousNode === node.ownerDocument.documentElement) {
isAfterDocument = true;
break;
}
previousNode = previousNode.previousSibling;
}
}
const afterDocument = isAfterDocument ? "\n" : "";
const beforeDocument = isBeforeDocument ? "\n" : "";
const encodedText = utils.encodeSpecialCharactersInText(node.data);
return `${afterDocument}<!--${encodedText}-->${beforeDocument}`;
}
/**
* Perform canonicalization of the given element node
*
* @api public
*/
process(elem: Element, options: CanonicalizationOrTransformationAlgorithmProcessOptions): string {
options = options || {};
let inclusiveNamespacesPrefixList = options.inclusiveNamespacesPrefixList || [];
const defaultNs = options.defaultNs || "";
const defaultNsForPrefix = options.defaultNsForPrefix || {};
const ancestorNamespaces = options.ancestorNamespaces || [];
/**
* If the inclusiveNamespacesPrefixList has not been explicitly provided then look it up in CanonicalizationMethod/InclusiveNamespaces
*/
if (!utils.isArrayHasLength(inclusiveNamespacesPrefixList)) {
const CanonicalizationMethod = utils.findChildren(elem, "CanonicalizationMethod");
if (CanonicalizationMethod.length !== 0) {
const inclusiveNamespaces = utils.findChildren(
CanonicalizationMethod[0],
"InclusiveNamespaces",
);
if (inclusiveNamespaces.length !== 0) {
inclusiveNamespacesPrefixList = (
inclusiveNamespaces[0].getAttribute("PrefixList") || ""
).split(" ");
}
}
}
/**
* If you have a PrefixList then use it and the ancestors to add the necessary namespaces
*/
if (utils.isArrayHasLength(inclusiveNamespacesPrefixList)) {
inclusiveNamespacesPrefixList.forEach(function (prefix) {
if (ancestorNamespaces) {
ancestorNamespaces.forEach(function (ancestorNamespace) {
if (prefix === ancestorNamespace.prefix) {
elem.setAttributeNS(
"http://www.w3.org/2000/xmlns/",
`xmlns:${prefix}`,
ancestorNamespace.namespaceURI,
);
}
});
}
});
}
const res = this.processInner(
elem,
[],
defaultNs,
defaultNsForPrefix,
inclusiveNamespacesPrefixList,
);
return res;
}
getAlgorithmName() {
return "http://www.w3.org/2001/10/xml-exc-c14n#";
}
}
export class ExclusiveCanonicalizationWithComments extends ExclusiveCanonicalization {
constructor() {
super();
this.includeComments = true;
}
getAlgorithmName() {
return "http://www.w3.org/2001/10/xml-exc-c14n#WithComments";
}
}
================================================
FILE: src/hash-algorithms.ts
================================================
import * as crypto from "crypto";
import type { HashAlgorithm } from "./types";
export class Sha1 implements HashAlgorithm {
getHash = function (xml) {
const shasum = crypto.createHash("sha1");
shasum.update(xml, "utf8");
const res = shasum.digest("base64");
return res;
};
getAlgorithmName = function () {
return "http://www.w3.org/2000/09/xmldsig#sha1";
};
}
export class Sha256 implements HashAlgorithm {
getHash = function (xml) {
const shasum = crypto.createHash("sha256");
shasum.update(xml, "utf8");
const res = shasum.digest("base64");
return res;
};
getAlgorithmName = function () {
return "http://www.w3.org/2001/04/xmlenc#sha256";
};
}
export class Sha512 implements HashAlgorithm {
getHash = function (xml) {
const shasum = crypto.createHash("sha512");
shasum.update(xml, "utf8");
const res = shasum.digest("base64");
return res;
};
getAlgorithmName = function () {
return "http://www.w3.org/2001/04/xmlenc#sha512";
};
}
================================================
FILE: src/index.ts
================================================
export { C14nCanonicalization, C14nCanonicalizationWithComments } from "./c14n-canonicalization";
export {
ExclusiveCanonicalization,
ExclusiveCanonicalizationWithComments,
} from "./exclusive-canonicalization";
export { SignedXml } from "./signed-xml";
export * from "./types";
export * from "./utils";
================================================
FILE: src/signature-algorithms.ts
================================================
import * as crypto from "crypto";
import { type SignatureAlgorithm, createOptionalCallbackFunction } from "./types";
export class RsaSha1 implements SignatureAlgorithm {
getSignature = createOptionalCallbackFunction(
(signedInfo: crypto.BinaryLike, privateKey: crypto.KeyLike): string => {
const signer = crypto.createSign("RSA-SHA1");
signer.update(signedInfo);
const res = signer.sign(privateKey, "base64");
return res;
},
);
verifySignature = createOptionalCallbackFunction(
(material: string, key: crypto.KeyLike, signatureValue: string): boolean => {
const verifier = crypto.createVerify("RSA-SHA1");
verifier.update(material);
const res = verifier.verify(key, signatureValue, "base64");
return res;
},
);
getAlgorithmName = () => {
return "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
};
}
export class RsaSha256 implements SignatureAlgorithm {
getSignature = createOptionalCallbackFunction(
(signedInfo: crypto.BinaryLike, privateKey: crypto.KeyLike): string => {
const signer = crypto.createSign("RSA-SHA256");
signer.update(signedInfo);
const res = signer.sign(privateKey, "base64");
return res;
},
);
verifySignature = createOptionalCallbackFunction(
(material: string, key: crypto.KeyLike, signatureValue: string): boolean => {
const verifier = crypto.createVerify("RSA-SHA256");
verifier.update(material);
const res = verifier.verify(key, signatureValue, "base64");
return res;
},
);
getAlgorithmName = () => {
return "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
};
}
export class RsaSha256Mgf1 implements SignatureAlgorithm {
getSignature = createOptionalCallbackFunction(
(signedInfo: crypto.BinaryLike, privateKey: crypto.KeyLike): string => {
if (!(typeof privateKey === "string" || Buffer.isBuffer(privateKey))) {
throw new Error("keys must be strings or buffers");
}
const signer = crypto.createSign("RSA-SHA256");
signer.update(signedInfo);
const res = signer.sign(
{
key: privateKey,
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
saltLength: crypto.constants.RSA_PSS_SALTLEN_DIGEST,
},
"base64",
);
return res;
},
);
verifySignature = createOptionalCallbackFunction(
(material: string, key: crypto.KeyLike, signatureValue: string): boolean => {
if (!(typeof key === "string" || Buffer.isBuffer(key))) {
throw new Error("keys must be strings or buffers");
}
const verifier = crypto.createVerify("RSA-SHA256");
verifier.update(material);
const res = verifier.verify(
{
key: key,
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
saltLength: crypto.constants.RSA_PSS_SALTLEN_DIGEST,
},
signatureValue,
"base64",
);
return res;
},
);
getAlgorithmName = () => {
return "http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1";
};
}
export class RsaSha512 implements SignatureAlgorithm {
getSignature = createOptionalCallbackFunction(
(signedInfo: crypto.BinaryLike, privateKey: crypto.KeyLike): string => {
const signer = crypto.createSign("RSA-SHA512");
signer.update(signedInfo);
const res = signer.sign(privateKey, "base64");
return res;
},
);
verifySignature = createOptionalCallbackFunction(
(material: string, key: crypto.KeyLike, signatureValue: string): boolean => {
const verifier = crypto.createVerify("RSA-SHA512");
verifier.update(material);
const res = verifier.verify(key, signatureValue, "base64");
return res;
},
);
getAlgorithmName = () => {
return "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512";
};
}
export class HmacSha1 implements SignatureAlgorithm {
getSignature = createOptionalCallbackFunction(
(signedInfo: crypto.BinaryLike, privateKey: crypto.KeyLike): string => {
const signer = crypto.createHmac("SHA1", privateKey);
signer.update(signedInfo);
const res = signer.digest("base64");
return res;
},
);
verifySignature = createOptionalCallbackFunction(
(material: string, key: crypto.KeyLike, signatureValue: string): boolean => {
const verifier = crypto.createHmac("SHA1", key);
verifier.update(material);
const res = verifier.digest("base64");
// Use constant-time comparison to prevent timing attacks (CWE-208)
// See: https://github.com/node-saml/xml-crypto/issues/522
try {
return crypto.timingSafeEqual(
Buffer.from(res, "base64"),
Buffer.from(signatureValue, "base64"),
);
} catch (e) {
// timingSafeEqual throws if buffer lengths don't match
return false;
}
},
);
getAlgorithmName = () => {
return "http://www.w3.org/2000/09/xmldsig#hmac-sha1";
};
}
================================================
FILE: src/signed-xml.ts
================================================
import type {
CanonicalizationAlgorithmType,
CanonicalizationOrTransformAlgorithmType,
CanonicalizationOrTransformationAlgorithm,
CanonicalizationOrTransformationAlgorithmProcessOptions,
ComputeSignatureOptions,
ErrorFirstCallback,
GetKeyInfoContentArgs,
HashAlgorithm,
HashAlgorithmType,
ObjectAttributes,
Reference,
SignatureAlgorithm,
SignatureAlgorithmType,
SignedXmlOptions,
} from "./types";
import * as isDomNode from "@xmldom/is-dom-node";
import * as xmldom from "@xmldom/xmldom";
import * as crypto from "crypto";
import { deprecate } from "util";
import * as xpath from "xpath";
import * as c14n from "./c14n-canonicalization";
import * as envelopedSignatures from "./enveloped-signature";
import * as execC14n from "./exclusive-canonicalization";
import * as hashAlgorithms from "./hash-algorithms";
import * as signatureAlgorithms from "./signature-algorithms";
import * as utils from "./utils";
export class SignedXml {
idMode?: "wssecurity";
idAttributes: string[];
/**
* A {@link Buffer} or pem encoded {@link String} containing your private key
*/
privateKey?: crypto.KeyLike;
publicCert?: crypto.KeyLike;
/**
* One of the supported signature algorithms.
* @see {@link SignatureAlgorithmType}
*/
signatureAlgorithm?: SignatureAlgorithmType = undefined;
/**
* Rules used to convert an XML document into its canonical form.
*/
canonicalizationAlgorithm?: CanonicalizationAlgorithmType = undefined;
/**
* It specifies a list of namespace prefixes that should be considered "inclusive" during the canonicalization process.
*/
inclusiveNamespacesPrefixList: string[] = [];
namespaceResolver: XPathNSResolver = {
lookupNamespaceURI: function (/* prefix */) {
throw new Error("Not implemented");
},
};
implicitTransforms: ReadonlyArray<CanonicalizationOrTransformAlgorithmType> = [];
keyInfoAttributes: { [attrName: string]: string } = {};
getKeyInfoContent = SignedXml.getKeyInfoContent;
getCertFromKeyInfo = SignedXml.getCertFromKeyInfo;
objects?: Array<{ content: string; attributes?: ObjectAttributes }>;
// Internal state
private id = 0;
private signedXml = "";
private signatureXml = "";
private signatureNode: Node | null = null;
private signatureValue = "";
private originalXmlWithIds = "";
private keyInfo: Node | null = null;
/**
* Contains the references that were signed.
* @see {@link Reference}
*/
private references: Reference[] = [];
/**
* Contains the canonicalized XML of the references that were validly signed.
*
* This populates with the canonical XML of the reference only after
* verifying the signature is cryptographically authentic.
*/
private signedReferences: string[] = [];
/**
* To add a new transformation algorithm create a new class that implements the {@link TransformationAlgorithm} interface, and register it here. More info: {@link https://github.com/node-saml/xml-crypto#customizing-algorithms|Customizing Algorithms}
*/
CanonicalizationAlgorithms: Record<
CanonicalizationOrTransformAlgorithmType,
new () => CanonicalizationOrTransformationAlgorithm
> = {
"http://www.w3.org/TR/2001/REC-xml-c14n-20010315": c14n.C14nCanonicalization,
"http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments":
c14n.C14nCanonicalizationWithComments,
"http://www.w3.org/2001/10/xml-exc-c14n#": execC14n.ExclusiveCanonicalization,
"http://www.w3.org/2001/10/xml-exc-c14n#WithComments":
execC14n.ExclusiveCanonicalizationWithComments,
"http://www.w3.org/2000/09/xmldsig#enveloped-signature": envelopedSignatures.EnvelopedSignature,
};
// TODO: In v7.x we may consider deprecating sha1
/**
* To add a new hash algorithm create a new class that implements the {@link HashAlgorithm} interface, and register it here. More info: {@link https://github.com/node-saml/xml-crypto#customizing-algorithms|Customizing Algorithms}
*/
HashAlgorithms: Record<HashAlgorithmType, new () => HashAlgorithm> = {
"http://www.w3.org/2000/09/xmldsig#sha1": hashAlgorithms.Sha1,
"http://www.w3.org/2001/04/xmlenc#sha256": hashAlgorithms.Sha256,
"http://www.w3.org/2001/04/xmlenc#sha512": hashAlgorithms.Sha512,
};
// TODO: In v7.x we may consider deprecating sha1
/**
* To add a new signature algorithm create a new class that implements the {@link SignatureAlgorithm} interface, and register it here. More info: {@link https://github.com/node-saml/xml-crypto#customizing-algorithms|Customizing Algorithms}
*/
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/2007/05/xmldsig-more#sha256-rsa-MGF1": signatureAlgorithms.RsaSha256Mgf1,
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha512": signatureAlgorithms.RsaSha512,
// Disabled by default due to key confusion concerns.
// 'http://www.w3.org/2000/09/xmldsig#hmac-sha1': SignatureAlgorithms.HmacSha1
};
static defaultNsForPrefix = {
ds: "http://www.w3.org/2000/09/xmldsig#",
};
static noop = () => null;
/**
* The SignedXml constructor provides an abstraction for sign and verify xml documents. The object is constructed using
* @param options {@link SignedXmlOptions}
*/
constructor(options: SignedXmlOptions = {}) {
const {
idMode,
idAttribute,
privateKey,
publicCert,
signatureAlgorithm,
canonicalizationAlgorithm,
inclusiveNamespacesPrefixList,
implicitTransforms,
keyInfoAttributes,
getKeyInfoContent,
getCertFromKeyInfo,
objects,
} = options;
// Options
this.idMode = idMode;
this.idAttributes = ["Id", "ID", "id"];
if (idAttribute) {
this.idAttributes.unshift(idAttribute);
}
this.privateKey = privateKey;
this.publicCert = publicCert;
this.signatureAlgorithm = signatureAlgorithm ?? this.signatureAlgorithm;
this.canonicalizationAlgorithm = canonicalizationAlgorithm;
if (typeof inclusiveNamespacesPrefixList === "string") {
this.inclusiveNamespacesPrefixList = inclusiveNamespacesPrefixList.split(" ");
} else if (utils.isArrayHasLength(inclusiveNamespacesPrefixList)) {
this.inclusiveNamespacesPrefixList = inclusiveNamespacesPrefixList;
}
this.implicitTransforms = implicitTransforms ?? this.implicitTransforms;
this.keyInfoAttributes = keyInfoAttributes ?? this.keyInfoAttributes;
this.getKeyInfoContent = getKeyInfoContent ?? this.getKeyInfoContent;
this.getCertFromKeyInfo = getCertFromKeyInfo ?? SignedXml.noop;
this.objects = objects;
this.CanonicalizationAlgorithms;
this.HashAlgorithms;
this.SignatureAlgorithms;
}
/**
* Due to key-confusion issues, it's risky to have both hmac
* and digital signature algorithms enabled at the same time.
* This enables HMAC and disables other signing algorithms.
*/
enableHMAC(): void {
this.SignatureAlgorithms = {
"http://www.w3.org/2000/09/xmldsig#hmac-sha1": signatureAlgorithms.HmacSha1,
};
this.getKeyInfoContent = SignedXml.noop;
}
/**
* Builds the contents of a KeyInfo element as an XML string.
*
* For example, if the value of the prefix argument is 'foo', then
* the resultant XML string will be "<foo:X509Data></foo:X509Data>"
*
* @return an XML string representation of the contents of a KeyInfo element, or `null` if no `KeyInfo` element should be included
*/
static getKeyInfoContent({ publicCert, prefix }: GetKeyInfoContentArgs): string | null {
if (publicCert == null) {
return null;
}
prefix = prefix ? `${prefix}:` : "";
let x509Certs = "";
if (Buffer.isBuffer(publicCert)) {
publicCert = publicCert.toString("latin1");
}
let publicCertMatches: string[] = [];
if (typeof publicCert === "string") {
publicCertMatches = publicCert.match(utils.EXTRACT_X509_CERTS) || [];
}
if (publicCertMatches.length > 0) {
x509Certs = publicCertMatches
.map(
(c) =>
`<${prefix}X509Certificate>${utils
.pemToDer(c)
.toString("base64")}</${prefix}X509Certificate>`,
)
.join("");
}
return `<${prefix}X509Data>${x509Certs}</${prefix}X509Data>`;
}
/**
* Returns the value of the signing certificate based on the contents of the
* specified KeyInfo.
*
* @param keyInfo KeyInfo element (@see https://www.w3.org/TR/2008/REC-xmldsig-core-20080610/#sec-X509Data)
* @return the signing certificate as a string in PEM format
*/
static getCertFromKeyInfo(keyInfo?: Node | null): string | null {
if (keyInfo != null) {
const cert = xpath.select1(".//*[local-name(.)='X509Certificate']", keyInfo);
if (isDomNode.isNodeLike(cert)) {
return utils.derToPem(cert.textContent ?? "", "CERTIFICATE");
}
}
return null;
}
/**
* Validates the signature of the provided XML document synchronously using the configured key info provider.
*
* @param xml The XML document containing the signature to be validated.
* @returns `true` if the signature is valid
* @throws Error if no key info resolver is provided.
*/
checkSignature(xml: string): boolean;
/**
* Validates the signature of the provided XML document synchronously using the configured key info provider.
*
* @param xml The XML document containing the signature to be validated.
* @param callback Callback function to handle the validation result asynchronously.
* @throws Error if the last parameter is provided and is not a function, or if no key info resolver is provided.
*/
checkSignature(xml: string, callback: (error: Error | null, isValid?: boolean) => void): void;
checkSignature(
xml: string,
callback?: (error: Error | null, isValid?: boolean) => void,
): unknown {
if (callback != null && typeof callback !== "function") {
throw new Error("Last parameter must be a callback function");
}
this.signedXml = xml;
const doc = new xmldom.DOMParser().parseFromString(xml);
// Reset the references as only references from our re-parsed signedInfo node can be trusted
this.references = [];
const unverifiedSignedInfoCanon = this.getCanonSignedInfoXml(doc);
if (!unverifiedSignedInfoCanon) {
if (callback) {
callback(new Error("Canonical signed info cannot be empty"), false);
return;
}
throw new Error("Canonical signed info cannot be empty");
}
// unsigned, verify later to keep with consistent callback behavior
const parsedUnverifiedSignedInfo = new xmldom.DOMParser().parseFromString(
unverifiedSignedInfoCanon,
"text/xml",
);
const unverifiedSignedInfoDoc = parsedUnverifiedSignedInfo.documentElement;
if (!unverifiedSignedInfoDoc) {
if (callback) {
callback(new Error("Could not parse unverifiedSignedInfoCanon into a document"), false);
return;
}
throw new Error("Could not parse unverifiedSignedInfoCanon into a document");
}
const references = utils.findChildren(unverifiedSignedInfoDoc, "Reference");
if (!utils.isArrayHasLength(references)) {
if (callback) {
callback(new Error("could not find any Reference elements"), false);
return;
}
throw new Error("could not find any Reference elements");
}
// TODO: In a future release we'd like to load the Signature and its References at the same time,
// however, in the `.loadSignature()` method we don't have the entire document,
// which we need to to keep the inclusive namespaces
for (const reference of references) {
this.loadReference(reference);
}
/* eslint-disable-next-line deprecation/deprecation */
if (!this.getReferences().every((ref) => this.validateReference(ref, doc))) {
/* Trustworthiness can only be determined if SignedInfo's (which holds References' DigestValue(s)
which were validated at this stage) signature is valid. Execution does not proceed to validate
signature phase thus each References' DigestValue must be considered to be untrusted (attacker
might have injected any data with new new references and/or recalculated new DigestValue for
altered Reference(s)). Returning any content via `signedReferences` would give false sense of
trustworthiness if/when SignedInfo's (which holds references' DigestValues) signature is not
valid(ated). Put simply: if one fails, they are all not trustworthy.
*/
this.signedReferences = [];
this.references.forEach((ref) => {
ref.signedReference = undefined;
});
// TODO: add this breaking change here later on for even more security: `this.references = [];`
if (callback) {
callback(new Error("Could not validate all references"), false);
return;
}
// We return false because some references validated, but not all
// We should actually be throwing an error here, but that would be a breaking change
// See https://www.w3.org/TR/xmldsig-core/#sec-CoreValidation
return false;
}
// (Stage B authentication step, show that the `signedInfoCanon` is signed)
// First find the key & signature algorithm, these should match
// Stage B: Take the signature algorithm and key and verify the `SignatureValue` against the canonicalized `SignedInfo`
const signer = this.findSignatureAlgorithm(this.signatureAlgorithm);
const key = this.getCertFromKeyInfo(this.keyInfo) || this.publicCert || this.privateKey;
if (key == null) {
throw new Error("KeyInfo or publicCert or privateKey is required to validate signature");
}
// Check the signature verification to know whether to reset signature value or not.
const sigRes = signer.verifySignature(unverifiedSignedInfoCanon, key, this.signatureValue);
if (sigRes === true) {
if (callback) {
callback(null, true);
} else {
return true;
}
} else {
// Ideally, we would start by verifying the `signedInfoCanon` first,
// but that may cause some breaking changes, so we'll handle that in v7.x.
// If we were validating `signedInfoCanon` first, we wouldn't have to reset this array.
this.signedReferences = [];
this.references.forEach((ref) => {
ref.signedReference = undefined;
});
// TODO: add this breaking change here later on for even more security: `this.references = [];`
if (callback) {
callback(
new Error(`invalid signature: the signature value ${this.signatureValue} is incorrect`),
);
return; // return early
} else {
throw new Error(
`invalid signature: the signature value ${this.signatureValue} is incorrect`,
);
}
}
}
private getCanonSignedInfoXml(doc: Document) {
if (this.signatureNode == null) {
throw new Error("No signature found.");
}
if (typeof this.canonicalizationAlgorithm !== "string") {
throw new Error("Missing canonicalizationAlgorithm when trying to get signed info for XML");
}
const signedInfo = utils.findChildren(this.signatureNode, "SignedInfo");
if (signedInfo.length === 0) {
throw new Error("could not find SignedInfo element in the message");
}
if (signedInfo.length > 1) {
throw new Error(
"could not get canonicalized signed info for a signature that contains multiple SignedInfo nodes",
);
}
if (
this.canonicalizationAlgorithm === "http://www.w3.org/TR/2001/REC-xml-c14n-20010315" ||
this.canonicalizationAlgorithm ===
"http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
) {
if (!doc || typeof doc !== "object") {
throw new Error(
"When canonicalization method is non-exclusive, whole xml dom must be provided as an argument",
);
}
}
/**
* Search for ancestor namespaces before canonicalization.
*/
const ancestorNamespaces = utils.findAncestorNs(doc, "//*[local-name()='SignedInfo']");
const c14nOptions = {
ancestorNamespaces: ancestorNamespaces,
};
return this.getCanonXml([this.canonicalizationAlgorithm], signedInfo[0], c14nOptions);
}
private getCanonReferenceXml(doc: Document, ref: Reference, node: Node) {
/**
* Search for ancestor namespaces before canonicalization.
*/
if (Array.isArray(ref.transforms)) {
ref.ancestorNamespaces = utils.findAncestorNs(doc, ref.xpath, this.namespaceResolver);
}
const c14nOptions = {
inclusiveNamespacesPrefixList: ref.inclusiveNamespacesPrefixList,
ancestorNamespaces: ref.ancestorNamespaces,
};
return this.getCanonXml(ref.transforms, node, c14nOptions);
}
private calculateSignatureValue(doc: Document, callback?: ErrorFirstCallback<string>) {
const signedInfoCanon = this.getCanonSignedInfoXml(doc);
const signer = this.findSignatureAlgorithm(this.signatureAlgorithm);
if (this.privateKey == null) {
throw new Error("Private key is required to compute signature");
}
if (typeof callback === "function") {
signer.getSignature(signedInfoCanon, this.privateKey, callback);
} else {
this.signatureValue = signer.getSignature(signedInfoCanon, this.privateKey);
}
}
private findSignatureAlgorithm(name?: SignatureAlgorithmType) {
if (name == null) {
throw new Error("signatureAlgorithm is required");
}
const algo = this.SignatureAlgorithms[name];
if (algo) {
return new algo();
} else {
throw new Error(`signature algorithm '${name}' is not supported`);
}
}
private findCanonicalizationAlgorithm(name: CanonicalizationOrTransformAlgorithmType) {
if (name != null) {
const algo = this.CanonicalizationAlgorithms[name];
if (algo) {
return new algo();
}
}
throw new Error(`canonicalization algorithm '${name}' is not supported`);
}
private findHashAlgorithm(name: HashAlgorithmType) {
const algo = this.HashAlgorithms[name];
if (algo) {
return new algo();
} else {
throw new Error(`hash algorithm '${name}' is not supported`);
}
}
validateElementAgainstReferences(elemOrXpath: Element | string, doc: Document): Reference {
let elem: Element;
if (typeof elemOrXpath === "string") {
const firstElem = xpath.select1(elemOrXpath, doc);
isDomNode.assertIsElementNode(firstElem);
elem = firstElem;
} else {
elem = elemOrXpath;
}
/* eslint-disable-next-line deprecation/deprecation */
for (const ref of this.getReferences()) {
const uri = ref.uri?.[0] === "#" ? ref.uri.substring(1) : ref.uri;
for (const attr of this.idAttributes) {
const elemId = elem.getAttribute(attr);
if (uri === elemId) {
ref.xpath = `//*[@*[local-name(.)='${attr}']='${uri}']`;
break; // found the correct element, no need to check further
}
}
const canonXml = this.getCanonReferenceXml(doc, ref, elem);
const hash = this.findHashAlgorithm(ref.digestAlgorithm);
const digest = hash.getHash(canonXml);
if (utils.validateDigestValue(digest, ref.digestValue)) {
return ref;
}
}
throw new Error("No references passed validation");
}
private validateReference(ref: Reference, doc: Document) {
const uri = ref.uri?.[0] === "#" ? ref.uri.substring(1) : ref.uri;
let elem: xpath.SelectSingleReturnType = null;
if (uri === "") {
elem = xpath.select1("//*", doc);
} else if (uri?.indexOf("'") !== -1) {
// xpath injection
throw new Error("Cannot validate a uri with quotes inside it");
} else {
let num_elements_for_id = 0;
for (const attr of this.idAttributes) {
const tmp_elemXpath = `//*[@*[local-name(.)='${attr}']='${uri}']`;
const tmp_elem = xpath.select(tmp_elemXpath, doc);
if (utils.isArrayHasLength(tmp_elem)) {
num_elements_for_id += tmp_elem.length;
if (num_elements_for_id > 1) {
throw new Error(
"Cannot validate a document which contains multiple elements with the " +
"same value for the ID / Id / Id attributes, in order to prevent " +
"signature wrapping attack.",
);
}
elem = tmp_elem[0];
ref.xpath = tmp_elemXpath;
}
}
}
ref.getValidatedNode = deprecate((xpathSelector?: string) => {
xpathSelector = xpathSelector || ref.xpath;
if (typeof xpathSelector !== "string" || ref.validationError != null) {
return null;
}
const selectedValue = xpath.select1(xpathSelector, doc);
return isDomNode.isNodeLike(selectedValue) ? selectedValue : null;
}, "`ref.getValidatedNode()` is deprecated and insecure. Use `ref.signedReference` or `this.getSignedReferences()` instead.");
if (!isDomNode.isNodeLike(elem)) {
const validationError = new Error(
`invalid signature: the signature references an element with uri ${ref.uri} but could not find such element in the xml`,
);
ref.validationError = validationError;
return false;
}
const canonXml = this.getCanonReferenceXml(doc, ref, elem);
const hash = this.findHashAlgorithm(ref.digestAlgorithm);
const digest = hash.getHash(canonXml);
if (!utils.validateDigestValue(digest, ref.digestValue)) {
const validationError = new Error(
`invalid signature: for uri ${ref.uri} calculated digest is ${digest} but the xml to validate supplies digest ${ref.digestValue}`,
);
ref.validationError = validationError;
return false;
}
// This step can only be done after we have verified the `signedInfo`.
// We verified that they have same hash,
// thus the `canonXml` and _only_ the `canonXml` can be trusted.
// Append this to `signedReferences`.
this.signedReferences.push(canonXml);
ref.signedReference = canonXml;
return true;
}
findSignatures(doc: Node): Node[] {
const nodes = xpath.select(
"//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']",
doc,
);
return isDomNode.isArrayOfNodes(nodes) ? nodes : [];
}
/**
* Loads the signature information from the provided XML node or string.
*
* @param signatureNode The XML node or string representing the signature.
*/
loadSignature(signatureNode: Node | string): void {
if (typeof signatureNode === "string") {
this.signatureNode = signatureNode = new xmldom.DOMParser().parseFromString(signatureNode);
} else {
this.signatureNode = signatureNode;
}
this.signatureXml = signatureNode.toString();
const node = xpath.select1(
".//*[local-name(.)='CanonicalizationMethod']/@Algorithm",
signatureNode,
);
if (!isDomNode.isNodeLike(node)) {
throw new Error("could not find CanonicalizationMethod/@Algorithm element");
}
if (isDomNode.isAttributeNode(node)) {
this.canonicalizationAlgorithm = node.value as CanonicalizationAlgorithmType;
}
const signatureAlgorithm = xpath.select1(
".//*[local-name(.)='SignatureMethod']/@Algorithm",
signatureNode,
);
if (isDomNode.isAttributeNode(signatureAlgorithm)) {
this.signatureAlgorithm = signatureAlgorithm.value as SignatureAlgorithmType;
}
const signedInfoNodes = utils.findChildren(this.signatureNode, "SignedInfo");
if (!utils.isArrayHasLength(signedInfoNodes)) {
throw new Error("no signed info node found");
}
if (signedInfoNodes.length > 1) {
throw new Error("could not load signature that contains multiple SignedInfo nodes");
}
// Try to operate on the c14n version of `signedInfo`. This forces the initial `getReferences()`
// API call to always return references that are loaded under the canonical `SignedInfo`
// in the case that the client access the `.references` **before** signature verification.
// Ensure canonicalization algorithm is exclusive, otherwise we'd need the entire document
let canonicalizationAlgorithmForSignedInfo = this.canonicalizationAlgorithm;
if (
!canonicalizationAlgorithmForSignedInfo ||
canonicalizationAlgorithmForSignedInfo ===
"http://www.w3.org/TR/2001/REC-xml-c14n-20010315" ||
canonicalizationAlgorithmForSignedInfo ===
"http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
) {
canonicalizationAlgorithmForSignedInfo = "http://www.w3.org/2001/10/xml-exc-c14n#";
}
const temporaryCanonSignedInfo = this.getCanonXml(
[canonicalizationAlgorithmForSignedInfo],
signedInfoNodes[0],
);
const temporaryCanonSignedInfoXml = new xmldom.DOMParser().parseFromString(
temporaryCanonSignedInfo,
"text/xml",
);
const signedInfoDoc = temporaryCanonSignedInfoXml.documentElement;
this.references = [];
const references = utils.findChildren(signedInfoDoc, "Reference");
if (!utils.isArrayHasLength(references)) {
throw new Error("could not find any Reference elements");
}
for (const reference of references) {
this.loadReference(reference);
}
const signatureValue = xpath.select1(
".//*[local-name(.)='SignatureValue']/text()",
signatureNode,
);
if (isDomNode.isTextNode(signatureValue)) {
this.signatureValue = signatureValue.data.replace(/\r?\n/g, "");
}
const keyInfo = xpath.select1(".//*[local-name(.)='KeyInfo']", signatureNode);
if (isDomNode.isNodeLike(keyInfo)) {
this.keyInfo = keyInfo;
}
}
/**
* Load the reference xml node to a model
*
*/
private loadReference(refNode: Node) {
let nodes = utils.findChildren(refNode, "DigestMethod");
if (nodes.length === 0) {
throw new Error(`could not find DigestMethod in reference ${refNode.toString()}`);
}
const digestAlgoNode = nodes[0];
const attr = utils.findAttr(digestAlgoNode, "Algorithm");
if (!attr) {
throw new Error(`could not find Algorithm attribute in node ${digestAlgoNode.toString()}`);
}
const digestAlgo = attr.value;
nodes = utils.findChildren(refNode, "DigestValue");
if (nodes.length === 0) {
throw new Error(`could not find DigestValue node in reference ${refNode.toString()}`);
}
if (nodes.length > 1) {
throw new Error(
`could not load reference for a node that contains multiple DigestValue nodes: ${refNode.toString()}`,
);
}
const digestValue = nodes[0].textContent;
if (!digestValue) {
throw new Error(`could not find the value of DigestValue in ${refNode.toString()}`);
}
const transforms: string[] = [];
let inclusiveNamespacesPrefixList: string[] = [];
nodes = utils.findChildren(refNode, "Transforms");
if (nodes.length !== 0) {
const transformsNode = nodes[0];
const transformsAll = utils.findChildren(transformsNode, "Transform");
for (const transform of transformsAll) {
const transformAttr = utils.findAttr(transform, "Algorithm");
if (transformAttr) {
transforms.push(transformAttr.value);
}
}
// This is a little strange, we are looking for children of the last child of `transformsNode`
const inclusiveNamespaces = utils.findChildren(
transformsAll[transformsAll.length - 1],
"InclusiveNamespaces",
);
if (utils.isArrayHasLength(inclusiveNamespaces)) {
// Should really only be one prefix list, but maybe there's some circumstances where more than one to let's handle it
inclusiveNamespacesPrefixList = inclusiveNamespaces
.flatMap((namespace) => (namespace.getAttribute("PrefixList") ?? "").split(" "))
.filter((value) => value.length > 0);
}
}
if (utils.isArrayHasLength(this.implicitTransforms)) {
this.implicitTransforms.forEach(function (t) {
transforms.push(t);
});
}
/**
* DigestMethods take an octet stream rather than a node set. If the output of the last transform is a node set, we
* need to canonicalize the node set to an octet stream using non-exclusive canonicalization. If there are no
* transforms, we need to canonicalize because URI dereferencing for a same-document reference will return a node-set.
* @see:
* https://www.w3.org/TR/xmldsig-core1/#sec-DigestMethod
* https://www.w3.org/TR/xmldsig-core1/#sec-ReferenceProcessingModel
* https://www.w3.org/TR/xmldsig-core1/#sec-Same-Document
*/
if (
transforms.length === 0 ||
transforms[transforms.length - 1] === "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
) {
transforms.push("http://www.w3.org/TR/2001/REC-xml-c14n-20010315");
}
const refUri = isDomNode.isElementNode(refNode)
? refNode.getAttribute("URI") || undefined
: undefined;
this.addReference({
transforms,
digestAlgorithm: digestAlgo,
uri: refUri,
digestValue,
inclusiveNamespacesPrefixList,
isEmptyUri: false,
});
}
/**
* Adds a reference to the signature.
*
* @param xpath The XPath expression to select the XML nodes to be referenced.
* @param transforms An array of transform algorithms to be applied to the selected nodes.
* @param digestAlgorithm The digest algorithm to use for computing the digest value.
* @param uri The URI identifier for the reference. If empty, an empty URI will be used.
* @param digestValue The expected digest value for the reference.
* @param inclusiveNamespacesPrefixList The prefix list for inclusive namespace canonicalization.
* @param isEmptyUri Indicates whether the URI is empty. Defaults to `false`.
* @param id An optional `Id` attribute for the reference.
* @param type An optional `Type` attribute for the reference.
*/
addReference({
xpath,
transforms,
digestAlgorithm,
uri = "",
digestValue,
inclusiveNamespacesPrefixList = [],
isEmptyUri = false,
id = undefined,
type = undefined,
}: Partial<Reference> & Pick<Reference, "xpath">): void {
if (digestAlgorithm == null) {
throw new Error("digestAlgorithm is required");
}
if (!utils.isArrayHasLength(transforms)) {
throw new Error("transforms must contain at least one transform algorithm");
}
this.references.push({
xpath,
transforms,
digestAlgorithm,
uri,
digestValue,
inclusiveNamespacesPrefixList,
isEmptyUri,
id,
type,
getValidatedNode: () => {
throw new Error(
"Reference has not been validated yet; Did you call `sig.checkSignature()`?",
);
},
});
}
/**
* Returns the list of references.
*/
getReferences() {
// TODO: Refactor once `getValidatedNode` is removed
/* Once we completely remove the deprecated `getValidatedNode()` method,
we can change this to return a clone to prevent accidental mutations,
e.g.:
return [...this.references];
*/
return this.references;
}
getSignedReferences() {
return [...this.signedReferences];
}
/**
* Compute the signature of the given XML (using the already defined settings).
*
* @param xml The XML to compute the signature for.
* @param callback A callback function to handle the signature computation asynchronously.
* @returns void
* @throws TypeError If the xml can not be parsed.
*/
computeSignature(xml: string): void;
/**
* Compute the signature of the given XML (using the already defined settings).
*
* @param xml The XML to compute the signature for.
* @param callback A callback function to handle the signature computation asynchronously.
* @returns void
* @throws TypeError If the xml can not be parsed.
*/
computeSignature(xml: string, callback: ErrorFirstCallback<SignedXml>): void;
/**
* Compute the signature of the given XML (using the already defined settings).
*
* @param xml The XML to compute the signature for.
* @param opts An object containing options for the signature computation.
* @returns If no callback is provided, returns `this` (the instance of SignedXml).
* @throws TypeError If the xml can not be parsed, or Error if there were invalid options passed.
*/
computeSignature(xml: string, options: ComputeSignatureOptions): void;
/**
* Compute the signature of the given XML (using the already defined settings).
*
* @param xml The XML to compute the signature for.
* @param opts An object containing options for the signature computation.
* @param callback A callback function to handle the signature computation asynchronously.
* @returns void
* @throws TypeError If the xml can not be parsed, or Error if there were invalid options passed.
*/
computeSignature(
xml: string,
options: ComputeSignatureOptions,
callback: ErrorFirstCallback<SignedXml>,
): void;
computeSignature(
xml: string,
options?: ComputeSignatureOptions | ErrorFirstCallback<SignedXml>,
callbackParam?: ErrorFirstCallback<SignedXml>,
): void {
let callback: ErrorFirstCallback<SignedXml>;
if (typeof options === "function" && callbackParam == null) {
callback = options as ErrorFirstCallback<SignedXml>;
options = {} as ComputeSignatureOptions;
} else {
callback = callbackParam as ErrorFirstCallback<SignedXml>;
options = (options ?? {}) as ComputeSignatureOptions;
}
const doc = new xmldom.DOMParser().parseFromString(xml);
let xmlNsAttr = "xmlns";
const signatureAttrs: string[] = [];
let currentPrefix: string;
const validActions = ["append", "prepend", "before", "after"];
const prefix = options.prefix;
const attrs = options.attrs || {};
const location = options.location || {};
const existingPrefixes = options.existingPrefixes || {};
this.namespaceResolver = {
lookupNamespaceURI: function (prefix) {
return prefix ? existingPrefixes[prefix] : null;
},
};
// defaults to the root node
location.reference = location.reference || "/*";
// defaults to append action
location.action = location.action || "append";
if (validActions.indexOf(location.action) === -1) {
const err = new Error(
`location.action option has an invalid action: ${
location.action
}, must be any of the following values: ${validActions.join(", ")}`,
);
if (!callback) {
throw err;
} else {
callback(err);
return;
}
}
// Add IDs for all non-self references upfront
for (const ref of this.getReferences()) {
if (ref.isEmptyUri) {
continue;
} // No specific nodes to ID for empty URI
const nodes = xpath.selectWithResolver(
ref.xpath ?? "",
doc,
this.namespaceResolver,
) as Element[];
for (const node of nodes) {
isDomNode.assertIsElementNode(node);
this.ensureHasId(node);
}
}
// Capture original with IDs (no sig yet)
this.originalXmlWithIds = doc.toString();
// automatic insertion of `:`
if (prefix) {
xmlNsAttr += `:${prefix}`;
currentPrefix = `${prefix}:`;
} else {
currentPrefix = "";
}
Object.keys(attrs).forEach(function (name) {
if (name !== "xmlns" && name !== xmlNsAttr) {
signatureAttrs.push(`${name}="${attrs[name]}"`);
}
});
// add the xml namespace attribute
signatureAttrs.push(`${xmlNsAttr}="http://www.w3.org/2000/09/xmldsig#"`);
let signatureXml = `<${currentPrefix}Signature ${signatureAttrs.join(" ")}>`;
signatureXml += this.createSignedInfo(doc, prefix);
signatureXml += this.getKeyInfo(prefix);
signatureXml += this.getObjects(prefix);
signatureXml += `</${currentPrefix}Signature>`;
let existingPrefixesString = "";
Object.keys(existingPrefixes).forEach(function (key) {
existingPrefixesString += `xmlns:${key}="${existingPrefixes[key]}" `;
});
// A trick to remove the namespaces that already exist in the xml
// This only works if the prefix and namespace match with those in the xml
const dummySignatureWrapper = `<Dummy ${existingPrefixesString}>${signatureXml}</Dummy>`;
const nodeXml = new xmldom.DOMParser().parseFromString(dummySignatureWrapper);
// Because we are using a dummy wrapper hack described above, we know there will be a `firstChild`
// and that it will be an `Element` node.
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const signatureElem = nodeXml.documentElement.firstChild! as Element;
const referenceNode = xpath.select1(location.reference, doc);
if (!isDomNode.isNodeLike(referenceNode)) {
const err2 = new Error(
`the following xpath cannot be used because it was not found: ${location.reference}`,
);
if (!callback) {
throw err2;
} else {
callback(err2);
return;
}
}
if (location.action === "append") {
referenceNode.appendChild(signatureElem);
} else if (location.action === "prepend") {
referenceNode.insertBefore(signatureElem, referenceNode.firstChild);
} else if (location.action === "before") {
if (referenceNode.parentNode == null) {
throw new Error(
"`location.reference` refers to the root node (by default), so we can't insert `before`",
);
}
referenceNode.parentNode.insertBefore(signatureElem, referenceNode);
} else if (location.action === "after") {
if (referenceNode.parentNode == null) {
throw new Error(
"`location.reference` refers to the root node (by default), so we can't insert `after`",
);
}
referenceNode.parentNode.insertBefore(signatureElem, referenceNode.nextSibling);
}
// Now add all references (including any to the signature itself)
this.addAllReferences(doc, signatureElem, prefix);
this.signatureNode = signatureElem;
const signedInfoNodes = utils.findChildren(this.signatureNode, "SignedInfo");
if (signedInfoNodes.length === 0) {
const err3 = new Error("could not find SignedInfo element in the message");
if (!callback) {
throw err3;
} else {
callback(err3);
return;
}
}
const signedInfoNode = signedInfoNodes[0];
if (typeof callback === "function") {
// Asynchronous flow
this.calculateSignatureValue(doc, (err, signature) => {
if (err) {
callback(err);
} else {
this.signatureValue = signature || "";
signatureElem.insertBefore(this.createSignature(prefix), signedInfoNode.nextSibling);
this.signatureXml = signatureElem.toString();
this.signedXml = doc.toString();
callback(null, this);
}
});
} else {
// Synchronous flow
this.calculateSignatureValue(doc);
signatureElem.insertBefore(this.createSignature(prefix), signedInfoNode.nextSibling);
this.signatureXml = signatureElem.toString();
this.signedXml = doc.toString();
}
}
/**
* Adds all references to the SignedInfo after the signature placeholder is inserted.
*/
private addAllReferences(doc: Document, signatureElem: Element, prefix?: string): void {
if (!utils.isArrayHasLength(this.references)) {
return;
}
const currentPrefix = prefix ? `${prefix}:` : "";
const signatureNamespace = "http://www.w3.org/2000/09/xmldsig#";
// Find the SignedInfo element to append to
const signedInfoNode = xpath.select1(`./*[local-name(.)='SignedInfo']`, signatureElem);
isDomNode.assertIsElementNode(signedInfoNode); // Type-safe assertion
// Signature document is technically the same document as the one we are signing,
// but we will extract it here for clarity (and also make it support detached signatures in the future)
const signatureDoc = signatureElem.ownerDocument;
// Process each reference
for (const ref of this.getReferences()) {
const nodes = xpath.selectWithResolver(ref.xpath ?? "", doc, this.namespaceResolver);
if (!utils.isArrayHasLength(nodes)) {
throw new Error(
`the following xpath cannot be signed because it was not found: ${ref.xpath}`,
);
}
// Process the reference
for (const node of nodes) {
isDomNode.assertIsElementNode(node);
// Must not be a reference to Signature, SignedInfo, or a child of SignedInfo
if (
node === signatureElem ||
node === signedInfoNode ||
utils.isDescendantOf(node, signedInfoNode)
) {
throw new Error(
`Cannot sign a reference to the Signature or SignedInfo element itself: ${ref.xpath}`,
);
}
// Compute the target URI (ID already ensured earlier, extract it)
let targetUri: string;
if (ref.isEmptyUri) {
targetUri = "";
} else {
const id = this.ensureHasId(node);
ref.uri = id;
targetUri = `#${id}`;
}
// Create the reference element directly using DOM methods to avoid namespace issues
const referenceElem = signatureDoc.createElementNS(
signatureNamespace,
`${currentPrefix}Reference`,
);
referenceElem.setAttribute("URI", targetUri);
if (ref.id) {
referenceElem.setAttribute("Id", ref.id);
}
if (ref.type) {
referenceElem.setAttribute("Type", ref.type);
}
const transformsElem = signatureDoc.createElementNS(
signatureNamespace,
`${currentPrefix}Transforms`,
);
for (const trans of ref.transforms || []) {
const transform = this.findCanonicalizationAlgorithm(trans);
const transformElem = signatureDoc.createElementNS(
signatureNamespace,
`${currentPrefix}Transform`,
);
transformElem.setAttribute("Algorithm", transform.getAlgorithmName());
if (utils.isArrayHasLength(ref.inclusiveNamespacesPrefixList)) {
const inclusiveNamespacesElem = signatureDoc.createElementNS(
transform.getAlgorithmName(),
"InclusiveNamespaces",
);
inclusiveNamespacesElem.setAttribute(
"PrefixList",
ref.inclusiveNamespacesPrefixList.join(" "),
);
transformElem.appendChild(inclusiveNamespacesElem);
}
transformsElem.appendChild(transformElem);
}
// Get the canonicalized XML
const canonXml = this.getCanonReferenceXml(doc, ref, node);
// Get the digest algorithm and compute the digest value
const digestAlgorithm = this.findHashAlgorithm(ref.digestAlgorithm);
const digestMethodElem = signatureDoc.createElementNS(
signatureNamespace,
`${currentPrefix}DigestMethod`,
);
digestMethodElem.setAttribute("Algorithm", digestAlgorithm.getAlgorithmName());
const digestValueElem = signatureDoc.createElementNS(
signatureNamespace,
`${currentPrefix}DigestValue`,
);
digestValueElem.textContent = digestAlgorithm.getHash(canonXml);
referenceElem.appendChild(transformsElem);
referenceElem.appendChild(digestMethodElem);
referenceElem.appendChild(digestValueElem);
// Append the reference element to SignedInfo
signedInfoNode.appendChild(referenceElem);
}
}
}
private getKeyInfo(prefix) {
const currentPrefix = prefix ? `${prefix}:` : "";
let keyInfoAttrs = "";
if (this.keyInfoAttributes) {
Object.keys(this.keyInfoAttributes).forEach((name) => {
keyInfoAttrs += ` ${name}="${this.keyInfoAttributes[name]}"`;
});
}
const keyInfoContent = this.getKeyInfoContent({ publicCert: this.publicCert, prefix });
if (keyInfoAttrs || keyInfoContent) {
return `<${currentPrefix}KeyInfo${keyInfoAttrs}>${keyInfoContent}</${currentPrefix}KeyInfo>`;
}
return "";
}
/**
* Creates XML for Object elements to be included in the signature
*
* @param prefix Optional namespace prefix
* @returns XML string with Object elements or empty string if none
*/
private getObjects(prefix?: string) {
const currentPrefix = prefix ? `${prefix}:` : "";
if (!this.objects || this.objects.length === 0) {
return "";
}
let result = "";
for (const obj of this.objects) {
let objectAttrs = "";
if (obj.attributes) {
Object.keys(obj.attributes).forEach((name) => {
const value = obj.attributes?.[name];
if (value !== undefined) {
objectAttrs += ` ${name}="${value}"`;
}
});
}
result += `<${currentPrefix}Object${objectAttrs}>${obj.content}</${currentPrefix}Object>`;
}
return result;
}
getCanonXml(
transforms: Reference["transforms"],
node: Node,
options: CanonicalizationOrTransformationAlgorithmProcessOptions = {},
) {
options.defaultNsForPrefix = options.defaultNsForPrefix ?? SignedXml.defaultNsForPrefix;
options.signatureNode = this.signatureNode;
const canonXml = node.cloneNode(true); // Deep clone
let transformedXml: Node | string = canonXml;
transforms.forEach((transformName) => {
if (isDomNode.isNodeLike(transformedXml)) {
// If, after processing, `transformedNode` is a string, we can't do anymore transforms on it
const transform = this.findCanonicalizationAlgorithm(transformName);
transformedXml = transform.process(transformedXml, options);
}
//TODO: currently transform.process may return either Node or String value (enveloped transformation returns Node, exclusive-canonicalization returns String).
//This either needs to be more explicit in the API, or all should return the same.
//exclusive-canonicalization returns String since it builds the Xml by hand. If it had used xmldom it would incorrectly minimize empty tags
//to <x/> instead of <x></x> and also incorrectly handle some delicate line break issues.
//enveloped transformation returns Node since if it would return String consider this case:
//<x xmlns:p='ns'><p:y/></x>
//if only y is the node to sign then a string would be <p:y/> without the definition of the p namespace. probably xmldom toString() should have added it.
});
return transformedXml.toString();
}
/**
* Ensure an element has Id attribute. If not create it with unique value.
* Work with both normal and wssecurity Id flavour
*/
private ensureHasId(node) {
let attr;
if (this.idMode === "wssecurity") {
attr = utils.findAttr(
node,
"Id",
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd",
);
} else {
this.idAttributes.some((idAttribute) => {
attr = utils.findAttr(node, idAttribute);
return !!attr; // This will break the loop as soon as a truthy attr is found.
});
}
if (attr) {
return attr.value;
}
//add the attribute
const id = `_${this.id++}`;
if (this.idMode === "wssecurity") {
node.setAttributeNS(
"http://www.w3.org/2000/xmlns/",
"xmlns:wsu",
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd",
);
node.setAttributeNS(
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd",
"wsu:Id",
id,
);
} else {
node.setAttribute("Id", id);
}
return id;
}
/**
* Create the SignedInfo element
*
*/
private createSignedInfo(doc, prefix) {
if (typeof this.canonicalizationAlgorithm !== "string") {
throw new Error(
"Missing canonicalizationAlgorithm when trying to create signed info for XML",
);
}
const transform = this.findCanonicalizationAlgorithm(this.canonicalizationAlgorithm);
const algo = this.findSignatureAlgorithm(this.signatureAlgorithm);
const currentPrefix = prefix ? `${prefix}:` : "";
let res = `<${currentPrefix}SignedInfo>`;
res += `<${currentPrefix}CanonicalizationMethod Algorithm="${transform.getAlgorithmName()}"`;
if (utils.isArrayHasLength(this.inclusiveNamespacesPrefixList)) {
res += ">";
res += `<InclusiveNamespaces PrefixList="${this.inclusiveNamespacesPrefixList.join(
" ",
)}" xmlns="${transform.getAlgorithmName()}"/>`;
res += `</${currentPrefix}CanonicalizationMethod>`;
} else {
res += " />";
}
res += `<${currentPrefix}SignatureMethod Algorithm="${algo.getAlgorithmName()}" />`;
// No references here - added later
res += `</${currentPrefix}SignedInfo>`;
return res;
}
/**
* Create the Signature element
*
*/
private createSignature(prefix?: string) {
let xmlNsAttr = "xmlns";
if (prefix) {
xmlNsAttr += `:${prefix}`;
prefix += ":";
} else {
prefix = "";
}
const signatureValueXml = `<${prefix}SignatureValue>${this.signatureValue}</${prefix}SignatureValue>`;
//the canonicalization requires to get a valid xml node.
//we need to wrap the info in a dummy signature since it contains the default namespace.
const dummySignatureWrapper = `<${prefix}Signature ${xmlNsAttr}="http://www.w3.org/2000/09/xmldsig#">${signatureValueXml}</${prefix}Signature>`;
const doc = new xmldom.DOMParser().parseFromString(dummySignatureWrapper);
// Because we are using a dummy wrapper hack described above, we know there will be a `firstChild`
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return doc.documentElement.firstChild!;
}
/**
* Returns just the signature part, must be called only after {@link computeSignature}
*
* @returns The signature XML.
*/
getSignatureXml(): string {
return this.signatureXml;
}
/**
* Returns the original xml with Id attributes added on relevant elements (required for validation), must be called only after {@link computeSignature}
*
* @returns The original XML with IDs.
*/
getOriginalXmlWithIds(): string {
return this.originalXmlWithIds;
}
/**
* Returns the original xml document with the signature in it, must be called only after {@link computeSignature}
*
* @returns The signed XML.
*/
getSignedXml(): string {
return this.signedXml;
}
}
================================================
FILE: src/types.ts
================================================
/* eslint-disable no-unused-vars */
// Type definitions for @node-saml/xml-crypto
// Project: https://github.com/node-saml/xml-crypto#readme
// Original definitions by: Eric Heikes <https://github.com/eheikes>
// Max Chehab <https://github.com/maxchehab>
/// <reference types="node" />
import * as crypto from "crypto";
export type ErrorFirstCallback<T> = (err: Error | null, result?: T) => void;
export type CanonicalizationAlgorithmType =
| "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"
| "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
| "http://www.w3.org/2001/10/xml-exc-c14n#"
| "http://www.w3.org/2001/10/xml-exc-c14n#WithComments"
| string;
export type CanonicalizationOrTransformAlgorithmType =
| CanonicalizationAlgorithmType
| "http://www.w3.org/2000/09/xmldsig#enveloped-signature";
export type HashAlgorithmType =
| "http://www.w3.org/2000/09/xmldsig#sha1"
| "http://www.w3.org/2001/04/xmlenc#sha256"
| "http://www.w3.org/2001/04/xmlenc#sha512"
| string;
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/2007/05/xmldsig-more#sha256-rsa-MGF1"
| "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"
| "http://www.w3.org/2000/09/xmldsig#hmac-sha1"
| string;
/**
* @param cert the certificate as a string or array of strings (@see https://www.w3.org/TR/2008/REC-xmldsig-core-20080610/#sec-X509Data)
* @param prefix an optional namespace alias to be used for the generated XML
*/
export interface GetKeyInfoContentArgs {
publicCert?: crypto.KeyLike;
prefix?: string | null;
}
/**
* Object attributes as defined in XMLDSig spec and are emitted verbatim
* @see https://www.w3.org/TR/xmldsig-core/#sec-Object
*/
export interface ObjectAttributes {
/** Optional ID attribute */
Id?: string;
/** Optional MIME type attribute */
MimeType?: string;
/** Optional encoding attribute */
Encoding?: string;
/** Any additional custom attributes */
[key: string]: string | undefined;
}
/**
* Options for the SignedXml constructor.
*/
export interface SignedXmlOptions {
idMode?: "wssecurity";
idAttribute?: string;
privateKey?: crypto.KeyLike;
publicCert?: crypto.KeyLike;
signatureAlgorithm?: SignatureAlgorithmType;
canonicalizationAlgorithm?: CanonicalizationAlgorithmType;
inclusiveNamespacesPrefixList?: string | string[];
implicitTransforms?: ReadonlyArray<CanonicalizationOrTransformAlgorithmType>;
keyInfoAttributes?: Record<string, string>;
getKeyInfoContent?(args?: GetKeyInfoContentArgs): string | null;
getCertFromKeyInfo?(keyInfo?: Node | null): string | null;
objects?: Array<{ content: string; attributes?: ObjectAttributes }>;
}
export interface NamespacePrefix {
prefix: string;
namespaceURI: string;
}
export interface RenderedNamespace {
rendered: string;
newDefaultNs: string;
}
export interface CanonicalizationOrTransformationAlgorithmProcessOptions {
defaultNs?: string;
defaultNsForPrefix?: Record<string, string>;
ancestorNamespaces?: NamespacePrefix[];
signatureNode?: Node | null;
inclusiveNamespacesPrefixList?: string[];
}
export interface ComputeSignatureOptionsLocation {
reference?: string;
action?: "append" | "prepend" | "before" | "after";
}
/**
* Options for the computeSignature method.
*
* - `prefix` {String} Adds a prefix for the generated signature tags
* - `attrs` {Object} A hash of attributes and values `attrName: value` to add to the signature root node
* - `location` {{ reference: String, action: String }}
* - `existingPrefixes` {Object} A hash of prefixes and namespaces `prefix: namespace` already in the xml
* An object with a `reference` key which should
* contain a XPath expression, an `action` key which
* should contain one of the following values:
* `append`, `prepend`, `before`, `after`
*/
export interface ComputeSignatureOptions {
prefix?: string;
attrs?: Record<string, string>;
location?: ComputeSignatureOptionsLocation;
existingPrefixes?: Record<string, string>;
}
/**
* Represents a reference node for XML digital signature.
*/
export interface Reference {
// The XPath expression that selects the data to be signed.
xpath?: string;
// An array of transforms to be applied to the data before signing.
transforms: ReadonlyArray<CanonicalizationOrTransformAlgorithmType>;
// The algorithm used to calculate the digest value of the data.
digestAlgorithm: HashAlgorithmType;
// The URI that identifies the data to be signed.
uri: string;
// Optional. The digest value of the referenced data.
digestValue?: unknown;
// A list of namespace prefixes to be treated as "inclusive" during canonicalization.
inclusiveNamespacesPrefixList: string[];
// Optional. Indicates whether the URI is empty.
isEmptyUri: boolean;
// Optional. The `Id` attribute of the reference node.
id?: string;
// Optional. The `Type` attribute of the reference node.
type?: string;
// Optional. The type of the reference node.
ancestorNamespaces?: NamespacePrefix[];
validationError?: Error;
getValidatedNode(xpathSelector?: string): Node | null;
signedReference?: string;
}
/** Implement this to create a new CanonicalizationOrTransformationAlgorithm */
export interface CanonicalizationOrTransformationAlgorithm {
process(
node: Node,
options: CanonicalizationOrTransformationAlgorithmProcessOptions,
): Node | string;
getAlgorithmName(): CanonicalizationOrTransformAlgorithmType;
}
/** Implement this to create a new HashAlgorithm */
export interface HashAlgorithm {
getAlgorithmName(): HashAlgorithmType;
getHash(xml: string): string;
}
/** Extend this to create a new SignatureAlgorithm */
export interface SignatureAlgorithm {
/**
* Sign the given string using the given key
*/
getSignature(signedInfo: crypto.BinaryLike, privateKey: crypto.KeyLike): string;
getSignature(
signedInfo: crypto.BinaryLike,
privateKey: crypto.KeyLike,
callback?: ErrorFirstCallback<string>,
): void;
/**
* Verify the given signature of the given string using key
*
* @param key a public cert, public key, or private key can be passed here
*/
verifySignature(material: string, key: crypto.KeyLike, signatureValue: string): boolean;
verifySignature(
material: string,
key: crypto.KeyLike,
signatureValue: string,
callback?: ErrorFirstCallback<boolean>,
): void;
getAlgorithmName(): SignatureAlgorithmType;
}
/** Implement this to create a new TransformAlgorithm */
export interface TransformAlgorithm {
getAlgorithmName(): CanonicalizationOrTransformAlgorithmType;
process(node: Node): string;
}
/**
* ### Sign
* #### Properties
* - {@link SignedXml#privateKey} [required]
* - {@link SignedXml#publicCert} [optional]
* - {@link SignedXml#signatureAlgorithm} [optional]
* - {@link SignedXml#canonicalizationAlgorithm} [optional]
* #### Api
* - {@link SignedXml#addReference}
* - {@link SignedXml#computeSignature}
* - {@link SignedXml#getSignedXml}
* - {@link SignedXml#getSignatureXml}
* - {@link SignedXml#getOriginalXmlWithIds}
*
* ### Verify
* #### Properties
* - {@link SignedXml#publicCert} [optional]
* #### Api
* - {@link SignedXml#loadSignature}
* - {@link SignedXml#checkSignature}
*/
function isErrorFirstCallback<T>(
possibleCallback: unknown,
): possibleCallback is ErrorFirstCallback<T> {
return typeof possibleCallback === "function";
}
/**
* This function will add a callback version of a sync function.
*
* This follows the factory pattern.
* Just call this function, passing the function that you'd like to add a callback version of.
*/
export function createOptionalCallbackFunction<T, A extends unknown[]>(
syncVersion: (...args: A) => T,
): {
(...args: A): T;
(...args: [...A, ErrorFirstCallback<T>]): void;
} {
return ((...args: A | [...A, ErrorFirstCallback<T>]) => {
const possibleCallback = args[args.length - 1];
if (isErrorFirstCallback(possibleCallback)) {
try {
const result = syncVersion(...(args.slice(0, -1) as A));
possibleCallback(null, result);
} catch (err) {
possibleCallback(err instanceof Error ? err : new Error("Unknown error"));
}
} else {
return syncVersion(...(args as A));
}
}) as {
(...args: A): T;
(...args: [...A, ErrorFirstCallback<T>]): void;
};
}
================================================
FILE: src/utils.ts
================================================
import * as xpath from "xpath";
import type { NamespacePrefix } from "./types";
import * as isDomNode from "@xmldom/is-dom-node";
export function isArrayHasLength(array: unknown): array is unknown[] {
return Array.isArray(array) && array.length > 0;
}
function attrEqualsExplicitly(attr: Attr, localName: string, namespace?: string) {
return attr.localName === localName && (attr.namespaceURI === namespace || namespace == null);
}
function attrEqualsImplicitly(attr: Attr, localName: string, namespace?: string, node?: Element) {
return (
attr.localName === localName &&
((!attr.namespaceURI && node?.namespaceURI === namespace) || namespace == null)
);
}
export function findAttr(element: Element, localName: string, namespace?: string) {
for (let i = 0; i < element.attributes.length; i++) {
const attr = element.attributes[i];
if (
attrEqualsExplicitly(attr, localName, namespace) ||
attrEqualsImplicitly(attr, localName, namespace, element)
) {
return attr;
}
}
return null;
}
export function findChildren(node: Node | Document, localName: string, namespace?: string) {
const element = (node as Document).documentElement ?? node;
const res: Element[] = [];
for (let i = 0; i < element.childNodes.length; i++) {
const child = element.childNodes[i];
if (
isDomNode.isElementNode(child) &&
child.localName === localName &&
(child.namespaceURI === namespace || namespace == null)
) {
res.push(child);
}
}
return res;
}
/** @deprecated */
export function findChilds(node: Node | Document, localName: string, namespace?: string) {
return findChildren(node, localName, namespace);
}
const xml_special_to_encoded_attribute = {
"&": "&",
"<": "<",
'"': """,
"\r": "
",
"\n": "
",
"\t": "	",
};
const xml_special_to_encoded_text = {
"&": "&",
"<": "<",
">": ">",
"\r": "
",
};
export function encodeSpecialCharactersInAttribute(attributeValue) {
return attributeValue.replace(/([&<"\r\n\t])/g, function (str, item) {
/** Special character normalization.
* @see:
* - https://www.w3.org/TR/xml-c14n#ProcessingModel (Attribute Nodes)
* - https://www.w3.org/TR/xml-c14n#Example-Chars
*/
return xml_special_to_encoded_attribute[item];
});
}
export function encodeSpecialCharactersInText(text: string): string {
return text.replace(/([&<>\r])/g, function (str, item) {
/** Special character normalization.
* @see:
* - https://www.w3.org/TR/xml-c14n#ProcessingModel (Text Nodes)
* - https://www.w3.org/TR/xml-c14n#Example-Chars
*/
return xml_special_to_encoded_text[item];
});
}
/**
* PEM format has wide range of usages, but this library
* is enforcing RFC7468 which focuses on PKIX, PKCS and CMS.
*
* https://www.rfc-editor.org/rfc/rfc7468
*
* PEM_FORMAT_REGEX is validating given PEM file against RFC7468 'stricttextualmsg' definition.
*
* With few exceptions;
* - 'posteb' MAY have 'eol', but it is not mandatory.
* - 'preeb' and 'posteb' lines are limited to 64 characters, but
* should not cause any issues in context of PKIX, PKCS and CMS.
*/
export const PEM_FORMAT_REGEX = new RegExp(
"^-----BEGIN [A-Z\x20]{1,48}-----([^-]*)-----END [A-Z\x20]{1,48}-----$",
"s",
);
export const EXTRACT_X509_CERTS = new RegExp(
"-----BEGIN CERTIFICATE-----[^-]*-----END CERTIFICATE-----",
"g",
);
export const BASE64_REGEX = new RegExp(
"^(?:[A-Za-z0-9\\+\\/]{4}\\n{0,1})*(?:[A-Za-z0-9\\+\\/]{2}==|[A-Za-z0-9\\+\\/]{3}=)?$",
"s",
);
/**
* -----BEGIN [LABEL]-----
* base64([DATA])
* -----END [LABEL]-----
*
* Above is shown what PEM file looks like. As can be seen, base64 data
* can be in single line or multiple lines.
*
* This function normalizes PEM presentation to;
* - contain PEM header and footer as they are given
* - normalize line endings to '\n'
* - normalize line length to maximum of 64 characters
* - ensure that 'preeb' has line ending '\n'
*
* With a couple of notes:
* - 'eol' is normalized to '\n'
*
* @param pem The PEM string to normalize to RFC7468 'stricttextualmsg' definition
*/
export function normalizePem(pem: string): string {
return `${(
pem
.trim()
.replace(/(\r\n|\r)/g, "\n")
.match(/.{1,64}/g) ?? []
).join("\n")}\n`;
}
/**
* @param pem The PEM-encoded base64 certificate to strip headers from
*/
export function pemToDer(pem: string): Buffer {
if (!PEM_FORMAT_REGEX.test(pem.trim())) {
throw new Error("Invalid PEM format.");
}
return Buffer.from(
pem
.replace(/(\r\n|\r)/g, "")
.replace(/-----BEGIN [A-Z\x20]{1,48}-----\n?/, "")
.replace(/-----END [A-Z\x20]{1,48}-----\n?/, ""),
"base64",
);
}
/**
* @param der The DER-encoded base64 certificate to add PEM headers too
* @param pemLabel The label of the header and footer to add
*/
export function derToPem(
der: string | Buffer,
pemLabel?: "CERTIFICATE" | "PRIVATE KEY" | "RSA PUBLIC KEY",
): string {
const base64Der = Buffer.isBuffer(der)
? der.toString("base64").trim()
: der.replace(/(\r\n|\r)/g, "").trim();
if (PEM_FORMAT_REGEX.test(base64Der)) {
return normalizePem(base64Der);
}
if (BASE64_REGEX.test(base64Der.replace(/ /g, ""))) {
if (pemLabel == null) {
throw new Error("PEM label is required when DER is given.");
}
const pem = `-----BEGIN ${pemLabel}-----\n${base64Der.replace(
/ /g,
"",
)}\n-----END ${pemLabel}-----`;
return normalizePem(pem);
}
throw new Error("Unknown DER format.");
}
function collectAncestorNamespaces(
node: Element,
nsArray: NamespacePrefix[] = [],
): NamespacePrefix[] {
if (!isDomNode.isElementNode(node.parentNode)) {
return nsArray;
}
const parent: Element = node.parentNode;
if (!parent) {
return nsArray;
}
if (parent.attributes && parent.attributes.length > 0) {
for (let i = 0; i < parent.attributes.length; i++) {
const attr = parent.attributes[i];
if (attr && attr.nodeName && attr.nodeName.search(/^xmlns:?/) !== -1) {
nsArray.push({
prefix: attr.nodeName.replace(/^xmlns:?/, ""),
namespaceURI: attr.nodeValue || "",
});
}
}
}
return collectAncestorNamespaces(parent, nsArray);
}
function findNSPrefix(subset) {
const subsetAttributes = subset.attributes;
for (let k = 0; k < subsetAttributes.length; k++) {
const nodeName = subsetAttributes[k].nodeName;
if (nodeName.search(/^xmlns:?/) !== -1) {
return nodeName.replace(/^xmlns:?/, "");
}
}
return subset.prefix || "";
}
function isElementSubset(docSubset: Node[]): docSubset is Element[] {
return docSubset.every((node) => isDomNode.isElementNode(node));
}
/**
* Extract ancestor namespaces in order to import it to root of document subset
* which is being canonicalized for non-exclusive c14n.
*
* @param doc - Usually a product from `new xmldom.DOMParser().parseFromString()`
* @param docSubsetXpath - xpath query to get document subset being canonicalized
* @param namespaceResolver - xpath namespace resolver
* @returns i.e. [{prefix: "saml", namespaceURI: "urn:oasis:names:tc:SAML:2.0:assertion"}]
*/
export function findAncestorNs(
doc: Document,
docSubsetXpath?: string,
namespaceResolver?: XPathNSResolver,
): NamespacePrefix[] {
if (docSubsetXpath == null) {
return [];
}
const docSubset = xpath.selectWithResolver(docSubsetXpath, doc, namespaceResolver);
if (!isArrayHasLength(docSubset)) {
return [];
}
if (!isElementSubset(docSubset)) {
throw new Error("Document subset must be list of elements");
}
// Remove duplicate on ancestor namespace
const ancestorNs = collectAncestorNamespaces(docSubset[0]);
const ancestorNsWithoutDuplicate: NamespacePrefix[] = [];
for (let i = 0; i < ancestorNs.length; i++) {
let notOnTheList = true;
for (const v in ancestorNsWithoutDuplicate) {
if (ancestorNsWithoutDuplicate[v].prefix === ancestorNs[i].prefix) {
notOnTheList = false;
break;
}
}
if (notOnTheList) {
ancestorNsWithoutDuplicate.push(ancestorNs[i]);
}
}
// Remove namespaces which are already declared in the subset with the same prefix
const returningNs: NamespacePrefix[] = [];
const subsetNsPrefix = findNSPrefix(docSubset[0]);
for (const ancestorNs of ancestorNsWithoutDuplicate) {
if (ancestorNs.prefix !== subsetNsPrefix) {
returningNs.push(ancestorNs);
}
}
return returningNs;
}
export function validateDigestValue(digest, expectedDigest) {
const buffer = Buffer.from(digest, "base64");
const expectedBuffer = Buffer.from(expectedDigest, "base64");
if (typeof buffer.equals === "function") {
return buffer.equals(expectedBuffer);
}
if (buffer.length !== expectedBuffer.length) {
return false;
}
for (let i = 0; i < buffer.length; i++) {
if (buffer[i] !== expectedBuffer[i]) {
return false;
}
}
return true;
}
// Check if the given node is descendant of the given parent node
export function isDescendantOf(node: Node, parent: Node): boolean {
if (!node || !parent) {
return false;
}
let currentNode: Node | null = node.parentNode;
while (currentNode) {
if (currentNode === parent) {
return true;
}
currentNode = currentNode.parentNode;
}
return false;
}
================================================
FILE: test/c14n-non-exclusive-unit-tests.spec.ts
================================================
import { expect } from "chai";
import { C14nCanonicalization } from "../src/c14n-canonicalization";
import * as xmldom from "@xmldom/xmldom";
import * as xpath from "xpath";
import * as utils from "../src/utils";
import * as isDomNode from "@xmldom/is-dom-node";
const test_C14nCanonicalization = function (xml, xpathArg, expected) {
const doc = new xmldom.DOMParser().parseFromString(xml);
const node = xpath.select1(xpathArg, doc);
const can = new C14nCanonicalization();
isDomNode.assertIsNodeLike(node);
const result = can
.process(node, {
ancestorNamespaces: utils.findAncestorNs(doc, xpathArg),
})
.toString();
expect(result).to.equal(expected);
};
const test_findAncestorNs = function (xml, xpath, expected) {
const doc = new xmldom.DOMParser().parseFromString(xml);
const result = utils.findAncestorNs(doc, xpath);
expect(result).to.deep.equal(expected);
};
describe("C14N non-exclusive canonicalization tests", function () {
it("findAncestorNs: Correctly picks up root ancestor namespace", function () {
const xml = "<root xmlns:aaa='bbb'><child1><child2></child2></child1></root>";
const xpath = "/root/child1/child2";
const expected = [{ prefix: "aaa", namespaceURI: "bbb" }];
test_findAncestorNs(xml, xpath, expected);
});
it("findAncestorNs: Correctly picks up intermediate ancestor namespace", function () {
const xml = "<root><child1 xmlns:aaa='bbb'><child2></child2></child1></root>";
const xpath = "/root/child1/child2";
const expected = [{ prefix: "aaa", namespaceURI: "bbb" }];
test_findAncestorNs(xml, xpath, expected);
});
it("findAncestorNs: Correctly picks up multiple ancestor namespaces declared in the one same element", function () {
const xml = "<root xmlns:aaa='bbb' xmlns:ccc='ddd'><child1><child2></child2></child1></root>";
const xpath = "/root/child1/child2";
const expected = [
{ prefix: "aaa", namespaceURI: "bbb" },
{ prefix: "ccc", namespaceURI: "ddd" },
];
test_findAncestorNs(xml, xpath, expected);
});
it("findAncestorNs: Correctly picks up multiple ancestor namespaces scattered among depth", function () {
const xml = "<root xmlns:aaa='bbb'><child1 xmlns:ccc='ddd'><child2></child2></child1></root>";
const xpath = "/root/child1/child2";
const expected = [
{ prefix: "ccc", namespaceURI: "ddd" },
{ prefix: "aaa", namespaceURI: "bbb" },
];
test_findAncestorNs(xml, xpath, expected);
});
it("findAncestorNs: Correctly picks up multiple ancestor namespaces without duplicate", function () {
const xml = "<root xmlns:ccc='bbb'><child1 xmlns:ccc='bbb'><child2></child2></child1></root>";
const xpath = "/root/child1/child2";
const expected = [{ prefix: "ccc", namespaceURI: "bbb" }];
test_findAncestorNs(xml, xpath, expected);
});
it("findAncestorNs: Correctly eliminates duplicate prefix", function () {
const xml = "<root xmlns:ccc='bbb'><child1 xmlns:ccc='AAA'><child2></child2></child1></root>";
const xpath = "/root/child1/child2";
const expected = [{ prefix: "ccc", namespaceURI: "AAA" }];
test_findAncestorNs(xml, xpath, expected);
});
it("findAncestorNs: Exclude namespace which is already declared with same prefix on target node", function () {
const xml =
"<root xmlns:ccc='bbb'><child1 xmlns:ccc='AAA'><child2 xmlns:ccc='AAA'></child2></child1></root>";
const xpath = "/root/child1/child2";
const expected = [];
test_findAncestorNs(xml, xpath, expected);
});
it("findAncestorNs: Ignores namespace declared in the target xpath node", function () {
const xml = "<root xmlns:aaa='bbb'><child1><child2 xmlns:ccc='ddd'></child2></child1></root>";
const xpath = "/root/child1/child2";
const expected = [{ prefix: "aaa", namespaceURI: "bbb" }];
test_findAncestorNs(xml, xpath, expected);
});
it("findAncestorNs: Should find namespace without prefix", function () {
const xml =
"<root xmlns='bbb'><child1><ds:child2 xmlns:ds='ddd'><ds:child3></ds:child3></ds:child2></child1></root>";
const xpath = "//*[local-name()='child2']";
const expected = [{ prefix: "", namespaceURI: "bbb" }];
test_findAncestorNs(xml, xpath, expected);
});
it("findAncestorNs: Should not find namespace when both has no prefix", function () {
const xml = "<root xmlns='bbb'><child1><child2 xmlns='ddd'></child2></child1></root>";
const xpath = "//*[local-name()='child2']";
const expected = [];
test_findAncestorNs(xml, xpath, expected);
});
// Tests for c14nCanonicalization
it("C14n: Correctly picks up root ancestor namespace", function () {
const xml = "<root xmlns:aaa='bbb'><child1><child2></child2></child1></root>";
const xpath = "/root/child1/child2";
const expected = '<child2 xmlns:aaa="bbb"></child2>';
test_C14nCanonicalization(xml, xpath, expected);
});
it("C14n: Correctly picks up intermediate ancestor namespace", function () {
const xml = "<root><child1 xmlns:aaa='bbb'><child2></child2></child1></root>";
const xpath = "/root/child1/child2";
const expected = '<child2 xmlns:aaa="bbb"></child2>';
test_C14nCanonicalization(xml, xpath, expected);
});
it("C14n: Correctly picks up multiple ancestor namespaces declared in the one same element", function () {
const xml = "<root xmlns:aaa='bbb' xmlns:ccc='ddd'><child1><child2></child2></child1></root>";
const xpath = "/root/child1/child2";
const expected = '<child2 xmlns:aaa="bbb" xmlns:ccc="ddd"></child2>';
test_C14nCanonicalization(xml, xpath, expected);
});
it("C14n: Correctly picks up multiple ancestor namespaces scattered among depth", function () {
const xml = "<root xmlns:aaa='bbb'><child1 xmlns:ccc='ddd'><child2></child2></child1></root>";
const xpath = "/root/child1/child2";
const expected = '<child2 xmlns:aaa="bbb" xmlns:ccc="ddd"></child2>';
test_C14nCanonicalization(xml, xpath, expected);
});
it("C14n: Correctly picks up multiple ancestor namespaces without duplicate", function () {
const xml = "<root xmlns:ccc='bbb'><child1 xmlns:ccc='bbb'><child2></child2></child1></root>";
const xpath = "/root/child1/child2";
const expected = '<child2 xmlns:ccc="bbb"></child2>';
test_C14nCanonicalization(xml, xpath, expected);
});
it("C14n: Correctly eliminates duplicate prefix", function () {
const xml = "<root xmlns:ccc='bbb'><child1 xmlns:ccc='AAA'><child2></child2></child1></root>";
const xpath = "/root/child1/child2";
const expected = '<child2 xmlns:ccc="AAA"></child2>';
test_C14nCanonicalization(xml, xpath, expected);
});
it("C14n: Exclude namespace which is already declared with same prefix on target node", function () {
const xml =
"<root xmlns:ccc='bbb'><child1 xmlns:ccc='AAA'><child2 xmlns:ccc='AAA'></child2></child1></root>";
const xpath = "/root/child1/child2";
const expected = '<child2 xmlns:ccc="AAA"></child2>';
test_C14nCanonicalization(xml, xpath, expected);
});
it("C14n: Preserve namespace declared in the target xpath node", function () {
const xml = '<root xmlns:aaa="bbb"><child1><child2 xmlns:ccc="ddd"></child2></child1></root>';
const xpath = "/root/child1/child2";
const expected = '<child2 xmlns:aaa="bbb" xmlns:ccc="ddd"></child2>';
test_C14nCanonicalization(xml, xpath, expected);
});
it("C14n: Don't redeclare an attribute's namespace prefix if already in scope", function () {
const xml =
"<root xmlns:aaa='bbb'><child1><child2 xmlns:aaa='bbb' aaa:foo='bar'></child2></child1></root>";
const xpath = "/root/child1/child2";
const expected = '<child2 xmlns:aaa="bbb" aaa:foo="bar"></child2>';
test_C14nCanonicalization(xml, xpath, expected);
});
it("C14n: Don't declare an attribute's namespace prefix if in scope from parent", function () {
const xml =
"<root xmlns:aaa='bbb'><child1><child2><child3 aaa:foo='bar'></child3></child2></child1></root>";
const xpath = "/root/child1";
const expected =
'<child1 xmlns:aaa="bbb"><child2><child3 aaa:foo="bar"></child3></child2></child1>';
test_C14nCanonicalization(xml, xpath, expected);
});
it("C14n: should not has colon when parent namespace has no prefix", function () {
const xml =
"<root xmlns='bbb'><child1><cc:child2 xmlns:cc='ddd'><cc:child3></cc:child3></cc:child2></child1></root>";
const xpath = "//*[local-name()='child3']";
const expected = '<cc:child3 xmlns="bbb" xmlns:cc="ddd"></cc:child3>';
test_C14nCanonicalization(xml, xpath, expected);
});
});
================================================
FILE: test/c14nWithComments-unit-tests.spec.ts
================================================
import { expect } from "chai";
import { ExclusiveCanonicalizationWithComments as c14nWithComments } from "../src/exclusive-canonicalization";
import * as xmldom from "@xmldom/xmldom";
import * as xpath from "xpath";
import { SignedXml } from "../src/index";
import * as isDomNode from "@xmldom/is-dom-node";
const compare = function (xml, xpathArg, expected, inclusiveNamespacesPrefixList?: string[]) {
const doc = new xmldom.DOMParser().parseFromString(xml);
const elem = xpath.select1(xpathArg, doc);
const can = new c14nWithComments();
isDomNode.assertIsElementNode(elem);
const result = can.process(elem, { inclusiveNamespacesPrefixList }).toString();
expect(result).to.equal(expected);
};
describe("Exclusive canonicalization with comments", function () {
it("Exclusive canonicalization works on xml with no namespaces", function () {
compare("<root><child>123</child></root>", "//*", "<root><child>123</child></root>");
});
it("Exclusive canonicalization works on inner xpath", function () {
compare("<root><child>123</child></root>", "//*[local-name(.)='child']", "<child>123</child>");
});
it("Exclusive canonicalization works on xml with prefixed namespaces defined in output nodes", function () {
compare(
'<root><p:child xmlns:p="s"><inner>123</inner></p:child></root>',
"//*[local-name(.)='child']",
'<p:child xmlns:p="s"><inner>123</inner></p:child>',
);
});
it("element used prefixed ns which is also the default", function () {
compare(
'<root><child xmlns="s"><p:inner xmlns:p="s">123</p:inner></child></root>',
"//*[local-name(.)='child']",
'<child xmlns="s"><p:inner xmlns:p="s">123</p:inner></child>',
);
});
it("Exclusive canonicalization works on xml with prefixed namespaces defined in output nodes. ns definition is not duplicated on each usage", function () {
compare(
'<root><p:child xmlns:p="ns"><p:inner>123</p:inner></p:child></root>',
"//*[local-name(.)='child']",
'<p:child xmlns:p="ns"><p:inner>123</p:inner></p:child>',
);
});
it("Exclusive canonicalization works on xml with prefixed namespaces defined in output nodes but before used", function () {
compare(
'<root><child xmlns:p="ns"><p:inner>123</p:inner></child></root>',
"//*[local-name(.)='child']",
'<child><p:inner xmlns:p="ns">123</p:inner></child>',
);
});
it("Exclusive canonicalization works on xml with prefixed namespaces defined outside output nodes", function () {
compare(
'<root xmlns:p="ns"><p:child>123</p:child></root>',
"//*[local-name(.)='child']",
'<p:child xmlns:p="ns">123</p:child>',
);
});
it("Exclusive canonicalization works on xml with prefixed namespace defined in inclusive list", function () {
compare(
'<root xmlns:p="ns"><p:child xmlns:inclusive="ns2"><inclusive:inner xmlns:inclusive="ns2">123</inclusive:inner></p:child></root>',
"//*[local-name(.)='child']",
'<p:child xmlns:inclusive="ns2" xmlns:p="ns"><inclusive:inner>123</inclusive:inner></p:child>',
["inclusive"],
);
});
it("Exclusive canonicalization works on xml with multiple prefixed namespaces defined in inclusive list", function () {
compare(
'<root xmlns:p="ns"><p:child xmlns:inclusive="ns2" xmlns:inclusive2="ns3"><inclusive:inner xmlns:inclusive="ns2">123</inclusive:inner><inclusive2:inner xmlns:inclusive2="ns3">456</inclusive2:inner></p:child></root>',
"//*[local-name(.)='child']",
'<p:child xmlns:inclusive="ns2" xmlns:inclusive2="ns3" xmlns:p="ns"><inclusive:inner>123</inclusive:inner><inclusive2:inner>456</inclusive2:inner></p:child>',
["inclusive", "inclusive2"],
);
});
it("Exclusive canonicalization works on xml with prefixed namespace defined in inclusive list defined outside output nodes", function () {
compare(
'<root xmlns:p="ns" xmlns:inclusive="ns2"><p:child><inclusive:inner xmlns:inclusive="ns2">123</inclusive:inner></p:child></root>',
"//*[local-name(.)='child']",
'<p:child xmlns:p="ns"><inclusive:inner xmlns:inclusive="ns2">123</inclusive:inner></p:child>',
["inclusive"],
);
});
it("Exclusive canonicalization works on xml with prefixed namespace defined in inclusive list used on attribute", function () {
compare(
'<root xmlns:p="ns"><p:child xmlns:inclusive="ns2"><p:inner foo="inclusive:bar">123</p:inner></p:child></root>',
"//*[local-name(.)='child']",
'<p:child xmlns:inclusive="ns2" xmlns:p="ns"><p:inner foo="inclusive:bar">123</p:inner></p:child>',
["inclusive"],
);
});
it("Exclusive canonicalization works on xml with default namespace inside output nodes", function () {
compare(
'<root><child><inner xmlns="ns">123</inner></child></root>',
"//*[local-name(.)='child']",
'<child><inner xmlns="ns">123</inner></child>',
);
});
it("Exclusive canonicalization works on xml with multiple different default namespaces", function () {
compare(
'<root xmlns="ns1"><child xmlns="ns2"><inner xmlns="ns3">123</inner></child></root>',
"//*[local-name(.)='child']",
'<child xmlns="ns2"><inner xmlns="ns3">123</inner></child>',
);
});
it("Exclusive canonicalization works on xml with multiple similar default namespaces", function () {
compare(
'<root xmlns="ns1"><child xmlns="ns2"><inner xmlns="ns2">123</inner></child></root>',
"//*[local-name(.)='child']",
'<child xmlns="ns2"><inner>123</inner></child>',
);
});
it("Exclusive canonicalization works on xml with default namespace outside output nodes", function () {
compare(
'<root xmlns="ns"><child><inner>123</inner></child></root>',
"//*[local-name(.)='child']",
'<child xmlns="ns"><inner>123</inner></child>',
);
});
it("Exclusive canonicalization works when prefixed namespace is defined in output nodes not in the parent chain of who needs it", function () {
compare(
'<root><child><p:inner1 xmlns:p="foo" /><p:inner2 xmlns:p="foo" /></child></root>',
"//*[local-name(.)='child']",
'<child><p:inner1 xmlns:p="foo"></p:inner1><p:inner2 xmlns:p="foo"></p:inner2></child>',
);
});
it("Exclusive canonicalization works on xml with unordered attributes", function () {
compare(
'<root><child xmlns:z="ns2" xmlns:p="ns1" p:name="val1" z:someAttr="zval" Id="value" z:testAttr="ztestAttr" someAttr="someAttrVal" p:address="val2"><inner>123</inner></child></root>',
"//*[local-name(.)='child']",
'<child xmlns:p="ns1" xmlns:z="ns2" Id="value" someAttr="someAttrVal" p:address="val2" p:name="val1" z:someAttr="zval" z:testAttr="ztestAttr"><inner>123</inner></child>',
);
});
it("Exclusive canonicalization sorts upper case attributes before lower case", function () {
compare('<x id="" Id=""></x>', "//*[local-name(.)='x']", '<x Id="" id=""></x>');
});
it("C14N#WithComments retains Comments", function () {
compare(
'<x id="" Id=""><!-- Comment --></x>',
"//*[local-name(.)='x']",
'<x Id="" id=""><!-- Comment --></x>',
);
});
it("Exclusive canonicalization works on xml with attributes with different namespace than element", function () {
compare(
'<root><child xmlns="bla" xmlns:p="foo" p:attr="val"><inner>123</inner></child></root>',
"//*[local-name(.)='child']",
'<child xmlns="bla" xmlns:p="foo" p:attr="val"><inner>123</inner></child>',
);
});
it("Exclusive canonicalization works on xml with attribute and element values with special characters", function () {
compare(
'<root><child><inner attr="&11">&11</inner></child></root>',
"//*[local-name(.)='child']",
'<child><inner attr="&11">&11</inner></child>',
);
});
it("Exclusive canonicalization preserves white space in values", function () {
compare(
"<root><child><inner>12\n3\t</inner></child></root>",
"//*[local-name(.)='child']",
"<child><inner>12\n3\t</inner></child>",
);
});
it("Exclusive canonicalization preserves white space between elements", function () {
compare(
"<root><child><inner>123</inner>\n</child></root>",
"//*[local-name(.)='child']",
"<child><inner>123</inner>\n</child>",
);
});
it("Exclusive canonicalization turns empty element to start-end tag pairs", function () {
compare(
"<root><child><inner /></child></root>",
"//*[local-name(.)='child']",
"<child><inner></inner></child>",
);
});
it("Exclusive canonicalization preserves empty start-end tag pairs", function () {
compare(
"<root><child><inner></inner></child></root>",
"//*[local-name(.)='child']",
"<child><inner></inner></child>",
);
});
it("Exclusive canonicalization with empty default namespace outside output nodes", function () {
compare(
'<root xmlns=""><child><inner>123</inner></child></root>',
"//*[local-name(.)='child']",
"<child><inner>123</inner></child>",
);
});
/*
TODO: Uncomment this when this issue is fixed
it("Exclusive canonicalization removal of whitespace between PITarget and its data", function () {
compare(
"<root xmlns=\"\"><child><inner>123</inner></child></root><?pi-without-data ?>",
"//*[local-name(.)='child']",
"<child><inner>123</inner></child><?pi-without-data?>");
});
*/
it("Exclusive canonicalization with empty default namespace inside output nodes", function () {
compare(
'<root xmlns="foo"><child><inner xmlns="">123</inner></child></root>',
"//*[local-name(.)='child']",
'<child xmlns="foo"><inner xmlns="">123</inner></child>',
);
});
it("The XML declaration and document type declaration (DTD) are removed", function () {
compare(
'<?xml version="1.0" encoding="utf-8"?><root><child><inner>123</inner></child></root>',
"//*[local-name(.)='child']",
"<child><inner>123</inner></child>",
);
});
/*
TODO: Uncomment this when this issue is fixed
it("The XML declaration and document type declaration (DTD) are removed, stylesheet retained", function () {
compare(
"<?xml version=\"1.0\" encoding=\"utf-8\"?><?xml-stylesheet href=\"doc.xsl\" type=\"text/xsl\" ?><root><child><inner>123</inner></child></root>",
"//*[local-name(.)='child']",
"<?xml-stylesheet href=\"doc.xsl\" type=\"text/xsl\" ?><child><inner>123</inner></child>");
});
*/
it("Attribute value delimiters are set to quotation marks (double quotes)", function () {
compare(
"<root><child xmlns='ns'><inner attr='value'>123 </inner></child></root>",
"//*[local-name(.)='child']",
'<child xmlns="ns"><inner attr="value">123 </inner></child>',
);
});
it("CDATA sections are replaced with their character content", function () {
compare(
"<root><child><inner><![CDATA[foo & bar in the <x>123</x>]]></inner></child></root>",
"//*[local-name(.)='child']",
"<child><inner>foo & bar in the <x>123</x></inner></child>",
);
});
it("SignedInfo canonization", function () {
compare(
'<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/03/addressing" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"><soap:Header><wsa:Action wsu:Id="Id-fbcf79b7-9c1b-4e51-b3da-7d6c237be1ec">http://stockservice.contoso.com/wse/samples/2003/06/StockQuoteRequest</wsa:Action><wsa:MessageID wsu:Id="Id-02b76fe1-945c-4e26-a8a5-6650285bbd4c">uuid:6250c037-bcde-40ab-82b3-3a08efc86cdc</wsa:MessageID><wsa:ReplyTo wsu:Id="Id-ccc937f4-8ec8-416a-b97b-0b612a69b040"><wsa:Address>http://schemas.xmlsoap.org/ws/2004/03/addressing/role/anonymous</wsa:Address></wsa:ReplyTo><wsa:To wsu:Id="Id-fa48ae82-88bb-4bf1-9c0d-4eb1de66c4fc">http://localhost:8889/</wsa:To><wsse:Security soap:mustUnderstand="1"><wsu:Timestamp wsu:Id="Timestamp-4d2cce4a-39fb-4d7d-b0d5-17d583255ef5"><wsu:Created>2008-09-01T17:44:21Z</wsu:Created><wsu:Expires>2008-09-01T17:49:21Z</wsu:Expires></wsu:Timestamp><wsse:BinarySecurityToken ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="SecurityToken-d68c34d4-be89-4a29-aecc-971bce003ed3">MIIBxDCCAW6gAwIBAgIQxUSXFzWJYYtOZnmmuOMKkjANBgkqhkiG9w0BAQQFADAWMRQwEgYDVQQDEwtSb290IEFnZW5jeTAeFw0wMzA3MDgxODQ3NTlaFw0zOTEyMzEyMzU5NTlaMB8xHTAbBgNVBAMTFFdTRTJRdWlja1N0YXJ0Q2xpZW50MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+L6aB9x928noY4+0QBsXnxkQE4quJl7c3PUPdVu7k9A02hRG481XIfWhrDY5i7OEB7KGW7qFJotLLeMec/UkKUwCgv3VvJrs2nE9xO3SSWIdNzADukYh+Cxt+FUU6tUkDeqg7dqwivOXhuOTRyOI3HqbWTbumaLdc8jufz2LhaQIDAQABo0swSTBHBgNVHQEEQDA+gBAS5AktBh0dTwCNYSHcFmRjoRgwFjEUMBIGA1UEAxMLUm9vdCBBZ2VuY3mCEAY3bACqAGSKEc+41KpcNfQwDQYJKoZIhvcNAQEEBQADQQAfIbnMPVYkNNfX1tG1F+qfLhHwJdfDUZuPyRPucWF5qkh6sSdWVBY5sT/txBnVJGziyO8DPYdu2fPMER8ajJfl</wsse:BinarySecurityToken><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" /><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" /><Reference URI="#Id-fbcf79b7-9c1b-4e51-b3da-7d6c237be1ec"><Transforms><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" /><DigestValue>+465BlJx5xOfHsIFezQt0MS1vZQ=</DigestValue></Reference><Reference URI="#Id-02b76fe1-945c-4e26-a8a5-6650285bbd4c"><Transforms><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" /><DigestValue>jEe8rnaaqBWZQe+xHBQXriVG99o=</DigestValue></Reference><Reference URI="#Id-ccc937f4-8ec8-416a-b97b-0b612a69b040"><Transforms><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" /><DigestValue>W45ginYdBVqOqEaqPI2piZMPReA=</DigestValue></Reference><Reference URI="#Id-fa48ae82-88bb-4bf1-9c0d-4eb1de66c4fc"><Transforms><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" /><DigestValue>m2VlWz/ZDTWL7FREHK+wpKhvjJM=</DigestValue></Reference><Reference URI="#Timestamp-4d2cce4a-39fb-4d7d-b0d5-17d583255ef5"><Transforms><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" /><DigestValue>Qws229qmAzSTZ4OKmAUWgl0PWWo=</DigestValue></Reference><Reference URI="#Id-0175a715-4db3-4886-8af1-991b1472e7f4"><Transforms><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" /><DigestValue>iEazGnkPY5caCWVZOHyR87CZ1h0=</DigestValue></Reference></SignedInfo><SignatureValue>Fkm7AbwiJCiOzY8ldfuA9pTW1G+EtE+UX4Cv7SoMIqeUdfWRDVHZpJAQyf7aoQnlpJNV/3k9L1PT6rJbfV478CkULJENPLm1m0fmDeLzhIHDEANuzp/AirC60tMD5jCARb4B4Nr/6bTmoyDQsTY8VLRiiINng7Mpweg1FZvd8a0=</SignatureValue><KeyInfo><wsse:SecurityTokenReference><wsse:Reference URI="#SecurityToken-d68c34d4-be89-4a29-aecc-971bce003ed3" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" /></wsse:SecurityTokenReference></KeyInfo></Signature></wsse:Security></soap:Header><soap:Body wsu:Id="Id-0175a715-4db3-4886-8af1-991b1472e7f4"><StockQuoteRequest xmlns="http://stockservice.contoso.com/wse/samples/2003/06"><symbols><Symbol>FABRIKAM</Symbol></symbols></StockQuoteRequest></soap:Body></soap:Envelope>',
"//*[local-name(.)='SignedInfo']",
'<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#"><ds:CanonicalizationMethod xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:CanonicalizationMethod><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"></SignatureMethod><Reference URI="#Id-fbcf79b7-9c1b-4e51-b3da-7d6c237be1ec"><Transforms><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></Transform></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></DigestMethod><DigestValue>+465BlJx5xOfHsIFezQt0MS1vZQ=</DigestValue></Reference><Reference URI="#Id-02b76fe1-945c-4e26-a8a5-6650285bbd4c"><Transforms><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></Transform></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></DigestMethod><DigestValue>jEe8rnaaqBWZQe+xHBQXriVG99o=</DigestValue></Reference><Reference URI="#Id-ccc937f4-8ec8-416a-b97b-0b612a69b040"><Transforms><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></Transform></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></DigestMethod><DigestValue>W45ginYdBVqOqEaqPI2piZMPReA=</DigestValue></Reference><Reference URI="#Id-fa48ae82-88bb-4bf1-9c0d-4eb1de66c4fc"><Transforms><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></Transform></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></DigestMethod><DigestValue>m2VlWz/ZDTWL7FREHK+wpKhvjJM=</DigestValue></Reference><Reference URI="#Timestamp-4d2cce4a-39fb-4d7d-b0d5-17d583255ef5"><Transforms><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></Transform></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></DigestMethod><DigestValue>Qws229qmAzSTZ4OKmAUWgl0PWWo=</DigestValue></Reference><Reference URI="#Id-0175a715-4db3-4886-8af1-991b1472e7f4"><Transforms><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></Transform></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></DigestMethod><DigestValue>iEazGnkPY5caCWVZOHyR87CZ1h0=</DigestValue></Reference></SignedInfo>',
);
});
it("Exclusive canonicalization works on complex xml", function () {
compare(
'<?xml version="1.0" encoding="utf-8"?>\n' +
'<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/">\n' +
" <Body>\n" +
' <ACORD xmlns="http://www.ACORD.org/standards/PC_Surety/ACORD1.10.0/xml/">\n' +
" <SignonRq>\n" +
" <SessKey />\n" +
" <ClientDt />\n" +
" <CustLangPref />\n" +
" <ClientApp>\n" +
' <Org p6:type="AssignedIdentifier" id="wewe" xmlns:p6="http://www.w3.org/2001/XMLSchema-instance" />\n' +
" <Name />\n" +
" <Version />\n" +
" </ClientApp>\n" +
" <ProxyClient>\n" +
' <Org p6:type="AssignedIdentifier" id="erer" xmlns:p6="http://www.w3.org/2001/XMLSchema-instance" />\n' +
" <Name>ererer</Name>\n" +
" <Version>dfdf</Version>\n" +
" </ProxyClient>\n" +
" </SignonRq>\n" +
" <InsuranceSvcRq>\n" +
" <RqUID />\n" +
' <SPName id="rter" />\n' +
' <QuickHit xmlns="urn:com.thehartford.bi.acord-extensions">\n' +
' <StateProvCd CodeListRef="dfdf" xmlns="http://www.ACORD.org/standards/PC_Surety/ACORD1.10.0/xml/" />\n' +
" </QuickHit>\n" +
" <WorkCompPolicyQuoteInqRq>\n" +
" <RqUID>erer</RqUID>\n" +
' <TransactionRequestDt id="erer" />\n' +
" <CurCd />\n" +
' <BroadLOBCd id="erer" />\n' +
" <InsuredOrPrincipal>\n" +
" <ItemIdInfo>\n" +
' <AgencyId id="3434" />\n' +
" <OtherIdentifier>\n" +
' <CommercialName id="3434" />\n' +
" <ContractTerm>\n" +
' <EffectiveDt id="3434" />\n' +
' <StartTime id="3434" />\n' +
" </ContractTerm>\n" +
" </OtherIdentifier>\n" +
" </ItemIdInfo>\n" +
" </InsuredOrPrincipal>\n" +
" <InsuredOrPrincipal>\n" +
" </InsuredOrPrincipal>\n" +
" <CommlPolicy>\n" +
' <PolicyNumber id="3434" />\n' +
" <LOBCd />\n" +
" </CommlPolicy>\n" +
" <WorkCompLineBusiness>\n" +
" <LOBCd />\n" +
" <WorkCompRateState>\n" +
" <WorkCompLocInfo>\r" +
" </WorkCompLocInfo>\n" +
" </WorkCompRateState>\n" +
" </WorkCompLineBusiness>\n" +
' <RemarkText IdRef="">\n' +
" </RemarkText>\n" +
' <RemarkText IdRef="2323" id="3434">\n' +
" </RemarkText>\n" +
" </WorkCompPolicyQuoteInqRq>\n" +
" </InsuranceSvcRq>\n" +
" </ACORD>\n" +
" </Body>\n" +
"</Envelope>",
"//*[local-name(.)='Body']",
'<Body xmlns="http://schemas.xmlsoap.org/soap/envelope/">\n <ACORD xmlns="http://www.ACORD.org/standards/PC_Surety/ACORD1.10.0/xml/">\n <SignonRq>\n <SessKey></SessKey>\n <ClientDt></ClientDt>\n <CustLangPref></CustLangPref>\n <ClientApp>\n <Org xmlns:p6="http://www.w3.org/2001/XMLSchema-instance" id="wewe" p6:type="AssignedIdentifier"></Org>\n <Name></Name>\n <Version></Version>\n </ClientApp>\n <ProxyClient>\n <Org xmlns:p6="http://www.w3.org/2001/XMLSchema-instance" id="erer" p6:type="AssignedIdentifier"></Org>\n <Name>ererer</Name>\n <Version>dfdf</Version>\n </ProxyClient>\n </SignonRq>\n <InsuranceSvcRq>\n <RqUID></RqUID>\n <SPName id="rter"></SPName>\n <QuickHit xmlns="urn:com.thehartford.bi.acord-extensions">\n <StateProvCd xmlns="http://www.ACORD.org/standards/PC_Surety/ACORD1.10.0/xml/" CodeListRef="dfdf"></StateProvCd>\n </QuickHit>\n <WorkCompPolicyQuoteInqRq>\n <RqUID>erer</RqUID>\n <TransactionRequestDt id="erer"></TransactionRequestDt>\n <CurCd></CurCd>\n <BroadLOBCd id="erer"></BroadLOBCd>\n <InsuredOrPrincipal>\n <ItemIdInfo>\n <AgencyId id="3434"></AgencyId>\n <OtherIdentifier>\n <CommercialName id="3434"></CommercialName>\n <ContractTerm>\n <EffectiveDt id="3434"></EffectiveDt>\n <StartTime id="3434"></StartTime>\n </ContractTerm>\n </OtherIdentifier>\n </ItemIdInfo>\n </InsuredOrPrincipal>\n <InsuredOrPrincipal>\n </InsuredOrPrincipal>\n <CommlPolicy>\n <PolicyNumber id="3434"></PolicyNumber>\n <LOBCd></LOBCd>\n </CommlPolicy>\n <WorkCompLineBusiness>\n <LOBCd></LOBCd>\n <WorkCompRateState>\n <WorkCompLocInfo>\n </WorkCompLocInfo>\n </WorkCompRateState>\n </WorkCompLineBusiness>\n <RemarkText IdRef="">\n </RemarkText>\n <RemarkText IdRef="2323" id="3434">\n </RemarkText>\n </WorkCompPolicyQuoteInqRq>\n </InsuranceSvcRq>\n </ACORD>\n </Body>',
);
});
it("Multiple Canonicalization with namespace definition outside of signed element", function () {
const doc = new xmldom.DOMParser().parseFromString(
'<x xmlns:p="myns"><p:y><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"></ds:Signature></p:y></x>',
);
const node = xpath.select1("//*[local-name(.)='y']", doc);
isDomNode.assertIsNodeLike(node);
const sig = new SignedXml();
const res = sig.getCanonXml(
[
"http://www.w3.org/2000/09/xmldsig#enveloped-signature",
"http://www.w3.org/2001/10/xml-exc-c14n#",
],
node,
);
expect(res).to.equal('<p:y xmlns:p="myns"></p:y>');
});
it("Enveloped-signature canonicalization respects current node", function () {
// older versions of enveloped-signature removed the first signature in the whole doc, but should
// be the signature inside the current node if we want to be able to verify multiple signatures
// in a document.
const xml =
'<x><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" /><y><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" /></y></x>';
const doc = new xmldom.DOMParser().parseFromString(xml);
const node = xpath.select1("//*[local-name(.)='y']", doc);
const sig = new SignedXml();
const transforms = ["http://www.w3.org/2000/09/xmldsig#enveloped-signature"];
isDomNode.assertIsNodeLike(node);
const res = sig.getCanonXml(transforms, node);
expect(res).to.equal("<y/>");
});
it("The XML canonicalization method processes a node-set by imposing the following additional document order rules on the namespace and attribute nodes of each element: \
- An element's namespace and attribute nodes have a document order position greater than the element but less than any child node of the element. \
Namespace nodes have a lesser document order position than attribute nodes. \
- An element's namespace nodes are sorted lexicographically by local name (the default namespace node, if one exists, has no local name and is therefore lexicographically least). \
- An element's attribute nodes are sorted lexicographically with namespace URI as the primary key and local name as the secondary key (an empty namespace URI is lexicographically least). \
Lexicographic comparison, which orders strings from least to greatest alphabetically, is based on the UCS codepoint values, which is equivalent to lexicographic ordering based on UTF-8.", function () {
compare(
'<root xmlns:b="moo" b:attr1="a1" a:attr1="a1" b:attr4="b4" xmlns="foo" b:attr3="a3" xmlns:a="zoo"></root>',
"//*[local-name(.)='root']",
'<root xmlns="foo" xmlns:a="zoo" xmlns:b="moo" b:attr1="a1" b:attr3="a3" b:attr4="b4" a:attr1="a1"></root>',
);
});
it("saml attributed order (bug #25)", function () {
compare(
'<root xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" samlp:a="1" saml:a="1"></root>',
"//*[local-name(.)='root']",
'<root xmlns="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" saml:a="1" samlp:a="1"></root>',
);
});
});
================================================
FILE: test/canonicalization-unit-tests.spec.ts
================================================
import { expect } from "chai";
import { ExclusiveCanonicalization } from "../src/exclusive-canonicalization";
import * as xmldom from "@xmldom/xmldom";
import * as xpath from "xpath";
import { SignedXml } from "../src/index";
import * as isDomNode from "@xmldom/is-dom-node";
const compare = function (
xml: string,
xpathArg: string,
expected: string,
inclusiveNamespacesPrefixList?: string[],
defaultNsForPrefix?: Record<string, string>,
) {
const doc = new xmldom.DOMParser().parseFromString(xml);
const elem = xpath.select1(xpathArg, doc);
const can = new ExclusiveCanonicalization();
isDomNode.assertIsElementNode(elem);
const result = can
.process(elem, {
inclusiveNamespacesPrefixList,
defaultNsForPrefix,
})
.toString();
expect(expected).to.equal(result);
};
describe("Canonicalization unit tests", function () {
it("Exclusive canonicalization works on xml with no namespaces", function () {
compare("<root><child>123</child></root>", "//*", "<root><child>123</child></root>");
});
it("Exclusive canonicalization works on inner xpath", function () {
compare("<root><child>123</child></root>", "//*[local-name(.)='child']", "<child>123</child>");
});
it("Exclusive canonicalization works on xml with prefixed namespaces defined in output nodes", function () {
compare(
'<root><p:child xmlns:p="s"><inner>123</inner></p:child></root>',
"//*[local-name(.)='child']",
'<p:child xmlns:p="s"><inner>123</inner></p:child>',
);
});
it("element used prefixed ns which is also the default", function () {
compare(
'<root><child xmlns="s"><p:inner xmlns:p="s">123</p:inner></child></root>',
"//*[local-name(.)='child']",
'<child xmlns="s"><p:inner xmlns:p="s">123</p:inner></child>',
);
});
it("Exclusive canonicalization works with default namespace for prefix", function () {
compare(
'<ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></ds:SignedInfo>',
"//*[local-name(.)='SignedInfo']",
'<ds:SignedInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:CanonicalizationMethod></ds:SignedInfo>',
undefined,
{ ds: "http://www.w3.org/2000/09/xmldsig#" },
);
});
it("Exclusive canonicalization works on xml with prefixed namespaces defined in output nodes. ns definition is not duplicated on each usage", function () {
compare(
'<root><p:child xmlns:p="ns"><p:inner>123</p:inner></p:child></root>',
"//*[local-name(.)='child']",
'<p:child xmlns:p="ns"><p:inner>123</p:inner></p:child>',
);
});
it("Exclusive canonicalization works on xml with prefixed namespaces defined in output nodes but before used", function () {
compare(
'<root><child xmlns:p="ns"><p:inner>123</p:inner></child></root>',
"//*[local-name(.)='child']",
'<child><p:inner xmlns:p="ns">123</p:inner></child>',
);
});
it("Exclusive canonicalization works on xml with prefixed namespaces defined outside output nodes", function () {
compare(
'<root xmlns:p="ns"><p:child>123</p:child></root>',
"//*[local-name(.)='child']",
'<p:child xmlns:p="ns">123</p:child>',
);
});
it("Exclusive canonicalization works on xml with prefixed namespace defined in inclusive list", function () {
compare(
'<root xmlns:p="ns"><p:child xmlns:inclusive="ns2"><inclusive:inner xmlns:inclusive="ns2">123</inclusive:inner></p:child></root>',
"//*[local-name(.)='child']",
'<p:child xmlns:inclusive="ns2" xmlns:p="ns"><inclusive:inner>123</inclusive:inner></p:child>',
["inclusive"],
);
});
it("Exclusive canonicalization works on xml with multiple prefixed namespaces defined in inclusive list", function () {
compare(
'<root xmlns:p="ns"><p:child xmlns:inclusive="ns2" xmlns:inclusive2="ns3"><inclusive:inner xmlns:inclusive="ns2">123</inclusive:inner><inclusive2:inner xmlns:inclusive2="ns3">456</inclusive2:inner></p:child></root>',
"//*[local-name(.)='child']",
'<p:child xmlns:inclusive="ns2" xmlns:inclusive2="ns3" xmlns:p="ns"><inclusive:inner>123</inclusive:inner><inclusive2:inner>456</inclusive2:inner></p:child>',
["inclusive", "inclusive2"],
);
});
it("Exclusive canonicalization works on xml with prefixed namespace defined in inclusive list defined outside output nodes", function () {
compare(
'<root xmlns:p="ns" xmlns:inclusive="ns2"><p:child><inclusive:inner xmlns:inclusive="ns2">123</inclusive:inner></p:child></root>',
"//*[local-name(.)='child']",
'<p:child xmlns:p="ns"><inclusive:inner xmlns:inclusive="ns2">123</inclusive:inner></p:child>',
["inclusive"],
);
});
it("Exclusive canonicalization works on xml with prefixed namespace defined in inclusive list used on attribute", function () {
compare(
'<root xmlns:p="ns"><p:child xmlns:inclusive="ns2"><p:inner foo="inclusive:bar">123</p:inner></p:child></root>',
"//*[local-name(.)='child']",
'<p:child xmlns:inclusive="ns2" xmlns:p="ns"><p:inner foo="inclusive:bar">123</p:inner></p:child>',
["inclusive"],
);
});
it("Exclusive canonicalization works on xml with default namespace inside output nodes", function () {
compare(
'<root><child><inner xmlns="ns">123</inner></child></root>',
"//*[local-name(.)='child']",
'<child><inner xmlns="ns">123</inner></child>',
);
});
it("Exclusive canonicalization works on xml with multiple different default namespaces", function () {
compare(
'<roo
gitextract_8drkv3mt/ ├── .eslintignore ├── .eslintrc.json ├── .gitattributes ├── .github/ │ ├── dependabot.yml │ └── workflows/ │ ├── ci.yml │ └── codeql-analysis.yml ├── .gitignore ├── .grenrc.js ├── .markdownlint.json ├── .mocharc.json ├── .npmignore ├── .nycrc.json ├── .prettierignore ├── .prettierrc.json ├── .release-it.json ├── .vscode/ │ └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── example/ │ ├── client.pem │ ├── client_public.pem │ └── example.js ├── package.json ├── src/ │ ├── c14n-canonicalization.ts │ ├── enveloped-signature.ts │ ├── exclusive-canonicalization.ts │ ├── hash-algorithms.ts │ ├── index.ts │ ├── signature-algorithms.ts │ ├── signed-xml.ts │ ├── types.ts │ └── utils.ts ├── test/ │ ├── c14n-non-exclusive-unit-tests.spec.ts │ ├── c14nWithComments-unit-tests.spec.ts │ ├── canonicalization-unit-tests.spec.ts │ ├── document-tests.spec.ts │ ├── hmac-tests.spec.ts │ ├── key-info-tests.spec.ts │ ├── saml-response-tests.spec.ts │ ├── signature-integration-tests.spec.ts │ ├── signature-object-tests.spec.ts │ ├── signature-unit-tests.spec.ts │ ├── static/ │ │ ├── client.pem │ │ ├── client_bundle.pem │ │ ├── client_public.der │ │ ├── client_public.pem │ │ ├── empty_uri.pem │ │ ├── feide_public.pem │ │ ├── hmac-foobar.key │ │ ├── hmac.key │ │ ├── hmac_signature.xml │ │ ├── id_with_quotes.xml │ │ ├── idp_certificate.pem │ │ ├── idp_private_key.pem │ │ ├── integration/ │ │ │ ├── expectedVerify.xml │ │ │ └── expectedVerifyComplex.xml │ │ ├── invalid_saml_no_signed_info.xml │ │ ├── invalid_saml_sha256_rsa_mgf1.xml │ │ ├── invalid_signature - changed content.xml │ │ ├── invalid_signature - hash.xml │ │ ├── invalid_signature - non existing reference.xml │ │ ├── invalid_signature - signature value.xml │ │ ├── invalid_signature - wsu - changed content.xml │ │ ├── invalid_signature - wsu - hash.xml │ │ ├── invalid_signature - wsu - invalid signature value.xml │ │ ├── invalid_signature - wsu - non existing reference.xml │ │ ├── invalid_signature_without_transforms_element.xml │ │ ├── keyinfo - pretty-printed.xml │ │ ├── keyinfo.pem │ │ ├── saml_external_ns.pem │ │ ├── saml_external_ns.xml │ │ ├── saml_multiple_signed_info_nodes.xml │ │ ├── saml_wrapped_signed_info_node.xml │ │ ├── signature_with_inclusivenamespaces.pem │ │ ├── signature_with_inclusivenamespaces.xml │ │ ├── signature_with_inclusivenamespaces_lines.xml │ │ ├── signature_with_inclusivenamespaces_lines_windows.xml │ │ ├── unsigned_saml_response.xml │ │ ├── valid_saml.xml │ │ ├── valid_saml_sha256_rsa_mgf1.xml │ │ ├── valid_saml_signature_wrapping.xml │ │ ├── valid_saml_with_digest_comment.xml │ │ ├── valid_saml_withcomments.xml │ │ ├── valid_signature wsu.xml │ │ ├── valid_signature.xml │ │ ├── valid_signature_utf8.xml │ │ ├── valid_signature_with_lowercase_id_attribute.xml │ │ ├── valid_signature_with_reference_keyInfo.xml │ │ ├── valid_signature_with_root_level_sig_namespace.xml │ │ ├── valid_signature_with_unused_prefixes.xml │ │ ├── valid_signature_with_whitespace_in_digestvalue.xml │ │ ├── valid_signature_without_transforms_element.xml │ │ ├── windows_store_certificate.pem │ │ ├── windows_store_signature.xml │ │ ├── wsfederation_metadata.pem │ │ └── wsfederation_metadata.xml │ ├── utils-tests.spec.ts │ ├── validators/ │ │ ├── XmlCryptoJava/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── test/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── nodejs/ │ │ │ │ └── xmlcrypto/ │ │ │ │ └── HMACTest.java │ │ │ └── resources/ │ │ │ └── log4j.xml │ │ └── XmlCryptoUtilities/ │ │ ├── XmlCryptoUtilities/ │ │ │ ├── Program.cs │ │ │ ├── Properties/ │ │ │ │ └── AssemblyInfo.cs │ │ │ ├── ValidateSignature.csproj │ │ │ ├── bin/ │ │ │ │ └── Debug/ │ │ │ │ ├── ClientPrivate.pfx │ │ │ │ ├── Example.xml │ │ │ │ ├── XmlCryptoUtilities.pdb │ │ │ │ ├── XmlCryptoUtilities.vshost.exe.manifest │ │ │ │ └── signedExample.xml │ │ │ ├── obj/ │ │ │ │ └── x86/ │ │ │ │ └── Debug/ │ │ │ │ ├── DesignTimeResolveAssemblyReferencesInput.cache │ │ │ │ ├── ValidateSignature.csproj.FileListAbsolute.txt │ │ │ │ └── XmlCryptoUtilities.pdb │ │ │ ├── program-repro-misc-validation-and-canon.cs │ │ │ └── utilities.cs │ │ ├── XmlCryptoUtilities.sln │ │ └── XmlCryptoUtilities.suo │ └── wsfed-metadata-tests.spec.ts ├── tsconfig.eslint.json └── tsconfig.json
SYMBOL INDEX (151 symbols across 14 files)
FILE: example/example.js
function signXml (line 8) | function signXml(xml, xpath, key, dest) {
function validateXml (line 16) | function validateXml(xml, key) {
FILE: src/c14n-canonicalization.ts
class C14nCanonicalization (line 10) | class C14nCanonicalization implements CanonicalizationOrTransformationAl...
method constructor (line 13) | constructor() {
method attrCompare (line 17) | attrCompare(a, b) {
method nsCompare (line 37) | nsCompare(a, b) {
method renderAttrs (line 46) | renderAttrs(node) {
method renderNs (line 85) | renderNs(
method processInner (line 177) | processInner(node, prefixesInScope, defaultNs, defaultNsForPrefix, anc...
method renderComment (line 212) | renderComment(node: Comment) {
method process (line 257) | process(node: Node, options: CanonicalizationOrTransformationAlgorithm...
method getAlgorithmName (line 278) | getAlgorithmName() {
class C14nCanonicalizationWithComments (line 286) | class C14nCanonicalizationWithComments extends C14nCanonicalization {
method constructor (line 287) | constructor() {
method getAlgorithmName (line 292) | getAlgorithmName() {
FILE: src/enveloped-signature.ts
class EnvelopedSignature (line 10) | class EnvelopedSignature implements CanonicalizationOrTransformationAlgo...
method constructor (line 13) | constructor() {
method process (line 17) | process(node: Node, options: CanonicalizationOrTransformationAlgorithm...
method getAlgorithmName (line 58) | getAlgorithmName(): CanonicalizationOrTransformAlgorithmType {
FILE: src/exclusive-canonicalization.ts
function isPrefixInScope (line 9) | function isPrefixInScope(prefixesInScope, prefix, namespaceURI) {
class ExclusiveCanonicalization (line 20) | class ExclusiveCanonicalization implements CanonicalizationOrTransformat...
method constructor (line 23) | constructor() {
method attrCompare (line 27) | attrCompare(a, b) {
method nsCompare (line 47) | nsCompare(a, b) {
method renderAttrs (line 56) | renderAttrs(node) {
method renderNs (line 96) | renderNs(
method processInner (line 177) | processInner(
method renderComment (line 224) | renderComment(node: Comment) {
method process (line 268) | process(elem: Element, options: CanonicalizationOrTransformationAlgori...
method getAlgorithmName (line 322) | getAlgorithmName() {
class ExclusiveCanonicalizationWithComments (line 327) | class ExclusiveCanonicalizationWithComments extends ExclusiveCanonicaliz...
method constructor (line 328) | constructor() {
method getAlgorithmName (line 333) | getAlgorithmName() {
FILE: src/hash-algorithms.ts
class Sha1 (line 4) | class Sha1 implements HashAlgorithm {
class Sha256 (line 17) | class Sha256 implements HashAlgorithm {
class Sha512 (line 30) | class Sha512 implements HashAlgorithm {
FILE: src/signature-algorithms.ts
class RsaSha1 (line 4) | class RsaSha1 implements SignatureAlgorithm {
class RsaSha256 (line 30) | class RsaSha256 implements SignatureAlgorithm {
class RsaSha256Mgf1 (line 56) | class RsaSha256Mgf1 implements SignatureAlgorithm {
class RsaSha512 (line 103) | class RsaSha512 implements SignatureAlgorithm {
class HmacSha1 (line 129) | class HmacSha1 implements SignatureAlgorithm {
FILE: src/signed-xml.ts
class SignedXml (line 30) | class SignedXml {
method constructor (line 136) | constructor(options: SignedXmlOptions = {}) {
method enableHMAC (line 182) | enableHMAC(): void {
method getKeyInfoContent (line 197) | static getKeyInfoContent({ publicCert, prefix }: GetKeyInfoContentArgs...
method getCertFromKeyInfo (line 235) | static getCertFromKeyInfo(keyInfo?: Node | null): string | null {
method checkSignature (line 262) | checkSignature(
method getCanonSignedInfoXml (line 388) | private getCanonSignedInfoXml(doc: Document) {
method getCanonReferenceXml (line 430) | private getCanonReferenceXml(doc: Document, ref: Reference, node: Node) {
method calculateSignatureValue (line 446) | private calculateSignatureValue(doc: Document, callback?: ErrorFirstCa...
method findSignatureAlgorithm (line 459) | private findSignatureAlgorithm(name?: SignatureAlgorithmType) {
method findCanonicalizationAlgorithm (line 471) | private findCanonicalizationAlgorithm(name: CanonicalizationOrTransfor...
method findHashAlgorithm (line 482) | private findHashAlgorithm(name: HashAlgorithmType) {
method validateElementAgainstReferences (line 491) | validateElementAgainstReferences(elemOrXpath: Element | string, doc: D...
method validateReference (line 525) | private validateReference(ref: Reference, doc: Document) {
method findSignatures (line 595) | findSignatures(doc: Node): Node[] {
method loadSignature (line 609) | loadSignature(signatureNode: Node | string): void {
method loadReference (line 704) | private loadReference(refNode: Node) {
method addReference (line 807) | addReference({
method getReferences (line 847) | getReferences() {
method getSignedReferences (line 858) | getSignedReferences() {
method computeSignature (line 907) | computeSignature(
method addAllReferences (line 1092) | private addAllReferences(doc: Document, signatureElem: Element, prefix...
method getKeyInfo (line 1214) | private getKeyInfo(prefix) {
method getObjects (line 1238) | private getObjects(prefix?: string) {
method getCanonXml (line 1264) | getCanonXml(
method ensureHasId (line 1297) | private ensureHasId(node) {
method createSignedInfo (line 1342) | private createSignedInfo(doc, prefix) {
method createSignature (line 1374) | private createSignature(prefix?: string) {
method getSignatureXml (line 1401) | getSignatureXml(): string {
method getOriginalXmlWithIds (line 1410) | getOriginalXmlWithIds(): string {
method getSignedXml (line 1419) | getSignedXml(): string {
FILE: src/types.ts
type ErrorFirstCallback (line 11) | type ErrorFirstCallback<T> = (err: Error | null, result?: T) => void;
type CanonicalizationAlgorithmType (line 13) | type CanonicalizationAlgorithmType =
type CanonicalizationOrTransformAlgorithmType (line 20) | type CanonicalizationOrTransformAlgorithmType =
type HashAlgorithmType (line 24) | type HashAlgorithmType =
type SignatureAlgorithmType (line 30) | type SignatureAlgorithmType =
type GetKeyInfoContentArgs (line 42) | interface GetKeyInfoContentArgs {
type ObjectAttributes (line 51) | interface ObjectAttributes {
type SignedXmlOptions (line 65) | interface SignedXmlOptions {
type NamespacePrefix (line 80) | interface NamespacePrefix {
type RenderedNamespace (line 85) | interface RenderedNamespace {
type CanonicalizationOrTransformationAlgorithmProcessOptions (line 90) | interface CanonicalizationOrTransformationAlgorithmProcessOptions {
type ComputeSignatureOptionsLocation (line 98) | interface ComputeSignatureOptionsLocation {
type ComputeSignatureOptions (line 115) | interface ComputeSignatureOptions {
type Reference (line 125) | interface Reference {
type CanonicalizationOrTransformationAlgorithm (line 164) | interface CanonicalizationOrTransformationAlgorithm {
type HashAlgorithm (line 174) | interface HashAlgorithm {
type SignatureAlgorithm (line 181) | interface SignatureAlgorithm {
type TransformAlgorithm (line 208) | interface TransformAlgorithm {
function isErrorFirstCallback (line 236) | function isErrorFirstCallback<T>(
function createOptionalCallbackFunction (line 248) | function createOptionalCallbackFunction<T, A extends unknown[]>(
FILE: src/utils.ts
function isArrayHasLength (line 5) | function isArrayHasLength(array: unknown): array is unknown[] {
function attrEqualsExplicitly (line 9) | function attrEqualsExplicitly(attr: Attr, localName: string, namespace?:...
function attrEqualsImplicitly (line 13) | function attrEqualsImplicitly(attr: Attr, localName: string, namespace?:...
function findAttr (line 20) | function findAttr(element: Element, localName: string, namespace?: strin...
function findChildren (line 34) | function findChildren(node: Node | Document, localName: string, namespac...
function findChilds (line 51) | function findChilds(node: Node | Document, localName: string, namespace?...
function encodeSpecialCharactersInAttribute (line 71) | function encodeSpecialCharactersInAttribute(attributeValue) {
function encodeSpecialCharactersInText (line 82) | function encodeSpecialCharactersInText(text: string): string {
constant PEM_FORMAT_REGEX (line 106) | const PEM_FORMAT_REGEX = new RegExp(
constant EXTRACT_X509_CERTS (line 110) | const EXTRACT_X509_CERTS = new RegExp(
constant BASE64_REGEX (line 114) | const BASE64_REGEX = new RegExp(
function normalizePem (line 138) | function normalizePem(pem: string): string {
function pemToDer (line 150) | function pemToDer(pem: string): Buffer {
function derToPem (line 168) | function derToPem(
function collectAncestorNamespaces (line 195) | function collectAncestorNamespaces(
function findNSPrefix (line 224) | function findNSPrefix(subset) {
function isElementSubset (line 235) | function isElementSubset(docSubset: Node[]): docSubset is Element[] {
function findAncestorNs (line 248) | function findAncestorNs(
function validateDigestValue (line 296) | function validateDigestValue(digest, expectedDigest) {
function isDescendantOf (line 318) | function isDescendantOf(node: Node, parent: Node): boolean {
FILE: test/signature-integration-tests.spec.ts
function verifySignature (line 9) | function verifySignature(xml, expected, xpath, canonicalizationAlgorithm) {
FILE: test/signature-unit-tests.spec.ts
function signWith (line 19) | function signWith(signatureAlgorithm: string): string {
function loadSignature (line 36) | function loadSignature(xml: string): SignedXml {
function nodeExists (line 79) | function nodeExists(doc, xpathArg) {
function verifyAddsId (line 88) | function verifyAddsId(mode, nsMode) {
function verifyDoesNotDuplicateIdAttributes (line 163) | function verifyDoesNotDuplicateIdAttributes(prefix: string, idMode?: "ws...
class DummyDigest (line 386) | class DummyDigest {
class DummySignatureAlgorithm (line 396) | class DummySignatureAlgorithm {
class DummyTransformation (line 410) | class DummyTransformation {
class DummyCanonicalization (line 421) | class DummyCanonicalization {
class DummyDigest (line 547) | class DummyDigest {
class DummySignatureAlgorithm (line 557) | class DummySignatureAlgorithm {
class DummyTransformation (line 571) | class DummyTransformation {
class DummyCanonicalization (line 582) | class DummyCanonicalization {
class DummySignatureAlgorithm (line 769) | class DummySignatureAlgorithm {
function passLoadSignature (line 851) | function passLoadSignature(file: string, toString?: boolean) {
function loadSignature (line 932) | function loadSignature(xml: string, idMode?: "wssecurity") {
function passValidSignature (line 946) | function passValidSignature(file: string, mode?: "wssecurity") {
function failInvalidSignature (line 955) | function failInvalidSignature(file: string, idMode?: "wssecurity") {
function throwsValidatingSignature (line 963) | function throwsValidatingSignature(file: string, idMode?: "wssecurity") {
function getKeyInfoContentWithAssertionId (line 1102) | function getKeyInfoContentWithAssertionId({ assertionId }) {
FILE: test/validators/XmlCryptoJava/src/test/java/org/nodejs/xmlcrypto/HMACTest.java
class HMACTest (line 35) | public class HMACTest {
method testCreateHMACSignature (line 42) | @Test
method toFile (line 113) | private void toFile(Node node, File file) throws Exception {
FILE: test/validators/XmlCryptoUtilities/XmlCryptoUtilities/Program.cs
class SignVerifyEnvelope (line 15) | public class SignVerifyEnvelope
method Main (line 18) | public static void Main(String[] args)
method SignXmlFile (line 47) | public static void SignXmlFile(string FileName, string SignedFileName,...
method VerifyXmlFile (line 96) | public static Boolean VerifyXmlFile(String Name, RSA Key)
method CreateSomeXml (line 121) | public static void CreateSomeXml(string FileName)
FILE: test/validators/XmlCryptoUtilities/XmlCryptoUtilities/program-repro-misc-validation-and-canon.cs
class SignVerifyEnvelope (line 15) | public class SignVerifyEnvelope
class RSAPKCS1SHA256SignatureDescription (line 17) | public sealed class RSAPKCS1SHA256SignatureDescription : SignatureDesc...
method RSAPKCS1SHA256SignatureDescription (line 19) | public RSAPKCS1SHA256SignatureDescription()
method CreateDeformatter (line 27) | public override AsymmetricSignatureDeformatter CreateDeformatter(Asy...
method CreateFormatter (line 39) | public override AsymmetricSignatureFormatter CreateFormatter(Asymmet...
method ValidateXml (line 54) | static bool ValidateXml(XmlDocument receipt, X509Certificate2 certific...
method Main (line 122) | public static void Main(String[] args)
method SignXmlFile (line 185) | public static void SignXmlFile(string FileName, string SignedFileName,...
method VerifyXmlFile (line 234) | public static Boolean VerifyXmlFile(String Name, RSA Key)
method CreateSomeXml (line 259) | public static void CreateSomeXml(string FileName)
Condensed preview — 118 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (570K chars).
[
{
"path": ".eslintignore",
"chars": 0,
"preview": ""
},
{
"path": ".eslintrc.json",
"chars": 975,
"preview": "{\n \"env\": {\n \"browser\": false,\n \"node\": true,\n \"mocha\": true,\n \"es2020\": true\n },\n \"root\": true,\n \"parse"
},
{
"path": ".gitattributes",
"chars": 322,
"preview": "# Git to autodetect text files and normalise their line endings to LF when they are checked into your repository.\n* text"
},
{
"path": ".github/dependabot.yml",
"chars": 786,
"preview": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where "
},
{
"path": ".github/workflows/ci.yml",
"chars": 1180,
"preview": "name: Test Status\non:\n workflow_dispatch:\n push:\n branches: [master]\n pull_request:\n branches: [master]\n\njobs:\n"
},
{
"path": ".github/workflows/codeql-analysis.yml",
"chars": 2485,
"preview": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# Y"
},
{
"path": ".gitignore",
"chars": 155,
"preview": "lib/\nnode_modules/\np.js\np.txt\n*.svclog\n.DS_Store\n*.*~\n*.swp\nnpm-debug.log\n/test/validators/XmlCryptoJava/target/\n.eslint"
},
{
"path": ".grenrc.js",
"chars": 1548,
"preview": "module.exports = {\n dataSource: \"prs\",\n prefix: \"\",\n onlyMilestones: false,\n ignoreTagsWith: [],\n ignoreLabels: [],"
},
{
"path": ".markdownlint.json",
"chars": 39,
"preview": "{\n \"MD013\": false,\n \"MD024\": false\n}\n"
},
{
"path": ".mocharc.json",
"chars": 244,
"preview": "{\n \"diff\": true,\n \"extension\": \"spec.ts\",\n \"package\": \"./package.json\",\n \"recursive\": true,\n \"reporter\": \"spec\",\n "
},
{
"path": ".npmignore",
"chars": 14,
"preview": "test/\nexample/"
},
{
"path": ".nycrc.json",
"chars": 149,
"preview": "{\n \"extends\": \"@istanbuljs/nyc-config-typescript\",\n \"all\": true,\n \"check-coverage\": false,\n \"reporter\": [\"lcov\", \"te"
},
{
"path": ".prettierignore",
"chars": 141,
"preview": "# Ignore artifacts:\nlib\nnode_modules\npackage-lock.json\n.eslintcache\n.prettierignore\ntest/static/*\ntest/validators/*\n.nyc"
},
{
"path": ".prettierrc.json",
"chars": 24,
"preview": "{\n \"printWidth\": 100\n}\n"
},
{
"path": ".release-it.json",
"chars": 132,
"preview": "{\n \"github\": {\n \"release\": true,\n \"releaseName\": \"v${version}\"\n },\n \"hooks\": {\n \"after:bump\": \"npm run chang"
},
{
"path": ".vscode/settings.json",
"chars": 257,
"preview": "{\n \"cSpell.words\": [\n \"Canonicalization\",\n \"canonicalize\",\n \"canonicalized\",\n \"codecov\",\n \"feide\",\n \""
},
{
"path": "CHANGELOG.md",
"chars": 20547,
"preview": "# Changelog\n\n## 6.0.0 (2024-01-26)\n\n### 💣 Major Changes\n\n- [**breaking-change**] Set `getCertFromKeyInfo` to `noop` [#44"
},
{
"path": "LICENSE",
"chars": 1090,
"preview": "(The MIT License)\n\nCopyright (c) Yaron Naveh <yaronn01@gmail.com>\n\nPermission is hereby granted, free of charge, to any "
},
{
"path": "README.md",
"chars": 23822,
"preview": "# xml-crypto\n\n\n[![code style: "
},
{
"path": "example/client.pem",
"chars": 915,
"preview": "-----BEGIN PRIVATE KEY-----\nMIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAL4vpoH3H3byehjj\n7RAGxefGRATiq4mXtzc9Q91W7uT"
},
{
"path": "example/client_public.pem",
"chars": 682,
"preview": "-----BEGIN CERTIFICATE-----\r\nMIIBxDCCAW6gAwIBAgIQxUSXFzWJYYtOZnmmuOMKkjANBgkqhkiG9w0BAQQFADAW\r\nMRQwEgYDVQQDEwtSb290IEFnZ"
},
{
"path": "example/example.js",
"chars": 1350,
"preview": "/* eslint-disable no-console */\n\nconst select = require(\"xml-crypto\").xpath;\nconst dom = require(\"@xmldom/xmldom\").DOMPa"
},
{
"path": "package.json",
"chars": 2128,
"preview": "{\n \"name\": \"xml-crypto\",\n \"version\": \"6.0.0\",\n \"private\": false,\n \"description\": \"Xml digital signature and encrypti"
},
{
"path": "src/c14n-canonicalization.ts",
"chars": 8216,
"preview": "import type {\n CanonicalizationOrTransformationAlgorithm,\n CanonicalizationOrTransformationAlgorithmProcessOptions,\n "
},
{
"path": "src/enveloped-signature.ts",
"chars": 2089,
"preview": "import * as xpath from \"xpath\";\nimport * as isDomNode from \"@xmldom/is-dom-node\";\n\nimport type {\n CanonicalizationOrTra"
},
{
"path": "src/exclusive-canonicalization.ts",
"chars": 9386,
"preview": "import type {\n CanonicalizationOrTransformationAlgorithm,\n CanonicalizationOrTransformationAlgorithmProcessOptions,\n "
},
{
"path": "src/hash-algorithms.ts",
"chars": 1023,
"preview": "import * as crypto from \"crypto\";\nimport type { HashAlgorithm } from \"./types\";\n\nexport class Sha1 implements HashAlgori"
},
{
"path": "src/index.ts",
"chars": 308,
"preview": "export { C14nCanonicalization, C14nCanonicalizationWithComments } from \"./c14n-canonicalization\";\nexport {\n ExclusiveCa"
},
{
"path": "src/signature-algorithms.ts",
"chars": 4983,
"preview": "import * as crypto from \"crypto\";\nimport { type SignatureAlgorithm, createOptionalCallbackFunction } from \"./types\";\n\nex"
},
{
"path": "src/signed-xml.ts",
"chars": 51136,
"preview": "import type {\n CanonicalizationAlgorithmType,\n CanonicalizationOrTransformAlgorithmType,\n CanonicalizationOrTransform"
},
{
"path": "src/types.ts",
"chars": 8491,
"preview": "/* eslint-disable no-unused-vars */\n// Type definitions for @node-saml/xml-crypto\n// Project: https://github.com/node-sa"
},
{
"path": "src/utils.ts",
"chars": 9422,
"preview": "import * as xpath from \"xpath\";\nimport type { NamespacePrefix } from \"./types\";\nimport * as isDomNode from \"@xmldom/is-d"
},
{
"path": "test/c14n-non-exclusive-unit-tests.spec.ts",
"chars": 8629,
"preview": "import { expect } from \"chai\";\n\nimport { C14nCanonicalization } from \"../src/c14n-canonicalization\";\nimport * as xmldom "
},
{
"path": "test/c14nWithComments-unit-tests.spec.ts",
"chars": 27225,
"preview": "import { expect } from \"chai\";\n\nimport { ExclusiveCanonicalizationWithComments as c14nWithComments } from \"../src/exclus"
},
{
"path": "test/canonicalization-unit-tests.spec.ts",
"chars": 31741,
"preview": "import { expect } from \"chai\";\n\nimport { ExclusiveCanonicalization } from \"../src/exclusive-canonicalization\";\nimport * "
},
{
"path": "test/document-tests.spec.ts",
"chars": 5691,
"preview": "import { SignedXml } from \"../src/index\";\nimport * as xpath from \"xpath\";\nimport * as xmldom from \"@xmldom/xmldom\";\nimpo"
},
{
"path": "test/hmac-tests.spec.ts",
"chars": 2913,
"preview": "import { SignedXml } from \"../src/index\";\nimport * as xpath from \"xpath\";\nimport * as xmldom from \"@xmldom/xmldom\";\nimpo"
},
{
"path": "test/key-info-tests.spec.ts",
"chars": 2003,
"preview": "import * as xmldom from \"@xmldom/xmldom\";\nimport * as fs from \"fs\";\nimport * as xpath from \"xpath\";\nimport { SignedXml }"
},
{
"path": "test/saml-response-tests.spec.ts",
"chars": 9114,
"preview": "import { SignedXml } from \"../src/index\";\nimport * as xpath from \"xpath\";\nimport * as xmldom from \"@xmldom/xmldom\";\nimpo"
},
{
"path": "test/signature-integration-tests.spec.ts",
"chars": 9073,
"preview": "import * as xpath from \"xpath\";\nimport * as xmldom from \"@xmldom/xmldom\";\nimport { SignedXml } from \"../src/index\";\nimpo"
},
{
"path": "test/signature-object-tests.spec.ts",
"chars": 23420,
"preview": "import * as fs from \"fs\";\nimport { expect, assert } from \"chai\";\nimport * as xpath from \"xpath\";\nimport * as xmldom from"
},
{
"path": "test/signature-unit-tests.spec.ts",
"chars": 58080,
"preview": "import * as xpath from \"xpath\";\nimport * as xmldom from \"@xmldom/xmldom\";\nimport { SignedXml, createOptionalCallbackFunc"
},
{
"path": "test/static/client.pem",
"chars": 915,
"preview": "-----BEGIN PRIVATE KEY-----\nMIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAL4vpoH3H3byehjj\n7RAGxefGRATiq4mXtzc9Q91W7uT"
},
{
"path": "test/static/client_bundle.pem",
"chars": 4056,
"preview": "-----BEGIN CERTIFICATE-----\nMIIDCzCCAfMCFDdl3bSiEFLCBC3akD+sPuSbRKnyMA0GCSqGSIb3DQEBCwUAMEIx\nCzAJBgNVBAYTAlhYMRUwEwYDVQQ"
},
{
"path": "test/static/client_public.pem",
"chars": 672,
"preview": "-----BEGIN CERTIFICATE-----\nMIIBxDCCAW6gAwIBAgIQxUSXFzWJYYtOZnmmuOMKkjANBgkqhkiG9w0BAQQFADAW\nMRQwEgYDVQQDEwtSb290IEFnZW5"
},
{
"path": "test/static/empty_uri.pem",
"chars": 918,
"preview": "-----BEGIN CERTIFICATE-----\r\nMIICcTCCAVmgAwIBAgIBATANBgkqhkiG9w0BAQQFADA+MQswCQYDVQQGEwJFUzEbMBkGA1UEChMS\r\nU2N5dGwgVU5JV"
},
{
"path": "test/static/feide_public.pem",
"chars": 944,
"preview": "-----BEGIN CERTIFICATE-----\nMIICizCCAfQCCQCY8tKaMc0BMjANBgkqhkiG9w0BAQUFADCBiTELMAkGA1UEBhMC\nTk8xEjAQBgNVBAgTCVRyb25kaGV"
},
{
"path": "test/static/hmac-foobar.key",
"chars": 31,
"preview": "F\u001bG}P\\\u000e+=(9(\u000fz\n)e\u0015\u001e \u001b,\u001bz68\u0014\u0016\u001dã"
},
{
"path": "test/static/hmac.key",
"chars": 36,
"preview": "R -\u000f\u0016B%P)\fAkl`97<YzC\bY^qq_\"q\b؎1C\u001bo\u001e "
},
{
"path": "test/static/hmac_signature.xml",
"chars": 854,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><test:Root xmlns:test=\"urn:test\"><ds:Signature xmlns:ds=\"http://ww"
},
{
"path": "test/static/id_with_quotes.xml",
"chars": 2570,
"preview": "<samlp:Response xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID"
},
{
"path": "test/static/idp_certificate.pem",
"chars": 1826,
"preview": "-----BEGIN CERTIFICATE-----\nMIIFFzCCAv+gAwIBAgIUaAU88KUbZLe7NwTw+jdCHIDU6wIwDQYJKoZIhvcNAQEL\nBQAwGjEYMBYGA1UEAwwPaWRwLmV"
},
{
"path": "test/static/idp_private_key.pem",
"chars": 3272,
"preview": "-----BEGIN PRIVATE KEY-----\nMIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDXpgSoph176VQP\nw+4e91UAL6j0TbL/aI3Amj62TU9"
},
{
"path": "test/static/integration/expectedVerify.xml",
"chars": 1306,
"preview": "<root><x xmlns=\"ns\" Id=\"_0\"/><y z_attr=\"value\" a_attr1=\"foo\" Id=\"_1\"/><z><ns:w ns:attr=\"value\" xmlns:ns=\"myns\" Id=\"_2\"/>"
},
{
"path": "test/static/integration/expectedVerifyComplex.xml",
"chars": 845,
"preview": "<library><book Id=\"_0\"><name>Harry Potter</name><author id=\"123456789\"><firstName>Joanne K</firstName><lastName>Rowling<"
},
{
"path": "test/static/invalid_saml_no_signed_info.xml",
"chars": 7816,
"preview": "<samlp:Response xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID"
},
{
"path": "test/static/invalid_saml_sha256_rsa_mgf1.xml",
"chars": 5941,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><samlp:Response xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\""
},
{
"path": "test/static/invalid_signature - changed content.xml",
"chars": 1351,
"preview": "<root><x xmlns=\"ns\" Id=\"_0\"/><y z_attr=\"foofoo\" a_attr1=\"foo\" Id=\"_1\"/><z><ns:w ns:attr=\"value\" xmlns:ns=\"myns\" Id=\"_2\"/"
},
{
"path": "test/static/invalid_signature - hash.xml",
"chars": 1306,
"preview": "<root><x xmlns=\"ns\" Id=\"_0\"/><y z_attr=\"value\" a_attr1=\"foo\" Id=\"_1\"/><z><ns:w ns:attr=\"value\" xmlns:ns=\"myns\" Id=\"_2\"/>"
},
{
"path": "test/static/invalid_signature - non existing reference.xml",
"chars": 1317,
"preview": "<root><x xmlns=\"ns\" Id=\"_0\"/><y z_attr=\"value\" a_attr1=\"foo\" Id=\"_1\"/><z><ns:w ns:attr=\"value\" xmlns:ns=\"myns\" Id=\"_2\"/>"
},
{
"path": "test/static/invalid_signature - signature value.xml",
"chars": 1306,
"preview": "<root><x xmlns=\"ns\" Id=\"_0\"/><y z_attr=\"value\" a_attr1=\"foo\" Id=\"_1\"/><z><ns:w ns:attr=\"value\" xmlns:ns=\"myns\" Id=\"_2\"/>"
},
{
"path": "test/static/invalid_signature - wsu - changed content.xml",
"chars": 1603,
"preview": "<root><x xmlns=\"ns\" xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\" wsu:I"
},
{
"path": "test/static/invalid_signature - wsu - hash.xml",
"chars": 1603,
"preview": "<root><x xmlns=\"ns\" xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\" wsu:I"
},
{
"path": "test/static/invalid_signature - wsu - invalid signature value.xml",
"chars": 1603,
"preview": "<root><x xmlns=\"ns\" xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\" wsu:I"
},
{
"path": "test/static/invalid_signature - wsu - non existing reference.xml",
"chars": 1625,
"preview": "<root><x xmlns=\"ns\" xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\" wsu:I"
},
{
"path": "test/static/invalid_signature_without_transforms_element.xml",
"chars": 703,
"preview": "<?xml version=\"1.0\"?>\n<library><book ID=\"bookid\"><name>some tampered text</name></book><Signature xmlns=\"http://www.w3.o"
},
{
"path": "test/static/keyinfo - pretty-printed.xml",
"chars": 5580,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<samlp:Response xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" xmlns:saml=\"ur"
},
{
"path": "test/static/keyinfo.pem",
"chars": 1346,
"preview": "-----BEGIN CERTIFICATE-----\nMIIDtTCCAp2gAwIBAgIJAKg4VeVcIDz1MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV\nBAYTAlVTMRMwEQYDVQQIEwpTb21"
},
{
"path": "test/static/saml_external_ns.pem",
"chars": 426,
"preview": "-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQEAoXoc7IFZQRv+SwJ15zjIl9touwY5e6b7H4vn3OtOUByjOKHUX8VX\n0TpbAV2ctZE2GSALx1AGuQAv"
},
{
"path": "test/static/saml_external_ns.xml",
"chars": 5710,
"preview": "<samlp:Response xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" ID"
},
{
"path": "test/static/saml_multiple_signed_info_nodes.xml",
"chars": 8917,
"preview": "<?xml version=\"1.0\"?><samlp:Response xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" xmlns:samlp=\"urn:oasis:names:tc:"
},
{
"path": "test/static/saml_wrapped_signed_info_node.xml",
"chars": 8915,
"preview": "<samlp:Response xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" ID"
},
{
"path": "test/static/signature_with_inclusivenamespaces.pem",
"chars": 978,
"preview": "-----BEGIN CERTIFICATE-----\nMIICnTCCAgagAwIBAgIGAUBGHxqUMA0GCSqGSIb3DQEBBQUAMIGRMQswCQYDVQQGEwJVUzETMBEG\r\nA1UECAwKQ2FsaW"
},
{
"path": "test/static/signature_with_inclusivenamespaces.xml",
"chars": 3729,
"preview": "<saml2:Assertion xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"id8132302868541019755414121\" IssueInstant=\"2013"
},
{
"path": "test/static/signature_with_inclusivenamespaces_lines.xml",
"chars": 3745,
"preview": "<saml2:Assertion xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"id8132302868541019755414121\" IssueInstant=\"2013"
},
{
"path": "test/static/signature_with_inclusivenamespaces_lines_windows.xml",
"chars": 3747,
"preview": "<saml2:Assertion xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"id8132302868541019755414121\" IssueInstant=\"2013"
},
{
"path": "test/static/unsigned_saml_response.xml",
"chars": 2069,
"preview": "<samlp:Response\r\n xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"\r\n xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:ass"
},
{
"path": "test/static/valid_saml.xml",
"chars": 8242,
"preview": "<samlp:Response xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID"
},
{
"path": "test/static/valid_saml_sha256_rsa_mgf1.xml",
"chars": 5929,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><samlp:Response xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\""
},
{
"path": "test/static/valid_saml_signature_wrapping.xml",
"chars": 8621,
"preview": "<samlp:Response xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID"
},
{
"path": "test/static/valid_saml_with_digest_comment.xml",
"chars": 6403,
"preview": "<samlp:Response xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID"
},
{
"path": "test/static/valid_saml_withcomments.xml",
"chars": 8254,
"preview": "<samlp:Response xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID"
},
{
"path": "test/static/valid_signature wsu.xml",
"chars": 1603,
"preview": "<root><x xmlns=\"ns\" xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\" wsu:I"
},
{
"path": "test/static/valid_signature.xml",
"chars": 1350,
"preview": "<root><x xmlns=\"ns\" Id=\"_0\"/><y z_attr=\"value\" a_attr1=\"foo\" Id=\"_1\"/><z><ns:w ns:attr=\"value\" xmlns:ns=\"myns\" Id=\"_2\"/>"
},
{
"path": "test/static/valid_signature_utf8.xml",
"chars": 2969,
"preview": "<saml:Assertion xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" Version=\"2.0\" ID=\"_w014WYqtFGe3OCA7UIXqUKTj8Qmo2GHn\" "
},
{
"path": "test/static/valid_signature_with_lowercase_id_attribute.xml",
"chars": 1350,
"preview": "<root><x xmlns=\"ns\" id=\"_0\"/><y z_attr=\"value\" a_attr1=\"foo\" id=\"_1\"/><z><ns:w ns:attr=\"value\" xmlns:ns=\"myns\" id=\"_2\"/>"
},
{
"path": "test/static/valid_signature_with_reference_keyInfo.xml",
"chars": 1595,
"preview": "<root><x xmlns=\"ns\" Id=\"_0\"/><y z_attr=\"value\" a_attr1=\"foo\" Id=\"_1\"/><z><ns:w ns:attr=\"value\" xmlns:ns=\"myns\" Id=\"_2\"/>"
},
{
"path": "test/static/valid_signature_with_root_level_sig_namespace.xml",
"chars": 1500,
"preview": "<root xmlns:ns1=\"http://www.w3.org/2000/09/xmldsig#\"><x xmlns=\"ns\" Id=\"_0\"/><y z_attr=\"value\" a_attr1=\"foo\" Id=\"_1\"/><z>"
},
{
"path": "test/static/valid_signature_with_unused_prefixes.xml",
"chars": 5341,
"preview": "<soapenv:Envelope xmlns:eg=\"http://www.example.com\" xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\"><soapenv:H"
},
{
"path": "test/static/valid_signature_with_whitespace_in_digestvalue.xml",
"chars": 1314,
"preview": "<root><x xmlns=\"ns\" Id=\"_0\"/><y z_attr=\"value\" a_attr1=\"foo\" Id=\"_1\"/><z><ns:w ns:attr=\"value\" xmlns:ns=\"myns\" Id=\"_2\"/>"
},
{
"path": "test/static/valid_signature_without_transforms_element.xml",
"chars": 694,
"preview": "<?xml version=\"1.0\"?>\n<library><book ID=\"bookid\"><name>some text</name></book><Signature xmlns=\"http://www.w3.org/2000/0"
},
{
"path": "test/static/windows_store_certificate.pem",
"chars": 1374,
"preview": "-----BEGIN CERTIFICATE-----\nMIIDyTCCArGgAwIBAgIQNP+YKvSo8IVArhlhpgc/xjANBgkqhkiG9w0BAQsFADCB\njjELMAkGA1UEBhMCVVMxEzARBgN"
},
{
"path": "test/static/windows_store_signature.xml",
"chars": 1585,
"preview": "<Receipt Version=\"1.0\" ReceiptDate=\"2012-08-30T23:10:05Z\" CertificateId=\"b809e47cd0110a4db043b3f73e83acd917fe1336\" Recei"
},
{
"path": "test/static/wsfederation_metadata.pem",
"chars": 1080,
"preview": "-----BEGIN CERTIFICATE-----\r\nMIIC4jCCAcqgAwIBAgIQQNXrmzhLN4VGlUXDYCRT3zANBgkqhkiG9w0BAQsFADAt\r\nMSswKQYDVQQDEyJhY2NvdW50c"
},
{
"path": "test/static/wsfederation_metadata.xml",
"chars": 18177,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><EntityDescriptor ID=\"_8d1dcc18-2f1e-4a93-850b-e3a3081b3ca1\" entityID=\"https://st"
},
{
"path": "test/utils-tests.spec.ts",
"chars": 3595,
"preview": "import * as fs from \"fs\";\nimport * as utils from \"../src/utils\";\nimport { expect } from \"chai\";\nimport * as xmldom from "
},
{
"path": "test/validators/XmlCryptoJava/pom.xml",
"chars": 2491,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2"
},
{
"path": "test/validators/XmlCryptoJava/src/test/java/org/nodejs/xmlcrypto/HMACTest.java",
"chars": 5022,
"preview": "package org.nodejs.xmlcrypto;\n\nimport java.io.File;\nimport java.util.Collections;\nimport java.util.LinkedList;\nimport ja"
},
{
"path": "test/validators/XmlCryptoJava/src/test/resources/log4j.xml",
"chars": 630,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE log4j:configuration SYSTEM \"log4j.dtd\">\n<log4j:configuration xmlns:log4"
},
{
"path": "test/validators/XmlCryptoUtilities/XmlCryptoUtilities/Program.cs",
"chars": 4806,
"preview": "//\n// This example signs an XML file using an\n// envelope signature. It then verifies the \n// signed XML.\n//\nusing Syste"
},
{
"path": "test/validators/XmlCryptoUtilities/XmlCryptoUtilities/Properties/AssemblyInfo.cs",
"chars": 1427,
"preview": "using System.Reflection;\nusing System.Runtime.CompilerServices;\nusing System.Runtime.InteropServices;\n\n// General Infor"
},
{
"path": "test/validators/XmlCryptoUtilities/XmlCryptoUtilities/ValidateSignature.csproj",
"chars": 2482,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"4.0\" DefaultTargets=\"Build\" xmlns=\"http://schemas.microso"
},
{
"path": "test/validators/XmlCryptoUtilities/XmlCryptoUtilities/bin/Debug/Example.xml",
"chars": 65,
"preview": "<MyElement xmlns=\"samples\">Example text to be signed.</MyElement>"
},
{
"path": "test/validators/XmlCryptoUtilities/XmlCryptoUtilities/bin/Debug/XmlCryptoUtilities.vshost.exe.manifest",
"chars": 477,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\" manifestVers"
},
{
"path": "test/validators/XmlCryptoUtilities/XmlCryptoUtilities/bin/Debug/signedExample.xml",
"chars": 845,
"preview": "<library><book Id=\"_0\"><name>Harry Potter</name><author id=\"123456789\"><firstName>Joanne K</firstName><lastName>Rowling<"
},
{
"path": "test/validators/XmlCryptoUtilities/XmlCryptoUtilities/obj/x86/Debug/ValidateSignature.csproj.FileListAbsolute.txt",
"chars": 725,
"preview": "C:\\Users\\naveh\\Documents\\features\\projects\\xml-crypto\\test\\validators\\XmlCryptoUtilities\\XmlCryptoUtilities\\bin\\Debug\\Xm"
},
{
"path": "test/validators/XmlCryptoUtilities/XmlCryptoUtilities/program-repro-misc-validation-and-canon.cs",
"chars": 9582,
"preview": "//\n// This example signs an XML file using an\n// envelope signature. It then verifies the \n// signed XML.\n//\nusing Syste"
},
{
"path": "test/validators/XmlCryptoUtilities/XmlCryptoUtilities/utilities.cs",
"chars": 2674,
"preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Security.Cryptography"
},
{
"path": "test/validators/XmlCryptoUtilities/XmlCryptoUtilities.sln",
"chars": 872,
"preview": "\nMicrosoft Visual Studio Solution File, Format Version 11.00\n# Visual Studio 2010\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C"
},
{
"path": "test/wsfed-metadata-tests.spec.ts",
"chars": 961,
"preview": "import { SignedXml } from \"../src/index\";\nimport * as xpath from \"xpath\";\nimport * as xmldom from \"@xmldom/xmldom\";\nimpo"
},
{
"path": "tsconfig.eslint.json",
"chars": 82,
"preview": "{\n \"extends\": \"./tsconfig.json\",\n \"exclude\": [],\n \"include\": [\"test\", \"src\"]\n}\n"
},
{
"path": "tsconfig.json",
"chars": 5762,
"preview": "{\n \"compilerOptions\": {\n /* Visit https://aka.ms/tsconfig.json to read more about this file */\n\n /* Basic Options"
}
]
// ... and 6 more files (download for full content)
About this extraction
This page contains the full source code of the yaronn/xml-crypto GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 118 files (528.7 KB), approximately 178.7k tokens, and a symbol index with 151 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.