From 227791db8cbb64d8753f14c0ccb2c3539c988ce1 Mon Sep 17 00:00:00 2001 From: Max Ammann Date: Wed, 8 Apr 2026 17:15:54 +0200 Subject: [PATCH 1/3] add feature to allow only certain algorithms Port dev shell to devenv and fix xmlsec transforms - Add devenv shell wiring around the shared Nix build environment - Use generic xmlsec transform getters exposed by dynamic backend headers - Treat assertion-only ValidateAndMark output as a verified assertion shell Remove direnv Harden SAML response fallback and align Nix docs Require namespace-aware SAML roots and strict verified Response shell extraction. Preserve inherited namespaces during xmlsec child-root reduction. Replace standalone devenv locks/docs with the flake.lock environment story. Ignore .tmp Fix devenv Harden signature algorithm allowlist validation Reject signed references whose DigestMethod hash is outside the configured allowlist, make multi-reference pre-digest selection deterministic, and cover both behaviors with regressions. --- .envrc | 5 - .gitignore | 20 + README.md | 36 +- devenv.lock | 86 +++ devenv.nix | 67 +++ devenv.yaml | 8 + flake.lock | 271 ++++++++++ flake.nix | 74 +-- nix/samael-env.nix | 30 ++ src/crypto/crypto_disabled.rs | 7 +- src/crypto/mod.rs | 79 ++- src/crypto/url_verification.rs | 2 +- src/crypto/xmlsec/mod.rs | 232 ++++++++- src/crypto/xmlsec/wrapper/mod.rs | 3 - src/crypto/xmlsec/wrapper/xmldsig.rs | 36 -- src/schema/authn_request.rs | 6 +- src/service_provider/mod.rs | 753 ++++++++++++++++++++++++++- src/service_provider/tests.rs | 162 +++++- 18 files changed, 1683 insertions(+), 194 deletions(-) delete mode 100644 .envrc create mode 100644 devenv.lock create mode 100644 devenv.nix create mode 100644 devenv.yaml create mode 100644 nix/samael-env.nix diff --git a/.envrc b/.envrc deleted file mode 100644 index f399d2c..0000000 --- a/.envrc +++ /dev/null @@ -1,5 +0,0 @@ -source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.5/direnvrc" "sha256-RuwIS+QKFj/T9M2TFXScjBsLR6V3A17YVoEW/Q6AZ1w=" - -nix_direnv_manual_reload - -use flake diff --git a/.gitignore b/.gitignore index 23455a6..0049295 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,25 @@ /target +.tmp /.direnv +/.devenv* /result **/.DS_Store .idea + +# Devenv +devenv.local.nix +devenv.local.yaml + +# pre-commit +.pre-commit-config.yaml + +# Devenv +.devenv* +devenv.local.nix +devenv.local.yaml + +# direnv +.direnv + +# pre-commit +.pre-commit-config.yaml diff --git a/README.md b/README.md index c766c36..ae10463 100644 --- a/README.md +++ b/README.md @@ -43,35 +43,27 @@ If you want to use the `"xmlsec"` feature, you'll need to install the following # Build instructions -We use [nix](https://nixos.org) to faciliate reproducible builds of `samael`. -It will ensure you have the required libraries installed in a way that won't cause any issues with the rest of your system. -If you want to take advantage of this, you'll need to put in a little bit of work. +We use [nix](https://nixos.org) flakes to provide the Rust toolchain and the C libraries required by the `xmlsec` feature without installing them globally. `flake.lock` is the single project lock file for both builds and the development shell. 1. [Install nix](https://github.com/DeterminateSystems/nix-installer) -1. Install [direnv](https://direnv.net/) and [cachix](https://docs.cachix.org) +1. Build the library: + ```sh + nix build --accept-flake-config ``` - # Add ~/.nix-profile/bin to your path first - nix profile install nixpkgs#direnv - nix profile install nixpkgs#cachix +1. Enter the development shell: + ```sh + nix develop --no-pure-eval --accept-flake-config ``` -1. Run `cachix use nix-community` to enable a binary cache for the rust toolchain (otherwise you'll build the rust toolchain from scratch) -1. `cd` into this repo and run `direnv allow` and `nix-direnv-reload` -1. Install the [direnv VS Code extension](https://marketplace.visualstudio.com/items?itemName=mkhl.direnv) -## Building the library - -Just run `nix build` - -## Entering a dev environment - -If you followed the above instructions, just `cd`-ing into the directory will setup a reproducible dev environment, -but if you don't want to install `direnv`, then just run `nix develop`. - -From their you can build as normal: +Useful commands inside the development shell: ```sh -cargo build --features xmlsec -cargo test --features xmlsec +samael-build +samael-test +samael-clippy +samael-fmt +samael-doc +samael-audit ``` # How do I use this library? diff --git a/devenv.lock b/devenv.lock new file mode 100644 index 0000000..28afaa6 --- /dev/null +++ b/devenv.lock @@ -0,0 +1,86 @@ +{ + "nodes": { + "devenv": { + "locked": { + "dir": "src/modules", + "lastModified": 1781627264, + "narHash": "sha256-TPj5d5MUyvuQZjsfDAAoYJ0SB+tNNNBrdCpD8XL0WeU=", + "owner": "cachix", + "repo": "devenv", + "rev": "0fe5629a2955141c336b95e384e2c793c01214fb", + "type": "github" + }, + "original": { + "dir": "src/modules", + "owner": "cachix", + "repo": "devenv", + "type": "github" + } + }, + "nixpkgs": { + "inputs": { + "nixpkgs-src": "nixpkgs-src" + }, + "locked": { + "lastModified": 1781620901, + "narHash": "sha256-UF6scQlG+6lRkZBUpn/3KNavhOo5G8kDWhjVHcno8uc=", + "owner": "cachix", + "repo": "devenv-nixpkgs", + "rev": "2df109b343d3c68efd752e32a444a1d9b9f89afa", + "type": "github" + }, + "original": { + "owner": "cachix", + "ref": "rolling", + "repo": "devenv-nixpkgs", + "type": "github" + } + }, + "nixpkgs-src": { + "flake": false, + "locked": { + "lastModified": 1781454065, + "narHash": "sha256-d2xfDjnfRuf/xYGdu9VVRHiav/2w5hDL/5cw2TuVAXw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "9eac87a12312b8f60dd52e1c6e1a265f6fc7f5fc", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "devenv": "devenv", + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1781666412, + "narHash": "sha256-Tzgol+o9+V4lK99r4AERI4pyFoZwg6Si2xUd1wc/WQU=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "d0e019d9543f0f1215a3d961fc4dca59aa29c638", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} \ No newline at end of file diff --git a/devenv.nix b/devenv.nix new file mode 100644 index 0000000..78e769f --- /dev/null +++ b/devenv.nix @@ -0,0 +1,67 @@ +{ pkgs, lib, ... }: + +let + inherit (pkgs) stdenv; + samaelEnv = import ./nix/samael-env.nix { inherit pkgs lib; }; + check = name: "nix build --accept-flake-config .#checks.${stdenv.system}.${name} -L"; +in +{ + languages.rust = { + enable = true; + channel = "nightly"; + lsp.enable = false; + components = [ + "rustc" + "cargo" + "clippy" + "rustfmt" + "rust-src" + ]; + }; + languages.c.enable = false; + + packages = samaelEnv.nativeBuildInputs ++ (with pkgs; [ + cargo-audit + cargo-nextest + nix + nixpkgs-fmt + ]); + + env = samaelEnv.env; + + scripts = { + "samael-build" = { + description = "Build the default Nix package."; + exec = "nix build --accept-flake-config -L"; + }; + + "samael-test" = { + description = "Run the xmlsec nextest check."; + exec = check "samael-nextest"; + }; + + "samael-clippy" = { + description = "Run the clippy check."; + exec = check "samael-clippy"; + }; + + "samael-doc" = { + description = "Build crate documentation."; + exec = check "samael-doc"; + }; + + "samael-fmt" = { + description = "Check formatting."; + exec = check "samael-fmt"; + }; + + "samael-audit" = { + description = "Audit dependencies."; + exec = check "samael-audit"; + }; + }; + + enterTest = '' + samael-test + ''; +} diff --git a/devenv.yaml b/devenv.yaml new file mode 100644 index 0000000..37e8589 --- /dev/null +++ b/devenv.yaml @@ -0,0 +1,8 @@ +inputs: + nixpkgs: + url: github:cachix/devenv-nixpkgs/rolling + rust-overlay: + url: github:oxalica/rust-overlay + inputs: + nixpkgs: + follows: nixpkgs diff --git a/flake.lock b/flake.lock index 1785152..7860334 100644 --- a/flake.lock +++ b/flake.lock @@ -16,6 +16,39 @@ "type": "github" } }, + "cachix": { + "inputs": { + "devenv": [ + "devenv" + ], + "flake-compat": [ + "devenv", + "flake-compat" + ], + "git-hooks": [ + "devenv", + "git-hooks" + ], + "nixpkgs": [ + "devenv", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1777487137, + "narHash": "sha256-TuvKVBX60mqyMT6OB5JqVEh1YIWtFMR/igLCaCdC9tw=", + "owner": "cachix", + "repo": "cachix", + "rev": "a66a440c321d35f7193472c317f42a55ccd1cb93", + "type": "github" + }, + "original": { + "owner": "cachix", + "ref": "latest", + "repo": "cachix", + "type": "github" + } + }, "crane": { "locked": { "lastModified": 1773189535, @@ -31,6 +64,91 @@ "type": "github" } }, + "crate2nix": { + "flake": false, + "locked": { + "lastModified": 1772186516, + "narHash": "sha256-8s28pzmQ6TOIUzznwFibtW1CMieMUl1rYJIxoQYor58=", + "owner": "rossng", + "repo": "crate2nix", + "rev": "ba5dd398e31ee422fbe021767eb83b0650303a6e", + "type": "github" + }, + "original": { + "owner": "rossng", + "repo": "crate2nix", + "rev": "ba5dd398e31ee422fbe021767eb83b0650303a6e", + "type": "github" + } + }, + "devenv": { + "inputs": { + "cachix": "cachix", + "crate2nix": "crate2nix", + "flake-compat": "flake-compat", + "flake-parts": "flake-parts", + "ghostty": "ghostty", + "git-hooks": "git-hooks", + "nix": "nix", + "nixd": "nixd", + "nixpkgs": [ + "nixpkgs" + ], + "rust-overlay": [ + "rust-overlay" + ] + }, + "locked": { + "lastModified": 1780630679, + "narHash": "sha256-hhQyVAYmNKziZ0T+T4Gsk0PYmnz4vdzOzpkJAmDASKM=", + "owner": "cachix", + "repo": "devenv", + "rev": "90ed6227ab389dd4e874a69a724f25dba312b754", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "devenv", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1767039857, + "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": [ + "devenv", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1778716662, + "narHash": "sha256-m1Yf0wZ8j1OHjTc2UwHwyQRSnNeSgLJOd7q5Y45hzi4=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "f7c1a2d347e4c52d5fb8d10cb4d94b5884e546fb", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, "flake-utils": { "inputs": { "systems": "systems" @@ -49,6 +167,110 @@ "type": "github" } }, + "ghostty": { + "flake": false, + "locked": { + "lastModified": 1779069789, + "narHash": "sha256-ojo+gso45/6CVSuqfSVnlWpQ4d0QeLgwok+v/g3yu0E=", + "owner": "ghostty-org", + "repo": "ghostty", + "rev": "4b7bf0b20e3baf9c1ba10c63f2ad1fd853faea8f", + "type": "github" + }, + "original": { + "owner": "ghostty-org", + "repo": "ghostty", + "type": "github" + } + }, + "git-hooks": { + "inputs": { + "flake-compat": [ + "devenv", + "flake-compat" + ], + "gitignore": "gitignore", + "nixpkgs": [ + "devenv", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1778507602, + "narHash": "sha256-kTwur1wV+01SdqskVMSo6JMEpg71ps3HpbFY2GsflKs=", + "owner": "cachix", + "repo": "git-hooks.nix", + "rev": "61ab0e80d9c7ab14c256b5b453d8b3fb0189ba0a", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "git-hooks.nix", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "devenv", + "git-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709087332, + "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "nix": { + "inputs": { + "flake-compat": [ + "devenv", + "flake-compat" + ], + "flake-parts": [ + "devenv", + "flake-parts" + ], + "git-hooks-nix": [ + "devenv", + "git-hooks" + ], + "nixpkgs": [ + "devenv", + "nixpkgs" + ], + "nixpkgs-23-11": [ + "devenv" + ], + "nixpkgs-regression": [ + "devenv" + ] + }, + "locked": { + "lastModified": 1779748925, + "narHash": "sha256-meIhqGC04O5VXbKSFXSQoOKp+XCq5RMnwAk1Guo0VQo=", + "owner": "cachix", + "repo": "nix", + "rev": "0bc443c8ff235c3547d09327b48aaa2ab98b15f2", + "type": "github" + }, + "original": { + "owner": "cachix", + "ref": "devenv-2.34", + "repo": "nix", + "type": "github" + } + }, "nix-filter": { "locked": { "lastModified": 1757882181, @@ -64,6 +286,32 @@ "type": "github" } }, + "nixd": { + "inputs": { + "flake-parts": [ + "devenv", + "flake-parts" + ], + "nixpkgs": [ + "devenv", + "nixpkgs" + ], + "treefmt-nix": "treefmt-nix" + }, + "locked": { + "lastModified": 1778381404, + "narHash": "sha256-FqhdOTA8vyoIpkHhbs2cCT7h6EWM7nsLeOYJc1ifQLE=", + "owner": "nix-community", + "repo": "nixd", + "rev": "e3e45eb76663f522e196b7f0cf34cab201db7779", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixd", + "type": "github" + } + }, "nixpkgs": { "locked": { "lastModified": 1773705440, @@ -84,6 +332,7 @@ "inputs": { "advisory-db": "advisory-db", "crane": "crane", + "devenv": "devenv", "flake-utils": "flake-utils", "nix-filter": "nix-filter", "nixpkgs": "nixpkgs", @@ -124,6 +373,28 @@ "repo": "default", "type": "github" } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "devenv", + "nixd", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1775636079, + "narHash": "sha256-pc20NRoMdiar8oPQceQT47UUZMBTiMdUuWrYu2obUP0=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "790751ff7fd3801feeaf96d7dc416a8d581265ba", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index ec4a562..c05e0aa 100644 --- a/flake.nix +++ b/flake.nix @@ -16,9 +16,19 @@ url = "github:rustsec/advisory-db"; flake = false; }; + devenv = { + url = "github:cachix/devenv"; + inputs.nixpkgs.follows = "nixpkgs"; + inputs.rust-overlay.follows = "rust-overlay"; + }; + }; + + nixConfig = { + extra-trusted-public-keys = [ "devenv.cachix.org-1:w1cLUi8dv3hnoSPGAuibQv+f9TZLr6cv/Hm9XgU50cw=" ]; + extra-substituters = [ "https://devenv.cachix.org" ]; }; - outputs = { self, nixpkgs, nix-filter, rust-overlay, crane, advisory-db, flake-utils }: + outputs = { self, nixpkgs, nix-filter, rust-overlay, crane, advisory-db, flake-utils, devenv } @ inputs: flake-utils.lib.eachDefaultSystem (system: let @@ -26,10 +36,7 @@ (import rust-overlay) (final: prev: { nix-filter = nix-filter.lib; - rust-toolchain = pkgs.rust-bin.nightly.latest.default; - rust-dev-toolchain = pkgs.rust-toolchain.override { - extensions = [ "rust-src" ]; - }; + rust-toolchain = final.rust-bin.nightly.latest.default; }) ]; pkgs = import nixpkgs { @@ -38,17 +45,7 @@ craneLib = (crane.mkLib pkgs).overrideToolchain pkgs.rust-toolchain; lib = pkgs.lib; - stdenv = pkgs.stdenv; - commonNativeBuildInputs = with pkgs; [ - libiconv - libtool - libxml2 - libxslt - llvmPackages.libclang - openssl - pkg-config - xmlsec - ]; + samaelEnv = import ./nix/samael-env.nix { inherit pkgs lib; }; fixtureFilter = path: _type: builtins.match ".*test_vectors.*" path != null || builtins.match ".*\.h" path != null; @@ -59,29 +56,12 @@ filter = sourceAndFixtures; }; cargoFile = builtins.fromTOML (builtins.readFile ./Cargo.toml); - commonArgs = { + commonArgs = samaelEnv.env // { pname = "samael"; inherit src; version = cargoFile.package.version; - # Need to tell bindgen where to find libclang - LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib"; - - # Set C flags for Rust's bindgen program. Unlike ordinary C - # compilation, bindgen does not invoke $CC directly. Instead it - # uses LLVM's libclang. To make sure all necessary flags are - # included we need to look in a few places. - # See https://web.archive.org/web/20220523141208/https://hoverbear.org/blog/rust-bindgen-in-nix/ - BINDGEN_EXTRA_CLANG_ARGS = "${builtins.readFile "${stdenv.cc}/nix-support/libc-crt1-cflags"} \ - ${builtins.readFile "${stdenv.cc}/nix-support/libc-cflags"} \ - ${builtins.readFile "${stdenv.cc}/nix-support/cc-cflags"} \ - ${builtins.readFile "${stdenv.cc}/nix-support/libcxx-cxxflags"} \ - -idirafter ${pkgs.libiconv}/include \ - ${lib.optionalString stdenv.cc.isClang "-idirafter ${stdenv.cc.cc}/lib/clang/${lib.getVersion stdenv.cc.cc}/include"} \ - ${lib.optionalString stdenv.cc.isGNU "-isystem ${stdenv.cc.cc}/include/c++/${lib.getVersion stdenv.cc.cc} -isystem ${stdenv.cc.cc}/include/c++/${lib.getVersion stdenv.cc.cc}/${stdenv.hostPlatform.config} -idirafter ${stdenv.cc.cc}/lib/gcc/${stdenv.hostPlatform.config}/${lib.getVersion stdenv.cc.cc}/include"} \ - "; - - nativeBuildInputs = commonNativeBuildInputs; + nativeBuildInputs = samaelEnv.nativeBuildInputs; cargoExtraArgs = "--features xmlsec"; cargoTestExtraArgs = "--features xmlsec"; }; @@ -96,27 +76,9 @@ # `nix build` packages.default = samael; - # `nix develop` - devShells.default = pkgs.mkShell { - # Need to tell bindgen where to find libclang - LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib"; - - # Set C flags for Rust's bindgen program. Unlike ordinary C - # compilation, bindgen does not invoke $CC directly. Instead it - # uses LLVM's libclang. To make sure all necessary flags are - # included we need to look in a few places. - # See https://web.archive.org/web/20220523141208/https://hoverbear.org/blog/rust-bindgen-in-nix/ - BINDGEN_EXTRA_CLANG_ARGS = "${builtins.readFile "${stdenv.cc}/nix-support/libc-crt1-cflags"} \ - ${builtins.readFile "${stdenv.cc}/nix-support/libc-cflags"} \ - ${builtins.readFile "${stdenv.cc}/nix-support/cc-cflags"} \ - ${builtins.readFile "${stdenv.cc}/nix-support/libcxx-cxxflags"} \ - -idirafter ${pkgs.libiconv}/include \ - ${lib.optionalString stdenv.cc.isClang "-idirafter ${stdenv.cc.cc}/lib/clang/${lib.getVersion stdenv.cc.cc}/include"} \ - ${lib.optionalString stdenv.cc.isGNU "-isystem ${stdenv.cc.cc}/include/c++/${lib.getVersion stdenv.cc.cc} -isystem ${stdenv.cc.cc}/include/c++/${lib.getVersion stdenv.cc.cc}/${stdenv.hostPlatform.config} -idirafter ${stdenv.cc.cc}/lib/gcc/${stdenv.hostPlatform.config}/${lib.getVersion stdenv.cc.cc}/include"} \ - "; - - buildInputs = with pkgs; [ rust-dev-toolchain nixpkgs-fmt ]; - nativeBuildInputs = commonNativeBuildInputs; + devShells.default = devenv.lib.mkShell { + inherit inputs pkgs; + modules = [ ./devenv.nix ]; }; checks = { diff --git a/nix/samael-env.nix b/nix/samael-env.nix new file mode 100644 index 0000000..a805f39 --- /dev/null +++ b/nix/samael-env.nix @@ -0,0 +1,30 @@ +{ pkgs, lib }: + +let + inherit (pkgs) stdenv; +in +{ + nativeBuildInputs = with pkgs; [ + libiconv + libtool + libxml2 + libxslt + llvmPackages.libclang + openssl + pkg-config + xmlsec + ]; + + env = { + LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib"; + BINDGEN_EXTRA_CLANG_ARGS = '' + ${builtins.readFile "${stdenv.cc}/nix-support/libc-crt1-cflags"} \ + ${builtins.readFile "${stdenv.cc}/nix-support/libc-cflags"} \ + ${builtins.readFile "${stdenv.cc}/nix-support/cc-cflags"} \ + ${builtins.readFile "${stdenv.cc}/nix-support/libcxx-cxxflags"} \ + -idirafter ${pkgs.libiconv}/include \ + ${lib.optionalString stdenv.cc.isClang "-idirafter ${stdenv.cc.cc}/lib/clang/${lib.getVersion stdenv.cc.cc}/include"} \ + ${lib.optionalString stdenv.cc.isGNU "-isystem ${stdenv.cc.cc}/include/c++/${lib.getVersion stdenv.cc.cc} -isystem ${stdenv.cc.cc}/include/c++/${lib.getVersion stdenv.cc.cc}/${stdenv.hostPlatform.config} -idirafter ${stdenv.cc.cc}/lib/gcc/${stdenv.hostPlatform.config}/${lib.getVersion stdenv.cc.cc}/include"} \ + ''; + }; +} diff --git a/src/crypto/crypto_disabled.rs b/src/crypto/crypto_disabled.rs index 63010dc..744b957 100644 --- a/src/crypto/crypto_disabled.rs +++ b/src/crypto/crypto_disabled.rs @@ -1,6 +1,8 @@ //! This module provides the behaviour if no crypto is available. -use crate::crypto::{CertificateDer, CryptoError, CryptoProvider, ReduceMode}; +use crate::crypto::{ + AllowedSignatureAlgorithm, CertificateDer, CryptoError, CryptoProvider, ReduceMode, +}; use crate::schema::CipherValue; pub struct NoCrypto; @@ -16,10 +18,11 @@ impl CryptoProvider for NoCrypto { Ok(()) } - fn reduce_xml_to_signed( + fn reduce_xml_to_signed_with_allowed_algorithms( _xml_str: &str, _certs: &[CertificateDer], _reduce_mode: ReduceMode, + _allowed_algorithms: Option<&[AllowedSignatureAlgorithm]>, ) -> Result { Err(CryptoError::CryptoDisabled) } diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 4067c26..0d672e0 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -61,6 +61,60 @@ impl From> for CertificateDer { } } +/// Allowed XML signature algorithms for signature verification. +/// By default, all algorithms are allowed. Use this to restrict +/// signatures and reference digests to approved hash families. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum AllowedSignatureAlgorithm { + /// RSA-SHA256 (required by most SAML profiles) + RsaSha256, + /// ECDSA-SHA256 (required by most SAML profiles) + EcdsaSha256, + /// RSA-SHA224 + RsaSha224, + /// RSA-SHA384 + RsaSha384, + /// RSA-SHA512 + RsaSha512, + /// ECDSA-SHA224 + EcdsaSha224, + /// ECDSA-SHA384 + EcdsaSha384, + /// ECDSA-SHA512 + EcdsaSha512, + /// DSA-SHA256 + DsaSha256, +} + +impl AllowedSignatureAlgorithm { + /// Returns the SignatureMethod URI as defined in XML Signature specifications. + pub fn signature_uri(&self) -> &'static str { + match self { + Self::RsaSha256 => "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", + Self::EcdsaSha256 => "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256", + Self::RsaSha224 => "http://www.w3.org/2001/04/xmldsig-more#rsa-sha224", + Self::RsaSha384 => "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384", + Self::RsaSha512 => "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512", + Self::EcdsaSha224 => "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha224", + Self::EcdsaSha384 => "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384", + Self::EcdsaSha512 => "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512", + Self::DsaSha256 => "http://www.w3.org/2009/xmldsig11#dsa-sha256", + } + } + + /// Returns the DigestMethod URI required for signed references. + pub fn digest_uri(&self) -> &'static str { + match self { + Self::RsaSha224 | Self::EcdsaSha224 => "http://www.w3.org/2001/04/xmldsig-more#sha224", + Self::RsaSha256 | Self::EcdsaSha256 | Self::DsaSha256 => { + "http://www.w3.org/2001/04/xmlenc#sha256" + } + Self::RsaSha384 | Self::EcdsaSha384 => "http://www.w3.org/2001/04/xmldsig-more#sha384", + Self::RsaSha512 | Self::EcdsaSha512 => "http://www.w3.org/2001/04/xmlenc#sha512", + } + } +} + /// Defines which algorithm is used to reduce signed XML. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ReduceMode { @@ -101,13 +155,32 @@ pub trait CryptoProvider { /// Takes an XML document, parses it, verifies all XML digital signatures against the given /// certificates, and returns output according to `reduce_mode`. /// - /// `ReduceMode::PreDigest` returns xmlsec's verified pre-digest payload for exactly one - /// reference. The validate modes return a rooted XML document derived from the original input - /// with all unsigned content removed. + /// `ReduceMode::PreDigest` returns xmlsec's verified pre-digest payload for a single verified + /// reference, or for the only verified SAML `Response` when several references are signed. + /// The validate modes return a rooted XML document derived from the original input with all + /// unsigned content removed. fn reduce_xml_to_signed( xml_str: &str, certs_der: &[CertificateDer], reduce_mode: ReduceMode, + ) -> Result { + Self::reduce_xml_to_signed_with_allowed_algorithms(xml_str, certs_der, reduce_mode, None) + } + + /// Takes an XML document, parses it, verifies all XML digital signatures against the given + /// certificates, and returns output according to `reduce_mode`. + /// + /// If `allowed_algorithms` is `Some`, only the specified SignatureMethod algorithms + /// and their corresponding DigestMethod algorithms will be accepted. + /// If `None`, all algorithms are allowed. + /// + /// This provides protection against algorithm substitution attacks by enforcing signature + /// algorithm restrictions at the xmlsec library level before any cryptographic operations. + fn reduce_xml_to_signed_with_allowed_algorithms( + xml_str: &str, + certs_der: &[CertificateDer], + reduce_mode: ReduceMode, + allowed_algorithms: Option<&[AllowedSignatureAlgorithm]>, ) -> Result; fn decrypt_assertion_key_info( diff --git a/src/crypto/url_verification.rs b/src/crypto/url_verification.rs index ed839e2..b4d8434 100644 --- a/src/crypto/url_verification.rs +++ b/src/crypto/url_verification.rs @@ -244,7 +244,7 @@ pub fn sign_url( Ok(unsigned_url) } -#[cfg(test)] +#[cfg(all(test, feature = "xmlsec"))] mod test { use super::UrlVerifier; use crate::service_provider::ServiceProvider; diff --git a/src/crypto/xmlsec/mod.rs b/src/crypto/xmlsec/mod.rs index e2f9df0..f75350f 100644 --- a/src/crypto/xmlsec/mod.rs +++ b/src/crypto/xmlsec/mod.rs @@ -49,6 +49,12 @@ pub enum XmlSecProviderError { error: Box, }, + #[error("failed to preserve namespace declaration: {}", error)] + XmlNamespaceDeclarationError { + #[source] + error: Box, + }, + #[error("encountered malformed XML ID attribute value: {id}")] XmlInvalidIdAttribute { id: String }, @@ -115,12 +121,92 @@ struct AttributeName { namespace: Option, } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +struct NamespaceDeclaration { + prefix: String, + href: String, +} + struct ParsedSelection { _document: libxml::tree::Document, shell_source: Option, sequence_nodes: Vec, } +fn signature_uses_allowed_algorithm( + sig_node: &libxml::tree::Node, + allowed: &[super::AllowedSignatureAlgorithm], +) -> bool { + let Some(signed_info) = signed_info_node(sig_node) else { + return false; + }; + let Some(signature_algorithm) = signature_method_algorithm(&signed_info) else { + return false; + }; + if !allowed + .iter() + .any(|allowed_algorithm| allowed_algorithm.signature_uri() == signature_algorithm) + { + return false; + } + + let allowed_digest_algorithms = allowed + .iter() + .map(super::AllowedSignatureAlgorithm::digest_uri) + .collect::>(); + let Some(digest_algorithms) = reference_digest_algorithms(&signed_info) else { + return false; + }; + + digest_algorithms + .iter() + .all(|algorithm| allowed_digest_algorithms.contains(algorithm.as_str())) +} + +fn signed_info_node(sig_node: &libxml::tree::Node) -> Option { + sig_node + .get_child_elements() + .into_iter() + .find(|child| is_xml_dsig_element(child, "SignedInfo")) +} + +fn signature_method_algorithm(signed_info: &libxml::tree::Node) -> Option { + signed_info + .get_child_elements() + .into_iter() + .find(|child| is_xml_dsig_element(child, "SignatureMethod"))? + .get_attribute("Algorithm") +} + +fn reference_digest_algorithms(signed_info: &libxml::tree::Node) -> Option> { + let references = signed_info + .get_child_elements() + .into_iter() + .filter(|child| is_xml_dsig_element(child, "Reference")) + .collect::>(); + if references.is_empty() { + return None; + } + + references.iter().map(reference_digest_algorithm).collect() +} + +fn reference_digest_algorithm(reference: &libxml::tree::Node) -> Option { + reference + .get_child_elements() + .into_iter() + .find(|child| is_xml_dsig_element(child, "DigestMethod"))? + .get_attribute("Algorithm") +} + +fn is_xml_dsig_element(node: &libxml::tree::Node, expected_name: &str) -> bool { + node.get_name() == expected_name + && node + .get_namespace() + .map(|namespace| namespace.get_href() == XMLNS_XML_DSIG) + .unwrap_or(false) +} + pub struct XmlSec; impl super::CryptoProvider for XmlSec { @@ -149,10 +235,14 @@ impl super::CryptoProvider for XmlSec { /// Takes an XML document, parses it, verifies all XML digital signatures against the given /// certificates, and returns a derived version of the document where all elements that are not /// covered by a digital signature have been removed. - fn reduce_xml_to_signed( + /// + /// If `allowed_algorithms` is provided, only those signature algorithms will be accepted. + /// This provides protection against algorithm substitution attacks. + fn reduce_xml_to_signed_with_allowed_algorithms( xml_str: &str, certs_der: &[CertificateDer], reduce_mode: ReduceMode, + allowed_algorithms: Option<&[super::AllowedSignatureAlgorithm]>, ) -> Result { let mut xml = XmlParser::default().parse_string(xml_str)?; @@ -173,6 +263,12 @@ impl super::CryptoProvider for XmlSec { let mut selections = Vec::new(); for sig_node in &signature_nodes { + if let Some(allowed) = allowed_algorithms { + if !signature_uses_allowed_algorithm(sig_node, allowed) { + return Err(CryptoError::InvalidSignature); + } + } + let mut verified = false; let mut verified_references = Vec::new(); @@ -180,6 +276,7 @@ impl super::CryptoProvider for XmlSec { let mut sig_ctx = XmlSecSignatureContext::new_with_flags( wrapper::XMLSEC_DSIG_FLAGS_STORE_SIGNEDINFO_REFERENCES, )?; + let key = XmlSecKey::from_memory(key_data.der_data(), XmlSecKeyFormat::CertDer)?; sig_ctx.insert_key(key); verified = sig_ctx.verify_node(sig_node)?; @@ -208,20 +305,7 @@ impl super::CryptoProvider for XmlSec { } if reduce_mode == ReduceMode::PreDigest { - if predigest_results.len() == 1 { - return Ok(predigest_results.remove(0)); - } else { - for predigest in predigest_results { - return match crate::service_provider::root_element_local_name(&predigest) - .as_deref() - { - // We want to trust the full response as that includes the relevant data. - // Everything is signed so even if we found a signed Assertion in here we could trust it, for now lets keep it minimal. - Some("Response") => Ok(predigest), - _ => Err(CryptoError::InvalidSignature), - }; - } - } + return predigest_result(predigest_results); } if selections.is_empty() { @@ -332,6 +416,26 @@ impl super::CryptoProvider for XmlSec { } } +fn predigest_result(mut predigest_results: Vec) -> Result { + if predigest_results.len() == 1 { + return Ok(predigest_results.remove(0)); + } + + let mut response_predigests = predigest_results + .into_iter() + .filter(|predigest| predigest_root_is_response(predigest)) + .collect::>(); + if response_predigests.len() == 1 { + Ok(response_predigests.remove(0)) + } else { + Err(CryptoError::InvalidSignature) + } +} + +fn predigest_root_is_response(predigest: &str) -> bool { + crate::service_provider::root_element_is_saml_protocol_response(predigest) +} + /// Searches the document for all attributes named `ID` and stores them and their values in the XML /// document's internal ID table. /// @@ -843,11 +947,53 @@ fn move_output_root_to_document_root( let mut new_root = find_node_by_ptr_in_tree(¤t_root, output_root_ptr as *const _) .ok_or(XmlSecProviderError::XmlPredigestFragmentInvalid)?; + let namespaces = in_scope_namespace_declarations(doc, &new_root); new_root.unlink(); + preserve_namespace_declarations(&mut new_root, namespaces)?; doc.set_root_element(&new_root); Ok(()) } +fn in_scope_namespace_declarations( + doc: &libxml::tree::Document, + node: &libxml::tree::Node, +) -> Vec { + node.get_namespaces(doc) + .into_iter() + .map(|namespace| NamespaceDeclaration { + prefix: namespace.get_prefix(), + href: namespace.get_href(), + }) + .filter(namespace_declaration_should_be_preserved) + .collect() +} + +fn preserve_namespace_declarations( + node: &mut libxml::tree::Node, + namespaces: Vec, +) -> Result<(), CryptoError> { + let mut declared_prefixes = node + .get_namespace_declarations() + .into_iter() + .map(|namespace| namespace.get_prefix()) + .collect::>(); + + for namespace in namespaces { + if !declared_prefixes.insert(namespace.prefix.clone()) { + continue; + } + + libxml::tree::Namespace::new(&namespace.prefix, &namespace.href, node) + .map_err(|error| XmlSecProviderError::XmlNamespaceDeclarationError { error })?; + } + + Ok(()) +} + +fn namespace_declaration_should_be_preserved(namespace: &NamespaceDeclaration) -> bool { + !namespace.href.is_empty() && namespace.prefix != "xml" && namespace.prefix != "xmlns" +} + fn element_identity_matches(left: &libxml::tree::Node, right: &libxml::tree::Node) -> bool { element_local_name(left) == element_local_name(right) && left.get_namespace().map(|namespace| namespace.get_href()) @@ -1274,6 +1420,57 @@ mod tests { doc.to_string() } + #[test] + fn predigest_result_returns_single_result_without_reclassifying_it() { + let assertion = r#""#; + + assert_eq!( + predigest_result(vec![assertion.to_string()]).unwrap(), + assertion + ); + } + + #[test] + fn predigest_result_selects_the_only_response_from_multiple_verified_references() { + let response = r#""#; + let assertion = r#""#; + + assert_eq!( + predigest_result(vec![assertion.to_string(), response.to_string()]).unwrap(), + response + ); + } + + #[test] + fn predigest_result_rejects_multiple_response_predigests() { + let first_response = + r#""#; + let second_response = + r#""#; + + assert!(matches!( + predigest_result(vec![ + first_response.to_string(), + second_response.to_string() + ]), + Err(CryptoError::InvalidSignature) + )); + } + + #[test] + fn predigest_result_rejects_wrong_namespace_response_from_multiple_verified_references() { + let wrong_namespace_response = r#""#; + let assertion = r#""#; + + assert!(matches!( + predigest_result(vec![ + assertion.to_string(), + wrong_namespace_response.to_string() + ]), + Err(CryptoError::InvalidSignature) + )); + } + #[test] fn validate_and_mark_keeps_ancestors_for_signed_assertions() { let xml = r#" @@ -1315,9 +1512,9 @@ mod tests { fn validate_and_mark_no_ancestors_roots_signed_assertions_at_the_assertion() { let xml = r#" - + - + https://idp.example.com @@ -1344,6 +1541,7 @@ mod tests { }); assert!(reduced.contains("")); } diff --git a/src/crypto/xmlsec/wrapper/mod.rs b/src/crypto/xmlsec/wrapper/mod.rs index 0f98b04..10fce63 100644 --- a/src/crypto/xmlsec/wrapper/mod.rs +++ b/src/crypto/xmlsec/wrapper/mod.rs @@ -11,9 +11,6 @@ #[doc(hidden)] pub use libxml::tree::document::Document as XmlDocument; -#[doc(hidden)] -#[allow(unused)] -pub use libxml::tree::node::Node as XmlNode; mod backend; mod bindings; diff --git a/src/crypto/xmlsec/wrapper/xmldsig.rs b/src/crypto/xmlsec/wrapper/xmldsig.rs index e706e7c..81e9dbf 100644 --- a/src/crypto/xmlsec/wrapper/xmldsig.rs +++ b/src/crypto/xmlsec/wrapper/xmldsig.rs @@ -93,25 +93,6 @@ impl XmlSecSignatureContext { Ok(result) } - /// Retrieves the URI strings from the verified reference contexts. - pub fn get_verified_reference_uris(&self) -> XmlSecResult> { - Ok(self - .get_verified_references()? - .into_iter() - .filter_map(|reference| reference.uri) - .collect()) - } - - /// Retrieves the pre-digest data from the first and only verified reference. - pub fn get_predigest_data(&self) -> XmlSecResult { - let mut references = self.get_verified_references()?; - if references.len() != 1 { - return Err(XmlSecError::SigningError); - } - - Ok(references.remove(0).predigest_xml) - } - /// Sets the key to use for signature or verification. In case a key had /// already been set, the latter one gets released in the optional return. pub fn insert_key(&mut self, key: XmlSecKey) -> Option { @@ -129,23 +110,6 @@ impl XmlSecSignatureContext { old } - /// Releases a currently set key returning `Some(key)` or None otherwise. - #[allow(unused)] - pub fn release_key(&mut self) -> Option { - unsafe { - let ctx = self.ctx.as_mut(); - if ctx.signKey.is_null() { - None - } else { - let key = XmlSecKey::from_ptr(ctx.signKey); - - ctx.signKey = null_mut(); - - Some(key) - } - } - } - /// Takes a [`XmlDocument`][xmldoc] and attempts to sign it. For this to work it has to have a properly structured /// `` node within, and a XmlSecKey must have been previously set with [`insert_key`][inskey]. /// diff --git a/src/schema/authn_request.rs b/src/schema/authn_request.rs index b0bdc38..6d740c0 100644 --- a/src/schema/authn_request.rs +++ b/src/schema/authn_request.rs @@ -1,4 +1,6 @@ -use crate::crypto::{CertificateDer, Crypto, CryptoProvider}; +use crate::crypto::CertificateDer; +#[cfg(feature = "xmlsec")] +use crate::crypto::{Crypto, CryptoProvider}; use crate::schema::{Conditions, Issuer, NameIdPolicy, RequestedAuthnContext, Subject}; use crate::signature::Signature; use chrono::prelude::*; @@ -233,7 +235,7 @@ impl TryFrom<&AuthnRequest> for Event<'_> { } } -#[cfg(test)] +#[cfg(all(test, feature = "xmlsec"))] mod test { use super::*; use crate::crypto::UrlVerifier; diff --git a/src/service_provider/mod.rs b/src/service_provider/mod.rs index 860ae9a..78fe048 100644 --- a/src/service_provider/mod.rs +++ b/src/service_provider/mod.rs @@ -14,13 +14,22 @@ use base64::{engine::general_purpose, Engine as _}; use chrono::prelude::*; use chrono::Duration; use flate2::{write::DeflateEncoder, Compression}; -use quick_xml::{de::DeError, events::Event as XmlEvent, Reader}; +#[cfg(feature = "xmlsec")] +use quick_xml::{ + de::DeError, + events::{BytesStart, Event as XmlEvent}, + name::ResolveResult, + NsReader, Writer, +}; use std::fmt::Debug; -use std::io::Write; +use std::io::{Cursor, Write}; use thiserror::Error; use url::Url; const SUBJECT_CONFIRMATION_METHOD_BEARER: &str = "urn:oasis:names:tc:SAML:2.0:cm:bearer"; +const SAML_PROTOCOL_NS: &str = "urn:oasis:names:tc:SAML:2.0:protocol"; +const SAML_ASSERTION_NS: &str = "urn:oasis:names:tc:SAML:2.0:assertion"; +const SAML_STATUS_SUCCESS: &str = "urn:oasis:names:tc:SAML:2.0:status:Success"; #[cfg(test)] mod tests; @@ -174,6 +183,11 @@ pub struct ServiceProvider { pub contact_person: Option, pub max_issue_delay: Duration, pub max_clock_skew: Duration, + /// Optional list of allowed signature algorithms for signature verification. + /// If None, all algorithms are allowed (insecure, not recommended). + /// If Some, only the specified algorithms will be accepted, providing protection + /// against algorithm substitution attacks. + pub allowed_signature_algorithms: Option>, } impl Default for ServiceProvider { @@ -194,6 +208,7 @@ impl Default for ServiceProvider { contact_person: None, max_issue_delay: Duration::seconds(90), max_clock_skew: Duration::seconds(180), + allowed_signature_algorithms: None, } } } @@ -401,33 +416,51 @@ impl ServiceProvider { ) -> Result { let (reduced_xml, reduced_from_verified_signature) = if let Some(sign_certs) = self.idp_signing_certs()? { + let allowed_algorithms = self.allowed_signature_algorithms.as_deref(); + ( - Crypto::reduce_xml_to_signed(response_xml, &sign_certs, reduce_mode) - .map_err(|_e| Error::FailedToValidateSignature)?, + Crypto::reduce_xml_to_signed_with_allowed_algorithms( + response_xml, + &sign_certs, + reduce_mode, + allowed_algorithms, + ) + .map_err(|_e| Error::FailedToValidateSignature)?, true, ) } else { (String::from(response_xml), false) }; - match root_element_local_name(&reduced_xml).as_deref() { - Some("Response") => { - let response: Response = reduced_xml - .parse() - .map_err(Error::FailedToParseSamlResponse)?; - return self.validate_parsed_response(response, possible_request_ids); - } - Some("Assertion") if reduced_from_verified_signature => { + match saml_root_element(&reduced_xml) + .map_err(|error| Error::FailedToParseSamlResponse(DeError::from(error)))? + { + Some(SamlRootElement::ProtocolResponse) => match reduced_xml.parse::() { + Ok(response) => { + return self.validate_parsed_response(response, possible_request_ids); + } + Err(_) if reduced_from_verified_signature => { + let assertion_xml = verified_assertion_from_response_shell(&reduced_xml) + .map_err(Error::FailedToParseSamlAssertion)?; + let assertion: Assertion = assertion_xml + .parse() + .map_err(Error::FailedToParseSamlAssertion)?; + self.validate_assertion(&assertion, possible_request_ids)?; + return Ok(assertion); + } + Err(error) => return Err(Error::FailedToParseSamlResponse(error)), + }, + Some(SamlRootElement::Assertion) if reduced_from_verified_signature => { let assertion: Assertion = reduced_xml .parse() .map_err(Error::FailedToParseSamlAssertion)?; self.validate_assertion(&assertion, possible_request_ids)?; return Ok(assertion); } - Some(root_name) => { - return Err(Error::FailedToParseSamlResponse(DeError::Custom(format!( - "unexpected SAML root element `{root_name}`" - )))); + Some(_) => { + return Err(Error::FailedToParseSamlResponse(DeError::Custom( + "unexpected SAML root element".to_string(), + ))); } None => {} } @@ -711,19 +744,515 @@ impl ServiceProvider { } } -pub(crate) fn root_element_local_name(xml: &str) -> Option { - let mut reader = Reader::from_str(xml); +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +enum SamlRootElement { + ProtocolResponse, + Assertion, + Unsupported, +} + +#[cfg(feature = "xmlsec")] +pub(crate) fn root_element_is_saml_protocol_response(xml: &str) -> bool { + matches!( + saml_root_element(xml), + Ok(Some(SamlRootElement::ProtocolResponse)) + ) +} + +fn saml_root_element(xml: &str) -> quick_xml::Result> { + let mut reader = NsReader::from_str(xml); loop { - match reader.read_event() { - Ok(XmlEvent::Start(start)) | Ok(XmlEvent::Empty(start)) => { - return Some(String::from_utf8_lossy(start.local_name().as_ref()).into_owned()); + match reader.read_resolved_event()? { + (namespace, XmlEvent::Start(start)) | (namespace, XmlEvent::Empty(start)) => { + return Ok(Some(classify_saml_root( + &namespace, + start.local_name().as_ref(), + ))); + } + (_, XmlEvent::Eof) => return Ok(None), + _ => {} + } + } +} + +fn classify_saml_root(namespace: &ResolveResult<'_>, local_name: &[u8]) -> SamlRootElement { + if element_is(namespace, local_name, SAML_PROTOCOL_NS, "Response") { + SamlRootElement::ProtocolResponse + } else if element_is(namespace, local_name, SAML_ASSERTION_NS, "Assertion") { + SamlRootElement::Assertion + } else { + SamlRootElement::Unsupported + } +} + +fn verified_assertion_from_response_shell(xml: &str) -> Result> { + let mut reader = NsReader::from_str(xml); + let mut writer = Writer::new(Cursor::new(Vec::new())); + let mut namespace_stack = Vec::new(); + let mut root_seen = false; + let mut response_closed = false; + let mut depth = 0usize; + let mut assertion_count = 0usize; + let mut capture_depth = 0usize; + let mut status_seen = false; + let mut status_depth = None; + let mut status_code_count = 0usize; + + loop { + let (namespace, event) = reader.read_resolved_event()?; + match event { + XmlEvent::Start(start) => { + if !root_seen { + require_response_root(&namespace, start.local_name().as_ref())?; + validate_only_namespace_declarations(&start, "Response")?; + namespace_stack.push(namespace_declarations(&start)?); + root_seen = true; + depth = 1; + continue; + } + + reject_content_after_response(response_closed)?; + + if capture_depth > 0 { + reject_nested_assertion(&namespace, start.local_name().as_ref())?; + namespace_stack.push(namespace_declarations(&start)?); + capture_depth += 1; + depth += 1; + writer.write_event(XmlEvent::Start(start.into_owned()))?; + continue; + } + + if status_depth.is_some() && depth > status_depth.unwrap() { + require_status_code(&namespace, start.local_name().as_ref())?; + validate_status_code(&start)?; + namespace_stack.push(namespace_declarations(&start)?); + status_code_count += 1; + depth += 1; + continue; + } + + if depth == 1 + && element_is( + &namespace, + start.local_name().as_ref(), + SAML_ASSERTION_NS, + "Assertion", + ) + { + assertion_count += 1; + reject_multiple_assertions(assertion_count)?; + namespace_stack.push(namespace_declarations(&start)?); + let standalone_start = standalone_assertion_start(start, &namespace_stack)?; + writer.write_event(XmlEvent::Start(standalone_start))?; + capture_depth = 1; + depth += 1; + } else if depth == 1 + && element_is( + &namespace, + start.local_name().as_ref(), + SAML_PROTOCOL_NS, + "Status", + ) + { + if status_seen { + return reduced_response_shell_error( + "reduced signed response shell contains multiple Status elements", + ); + } + validate_only_namespace_declarations(&start, "Status")?; + namespace_stack.push(namespace_declarations(&start)?); + status_seen = true; + status_depth = Some(depth); + status_code_count = 0; + depth += 1; + } else { + reject_unexpected_element(&namespace, start.local_name().as_ref())?; + } + } + XmlEvent::Empty(empty) => { + if !root_seen { + require_response_root(&namespace, empty.local_name().as_ref())?; + validate_only_namespace_declarations(&empty, "Response")?; + root_seen = true; + response_closed = true; + continue; + } + + reject_content_after_response(response_closed)?; + + if capture_depth > 0 { + reject_nested_assertion(&namespace, empty.local_name().as_ref())?; + writer.write_event(XmlEvent::Empty(empty.into_owned()))?; + continue; + } + + if status_depth.is_some() && depth > status_depth.unwrap() { + require_status_code(&namespace, empty.local_name().as_ref())?; + validate_status_code(&empty)?; + status_code_count += 1; + continue; + } + + if depth == 1 + && element_is( + &namespace, + empty.local_name().as_ref(), + SAML_ASSERTION_NS, + "Assertion", + ) + { + assertion_count += 1; + reject_multiple_assertions(assertion_count)?; + namespace_stack.push(namespace_declarations(&empty)?); + let standalone_empty = standalone_assertion_start(empty, &namespace_stack)?; + namespace_stack.pop(); + writer.write_event(XmlEvent::Empty(standalone_empty))?; + } else if depth == 1 + && element_is( + &namespace, + empty.local_name().as_ref(), + SAML_PROTOCOL_NS, + "Status", + ) + { + return reduced_response_shell_error( + "reduced signed response shell Status is missing StatusCode", + ); + } else { + reject_unexpected_element(&namespace, empty.local_name().as_ref())?; + } + } + XmlEvent::End(end) => { + let closes_status = status_depth.is_some_and(|open_status_depth| { + depth == open_status_depth + 1 + && element_is( + &namespace, + end.local_name().as_ref(), + SAML_PROTOCOL_NS, + "Status", + ) + }); + let closes_response = depth == 1 + && element_is( + &namespace, + end.local_name().as_ref(), + SAML_PROTOCOL_NS, + "Response", + ); + + if capture_depth > 0 { + writer.write_event(XmlEvent::End(end.into_owned()))?; + capture_depth -= 1; + } + + if closes_status { + if status_code_count == 0 { + return reduced_response_shell_error( + "reduced signed response shell Status is missing StatusCode", + ); + } + status_depth = None; + } + + if closes_response { + response_closed = true; + } + + if depth == 0 { + return reduced_response_shell_error( + "reduced signed response shell has an unmatched end element", + ); + } + depth -= 1; + namespace_stack.pop(); + } + XmlEvent::Text(text) => { + if capture_depth > 0 { + writer.write_event(XmlEvent::Text(text.into_owned()))?; + } else if !xml_text_is_whitespace(text.as_ref()) { + return reduced_response_shell_error( + "reduced signed response shell contains non-whitespace text", + ); + } + } + XmlEvent::CData(cdata) => { + if capture_depth > 0 { + writer.write_event(XmlEvent::CData(cdata.into_owned()))?; + } else if !xml_text_is_whitespace(cdata.as_ref()) { + return reduced_response_shell_error( + "reduced signed response shell contains non-whitespace CDATA", + ); + } + } + XmlEvent::Comment(comment) => { + if capture_depth > 0 { + writer.write_event(XmlEvent::Comment(comment.into_owned()))?; + } else { + return reduced_response_shell_error( + "reduced signed response shell contains an unexpected comment", + ); + } + } + XmlEvent::Decl(_) if !root_seen => {} + XmlEvent::Eof => break, + event => { + if capture_depth > 0 { + writer.write_event(event.into_owned())?; + } else { + return reduced_response_shell_error( + "reduced signed response shell contains unexpected XML content", + ); + } } - Ok(XmlEvent::Eof) => return None, - Ok(_) => {} - Err(_) => return None, } } + + if !root_seen { + return reduced_response_shell_error( + "reduced signed response shell is missing Response root", + ); + } + + if assertion_count != 1 { + return reduced_response_shell_error( + "reduced signed response shell must contain exactly one direct SAML assertion", + ); + } + + Ok(String::from_utf8(writer.into_inner().into_inner())?) +} + +fn require_response_root( + namespace: &ResolveResult<'_>, + local_name: &[u8], +) -> Result<(), Box> { + if element_is(namespace, local_name, SAML_PROTOCOL_NS, "Response") { + Ok(()) + } else { + reduced_response_shell_error("reduced signed response shell root is not a SAML Response") + } +} + +fn require_status_code( + namespace: &ResolveResult<'_>, + local_name: &[u8], +) -> Result<(), Box> { + if element_is(namespace, local_name, SAML_PROTOCOL_NS, "StatusCode") { + Ok(()) + } else { + reduced_response_shell_error( + "reduced signed response shell Status contains an unexpected element", + ) + } +} + +fn reject_content_after_response(response_closed: bool) -> Result<(), Box> { + if response_closed { + reduced_response_shell_error( + "reduced signed response shell contains content after Response", + ) + } else { + Ok(()) + } +} + +fn reject_multiple_assertions(assertion_count: usize) -> Result<(), Box> { + if assertion_count > 1 { + reduced_response_shell_error( + "reduced signed response shell contains multiple direct SAML assertions", + ) + } else { + Ok(()) + } +} + +fn reject_nested_assertion( + namespace: &ResolveResult<'_>, + local_name: &[u8], +) -> Result<(), Box> { + if element_is(namespace, local_name, SAML_ASSERTION_NS, "Assertion") { + reduced_response_shell_error( + "reduced signed response shell contains a nested SAML assertion", + ) + } else { + Ok(()) + } +} + +fn reject_unexpected_element( + namespace: &ResolveResult<'_>, + local_name: &[u8], +) -> Result<(), Box> { + if local_name_is(local_name, "Assertion") + && !element_is(namespace, local_name, SAML_ASSERTION_NS, "Assertion") + { + reduced_response_shell_error( + "reduced signed response shell contains an Assertion outside the SAML assertion namespace", + ) + } else if local_name_is(local_name, "Status") + && !element_is(namespace, local_name, SAML_PROTOCOL_NS, "Status") + { + reduced_response_shell_error( + "reduced signed response shell contains a Status outside the SAML protocol namespace", + ) + } else { + reduced_response_shell_error( + "reduced signed response shell contains an unexpected direct child element", + ) + } +} + +fn validate_only_namespace_declarations( + start: &BytesStart<'_>, + element_name: &str, +) -> Result<(), Box> { + for attribute in start.attributes() { + let attribute = attribute?; + if !attribute_is_namespace_declaration(attribute.key.as_ref()) { + return reduced_response_shell_error(format!( + "reduced signed response shell {element_name} contains a non-namespace attribute" + )); + } + } + + Ok(()) +} + +fn validate_status_code(start: &BytesStart<'_>) -> Result<(), Box> { + let mut value = None; + + for attribute in start.attributes() { + let attribute = attribute?; + let key = attribute.key.as_ref(); + if attribute_is_namespace_declaration(key) { + continue; + } + + if key == b"Value" { + value = Some(String::from_utf8_lossy(attribute.value.as_ref()).into_owned()); + } else { + return reduced_response_shell_error( + "reduced signed response shell StatusCode contains an unexpected attribute", + ); + } + } + + match value.as_deref() { + Some(SAML_STATUS_SUCCESS) => Ok(()), + Some(code) => reduced_response_shell_error(format!( + "reduced signed response shell StatusCode is not successful: {code}" + )), + None => reduced_response_shell_error( + "reduced signed response shell StatusCode is missing Value", + ), + } +} + +fn standalone_assertion_start( + start: BytesStart<'_>, + namespace_stack: &[Vec<(String, String)>], +) -> Result, Box> { + let declared_on_assertion = namespace_stack.last().cloned().unwrap_or_default(); + let mut standalone = start.into_owned(); + + for (prefix, href) in in_scope_namespaces(namespace_stack) { + if namespace_is_declared(&declared_on_assertion, &prefix) { + continue; + } + + let attribute_name = namespace_attribute_name(&prefix); + standalone.push_attribute((attribute_name.as_str(), href.as_str())); + } + + Ok(standalone) +} + +fn namespace_declarations( + start: &BytesStart<'_>, +) -> Result, Box> { + let mut declarations = Vec::new(); + + for attribute in start.attributes() { + let attribute = attribute?; + let key = String::from_utf8_lossy(attribute.key.as_ref()); + let prefix = if key == "xmlns" { + Some(String::new()) + } else { + key.strip_prefix("xmlns:").map(ToString::to_string) + }; + + if let Some(prefix) = prefix { + declarations.push(( + prefix, + String::from_utf8_lossy(attribute.value.as_ref()).into_owned(), + )); + } + } + + Ok(declarations) +} + +fn in_scope_namespaces(namespace_stack: &[Vec<(String, String)>]) -> Vec<(String, String)> { + let mut namespaces = Vec::new(); + + for frame in namespace_stack { + for (prefix, href) in frame { + if let Some(existing) = namespaces + .iter_mut() + .find(|(existing_prefix, _)| existing_prefix == prefix) + { + existing.1 = href.clone(); + } else { + namespaces.push((prefix.clone(), href.clone())); + } + } + } + + namespaces +} + +fn namespace_is_declared(declarations: &[(String, String)], prefix: &str) -> bool { + declarations + .iter() + .any(|(declared_prefix, _)| declared_prefix == prefix) +} + +fn namespace_attribute_name(prefix: &str) -> String { + if prefix.is_empty() { + "xmlns".to_string() + } else { + format!("xmlns:{prefix}") + } +} + +fn element_is( + namespace: &ResolveResult<'_>, + local_name: &[u8], + expected_namespace: &str, + expected_local_name: &str, +) -> bool { + matches!(namespace, ResolveResult::Bound(bound) if bound.as_ref() == expected_namespace.as_bytes()) + && local_name_is(local_name, expected_local_name) +} + +fn local_name_is(local_name: &[u8], expected: &str) -> bool { + local_name == expected.as_bytes() +} + +fn attribute_is_namespace_declaration(attribute_name: &[u8]) -> bool { + attribute_name == b"xmlns" || attribute_name.starts_with(b"xmlns:") +} + +fn xml_text_is_whitespace(text: &[u8]) -> bool { + text.iter() + .all(|byte| matches!(byte, b' ' | b'\n' | b'\r' | b'\t')) +} + +fn reduced_response_shell_error( + message: impl Into, +) -> Result> { + Err(Box::new(DeError::Custom(message.into()))) } fn parse_certificates(key_descriptor: &KeyDescriptor) -> Result, Error> { @@ -815,3 +1344,179 @@ impl AuthnRequest { Err(Box::new(CryptoError::CryptoDisabled)) } } + +#[cfg(test)] +mod reduced_response_shell_tests { + use super::*; + + const DIRECT_ASSERTION_SHELL: &str = r#" + + + +"#; + + #[test] + fn reduced_response_shell_accepts_single_direct_assertion_and_reconstructs_inherited_namespaces( + ) { + let xml = r#" + + + http://idp.example.com/metadata.php + + +"#; + + let assertion_xml = verified_assertion_from_response_shell(xml) + .expect("single direct SAML assertion should be extracted"); + + assert!(assertion_xml.starts_with("http://idp.example.com/metadata.php")); + } + + #[test] + fn reduced_response_shell_accepts_success_status_and_single_direct_assertion() { + let xml = r#" + + + + + + +"#; + + let assertion_xml = verified_assertion_from_response_shell(xml) + .expect("success Status should be allowed in the reduced shell"); + + assert!(assertion_xml.starts_with(" + + + + + +"#; + + assert!(verified_assertion_from_response_shell(xml).is_err()); + } + + #[test] + fn reduced_response_shell_rejects_multiple_direct_assertions() { + let xml = DIRECT_ASSERTION_SHELL.replace( + " ", + " \n ", + ); + + assert!(verified_assertion_from_response_shell(&xml).is_err()); + } + + #[test] + fn reduced_response_shell_rejects_wrong_namespace_assertion() { + let wrong_namespace = r#" + + + +"#; + let no_namespace = r#" + + + +"#; + + assert!(verified_assertion_from_response_shell(wrong_namespace).is_err()); + assert!(verified_assertion_from_response_shell(no_namespace).is_err()); + } + + #[test] + fn reduced_response_shell_rejects_nested_assertion() { + let xml = r#" + + + + + +"#; + + assert!(verified_assertion_from_response_shell(xml).is_err()); + } + + #[test] + fn reduced_response_shell_rejects_assertion_nested_inside_direct_assertion() { + let xml = r#" + + + + + +"#; + + assert!(verified_assertion_from_response_shell(xml).is_err()); + } + + #[test] + fn reduced_response_shell_rejects_signature_object_assertion() { + let xml = r#" + + + + + + + +"#; + + assert!(verified_assertion_from_response_shell(xml).is_err()); + } + + #[test] + fn reduced_response_shell_rejects_response_attributes() { + let xml = DIRECT_ASSERTION_SHELL.replace( + ""#; + let assertion = r#""#; + let local_name_only_response = r#""#; + let wrong_namespace_assertion = r#""#; + + assert_eq!( + saml_root_element(protocol_response).unwrap(), + Some(SamlRootElement::ProtocolResponse) + ); + assert_eq!( + saml_root_element(assertion).unwrap(), + Some(SamlRootElement::Assertion) + ); + assert_eq!( + saml_root_element(local_name_only_response).unwrap(), + Some(SamlRootElement::Unsupported) + ); + assert_eq!( + saml_root_element(wrong_namespace_assertion).unwrap(), + Some(SamlRootElement::Unsupported) + ); + } +} diff --git a/src/service_provider/tests.rs b/src/service_provider/tests.rs index 2d0b634..a411510 100644 --- a/src/service_provider/tests.rs +++ b/src/service_provider/tests.rs @@ -278,6 +278,34 @@ mod encrypted_assertion_tests { .unwrap() } + fn create_multi_signed_response_sp() -> ServiceProvider { + let idp_metadata = r#" + + + + + + MIIB+jCCAWOgAwIBAgIUdcXUPTE+mOSWxRCJh8ldmDMPzREwDQYJKoZIhvcNAQELBQAwDzENMAsGA1UEAwwEVGVzdDAeFw0yNjA0MDIxMjQzMTNaFw0yNzA0MDIxMjQzMTNaMA8xDTALBgNVBAMMBFRlc3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAJEBNDJKH5nXr0hZKcSNIY1l4HeYLPBEKJLXyAnoFTdgGrvi40YyIx9lHh0LbDVWCgxJp21BmKll0CkgmeKidvGlr3FUwtETro44L+SgmjiJNbftvFxhNkgA26O2GDQuBoQwgSiagVadWXwJKkodH8tx4ojBPYK1pBO8fHf3wOnxAgMBAAGjUzBRMB0GA1UdDgQWBBSLoT4AEwcK1+0IMwgo6JYfA4e8ZTAfBgNVHSMEGDAWgBSLoT4AEwcK1+0IMwgo6JYfA4e8ZTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4GBAAtV1hclbZBD17LMbBwyrTj7szmmeUVISPeFEPaAKqiTXrHwRZ+akajboB2JjT3YYMXX2/eDaSvq9f20vJQUvkEAaYu8eNNDKWgm4btJFAeJT8uGxizmTspdJ0cxFSwxqaosV3qIqJgpwLbzUXEcu6mKfyqDM6AeFZdZevkxmKlE + + + + + urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + + + +"# + .parse() + .unwrap(); + + let mut sp = create_predigest_assertion_sp_with_metadata( + "https://api.dev.zoo.dev/auth/saml/00000000-00000000-00000000-00000000/login", + idp_metadata, + ); + sp.allow_idp_initiated = true; + sp + } + fn refresh_assertion_validation_windows(assertion: &mut Assertion) { if let Some(conditions) = assertion.conditions.as_mut() { conditions.not_before = Some(Utc::now() - Duration::minutes(1)); @@ -417,15 +445,15 @@ mod encrypted_assertion_tests { fn test_validate_and_mark_only_assertion_signed() { let sp = create_predigest_assertion_sp("http://sp.example.com/demo1/index.php?acs"); let response_xml = include_str!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/test_vectors/response_signed_assertion.xml" + env!("CARGO_MANIFEST_DIR"), + "/test_vectors/response_signed_assertion.xml" )); let assertion = sp .parse_xml_response_with_mode( &response_xml, Some(&["ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685"]), - ReduceMode::ValidateAndMark + ReduceMode::ValidateAndMark, ) .unwrap(); @@ -606,33 +634,121 @@ mod encrypted_assertion_tests { Error::AssertionSubjectConfirmationExpiredBefore { .. } )); } - #[test] fn test_parse_xml_response_with_empty_saml_response() { - let mut sp = create_predigest_assertion_sp_with_metadata("https://api.dev.zoo.dev/auth/saml/00000000-00000000-00000000-00000000/login", r#" - - - - - - MIIB+jCCAWOgAwIBAgIUdcXUPTE+mOSWxRCJh8ldmDMPzREwDQYJKoZIhvcNAQELBQAwDzENMAsGA1UEAwwEVGVzdDAeFw0yNjA0MDIxMjQzMTNaFw0yNzA0MDIxMjQzMTNaMA8xDTALBgNVBAMMBFRlc3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAJEBNDJKH5nXr0hZKcSNIY1l4HeYLPBEKJLXyAnoFTdgGrvi40YyIx9lHh0LbDVWCgxJp21BmKll0CkgmeKidvGlr3FUwtETro44L+SgmjiJNbftvFxhNkgA26O2GDQuBoQwgSiagVadWXwJKkodH8tx4ojBPYK1pBO8fHf3wOnxAgMBAAGjUzBRMB0GA1UdDgQWBBSLoT4AEwcK1+0IMwgo6JYfA4e8ZTAfBgNVHSMEGDAWgBSLoT4AEwcK1+0IMwgo6JYfA4e8ZTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4GBAAtV1hclbZBD17LMbBwyrTj7szmmeUVISPeFEPaAKqiTXrHwRZ+akajboB2JjT3YYMXX2/eDaSvq9f20vJQUvkEAaYu8eNNDKWgm4btJFAeJT8uGxizmTspdJ0cxFSwxqaosV3qIqJgpwLbzUXEcu6mKfyqDM6AeFZdZevkxmKlE - - - - - urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress - - - -"#.parse().unwrap()); - sp.allow_idp_initiated = true; + let sp = create_multi_signed_response_sp(); let response_xml = include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/test_vectors/multi_saml_response_signed_2.xml" )); - let assertion = sp - .parse_xml_response_with_mode(response_xml, None, ReduceMode::PreDigest) + sp.parse_xml_response_with_mode(response_xml, None, ReduceMode::PreDigest) .expect("signed assertion should parse in pre-digest mode"); } + + #[test] + fn test_allowed_signature_algorithms_accepts_listed_algorithm() { + use crate::crypto::AllowedSignatureAlgorithm; + + let mut sp = create_multi_signed_response_sp(); + sp.allowed_signature_algorithms = Some(vec![AllowedSignatureAlgorithm::RsaSha256]); + + let response_xml = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/test_vectors/multi_saml_response_signed_2.xml" + )); + + sp.parse_xml_response_with_mode(response_xml, None, ReduceMode::PreDigest) + .expect("RSA-SHA256 should verify when RSA-SHA256 is allowed"); + } + + #[test] + fn test_allowed_signature_algorithms_rejects_weak_algorithms() { + use crate::crypto::AllowedSignatureAlgorithm; + + let idp_metadata: EntityDescriptor = include_str!("../../test_vectors/idp_metadata.xml") + .parse() + .unwrap(); + + // Create a ServiceProvider that only allows strong algorithms (SHA256 and above) + let sp = ServiceProviderBuilder::default() + .idp_metadata(idp_metadata) + .acs_url(Some("http://sp.example.com/acs".to_string())) + .allowed_signature_algorithms(Some(vec![ + AllowedSignatureAlgorithm::RsaSha256, + AllowedSignatureAlgorithm::EcdsaSha256, + AllowedSignatureAlgorithm::RsaSha384, + AllowedSignatureAlgorithm::RsaSha512, + ])) + .build() + .unwrap(); + + // Verify the configuration is set + assert!(sp.allowed_signature_algorithms.is_some()); + assert_eq!(sp.allowed_signature_algorithms.as_ref().unwrap().len(), 4); + + // Test that a response signed with weak RSA-SHA1 is REJECTED + // This test vector is signed with RSA-SHA1, which is NOT in our allowed list + let response_xml = include_str!("../../test_vectors/response_signed_by_idp_2.xml"); + let result = + sp.parse_xml_response(response_xml, Some(&["id-sRfZ4Fe8w3bPPOtxPcLEYW6aWpZm"])); + + // This should FAIL because RSA-SHA1 is not in the allowed list + assert!( + result.is_err(), + "RSA-SHA1 should be rejected when not in allowed list" + ); + assert!(matches!(result, Err(Error::FailedToValidateSignature))); + } + + #[test] + fn test_allowed_signature_algorithms_rejects_weak_digest_methods() { + use crate::crypto::AllowedSignatureAlgorithm; + + let idp_metadata_xml = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/test_vectors/idp_ecdsa_metadata.xml" + )); + let response_instant = "2014-07-17T01:01:48Z".parse::>().unwrap(); + let sp = ServiceProvider { + metadata_url: Some("http://test_accept_signed_with_correct_key.test".into()), + acs_url: Some("http://sp.example.com/demo1/index.php?acs".into()), + idp_metadata: idp_metadata_xml.parse().unwrap(), + max_issue_delay: Utc::now() - response_instant + Duration::seconds(60), + allowed_signature_algorithms: Some(vec![AllowedSignatureAlgorithm::EcdsaSha256]), + ..Default::default() + }; + + let response_xml = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/test_vectors/response_signed_by_idp_ecdsa.xml" + )); + let result = sp.parse_xml_response( + response_xml, + Some(&["ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685"]), + ); + + assert!(matches!(result, Err(Error::FailedToValidateSignature))); + } + + #[test] + fn test_default_has_no_algorithm_restrictions() { + let idp_metadata: EntityDescriptor = include_str!("../../test_vectors/idp_metadata.xml") + .parse() + .unwrap(); + + // ServiceProvider without explicit algorithm restrictions + let sp = ServiceProviderBuilder::default() + .idp_metadata(idp_metadata) + .acs_url(Some("http://sp.example.com/acs".to_string())) + .build() + .unwrap(); + + // Verify no restrictions are set by default + assert!(sp.allowed_signature_algorithms.is_none()); + + // Note: All other existing tests in the suite pass without restrictions, + // demonstrating that the default behavior (no restrictions) works correctly. + // This test simply verifies that the field defaults to None. + } } From 67174d83ca57f15f8cae6671414eef2392aad225 Mon Sep 17 00:00:00 2001 From: Nathan Jaremko Date: Wed, 17 Jun 2026 15:26:51 -0400 Subject: [PATCH 2/3] Increment version --- Cargo.lock | 608 +++++++++++++++++++++++++---------------------------- Cargo.toml | 2 +- 2 files changed, 290 insertions(+), 320 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 74e93c3..9bc085b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,25 +4,19 @@ version = 4 [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -34,9 +28,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "base64" @@ -60,29 +54,30 @@ dependencies = [ "quote", "regex", "rustc-hash", - "shlex", + "shlex 1.3.0", "syn", ] [[package]] name = "bitflags" -version = "2.9.0" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "cc" -version = "1.2.21" +version = "1.2.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8691782945451c1c383942c4874dbe63814f61cb57ef773cda2972682b7bb3c0" +checksum = "dad887fd958be91b5098c0248def011f4523ab786cd411be668777e55063501f" dependencies = [ - "shlex", + "find-msvc-tools", + "shlex 2.0.1", ] [[package]] @@ -96,17 +91,16 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chrono" -version = "0.4.41" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ - "android-tzdata", "iana-time-zone", "js-sys", "num-traits", @@ -134,9 +128,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] @@ -178,9 +172,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.9.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" [[package]] name = "derive_builder" @@ -215,9 +209,9 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", @@ -226,15 +220,21 @@ dependencies = [ [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "flate2" -version = "1.1.1" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", @@ -263,36 +263,71 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + [[package]] name = "getrandom" -version = "0.3.2" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", "libc", - "r-efi", - "wasi", + "r-efi 6.0.0", ] [[package]] name = "glob" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "iana-time-zone" -version = "0.1.63" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -314,21 +349,23 @@ dependencies = [ [[package]] name = "icu_collections" -version = "1.5.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", + "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", ] [[package]] -name = "icu_locid" -version = "1.5.0" +name = "icu_locale_core" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -337,99 +374,61 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" - [[package]] name = "icu_normalizer" -version = "1.5.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ - "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", - "utf16_iter", - "utf8_iter", - "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "1.5.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "1.5.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ - "displaydoc", "icu_collections", - "icu_locid_transform", + "icu_locale_core", "icu_properties_data", "icu_provider", - "tinystr", + "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "1.5.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "1.5.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", + "icu_locale_core", "writeable", "yoke", "zerofrom", + "zerotrie", "zerovec", ] -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "ident_case" version = "1.0.1" @@ -438,9 +437,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", @@ -449,9 +448,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.0" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ "icu_normalizer", "icu_properties", @@ -468,11 +467,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31" dependencies = [ - "once_cell", + "cfg-if", + "futures-util", "wasm-bindgen", ] @@ -484,18 +484,18 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.172" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libloading" -version = "0.8.6" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-targets", + "windows-link", ] [[package]] @@ -511,21 +511,21 @@ dependencies = [ [[package]] name = "litemap" -version = "0.7.5" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "log" -version = "0.4.27" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" [[package]] name = "memchr" -version = "2.7.4" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] name = "minimal-lexical" @@ -535,11 +535,12 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", + "simd-adler32", ] [[package]] @@ -563,21 +564,20 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "openssl" -version = "0.10.72" +version = "0.10.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" +checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" dependencies = [ "bitflags", "cfg-if", "foreign-types", "libc", - "once_cell", "openssl-macros", "openssl-sys", ] @@ -601,9 +601,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.108" +version = "0.9.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e145e1651e858e820e4860f7b9c5e169bc1d8ce1c86043be79fa7b7634821847" +checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" dependencies = [ "cc", "libc", @@ -613,15 +613,30 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] [[package]] name = "ppv-lite86" @@ -634,9 +649,9 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.32" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", "syn", @@ -644,9 +659,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -663,24 +678,30 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.40" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.9.1" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha", "rand_core", @@ -698,18 +719,18 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ - "getrandom", + "getrandom 0.3.4", ] [[package]] name = "regex" -version = "1.11.1" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", @@ -719,9 +740,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -730,25 +751,25 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustversion" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "samael" -version = "0.0.20" +version = "0.0.21" dependencies = [ "base64", "bindgen", @@ -773,18 +794,28 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -797,17 +828,35 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "strsim" @@ -817,9 +866,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.101" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ "proc-macro2", "quote", @@ -839,18 +888,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -859,9 +908,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.7.6" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", @@ -869,27 +918,22 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "url" -version = "2.5.4" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -898,11 +942,13 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.16.0" +version = "1.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" +checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7" dependencies = [ - "getrandom", + "getrandom 0.4.3", + "js-sys", + "wasm-bindgen", ] [[package]] @@ -912,45 +958,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" +name = "wasip2" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -958,31 +991,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f" dependencies = [ "unicode-ident", ] [[package]] name = "windows-core" -version = "0.61.0" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", @@ -993,9 +1026,9 @@ dependencies = [ [[package]] name = "windows-implement" -version = "0.60.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", @@ -1004,9 +1037,9 @@ dependencies = [ [[package]] name = "windows-interface" -version = "0.59.1" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", @@ -1015,120 +1048,46 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.1.1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-result" -version = "0.3.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ "windows-link", ] [[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "wit-bindgen-rt" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags", -] - -[[package]] -name = "write16" -version = "1.0.0" +name = "wit-bindgen" +version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] name = "writeable" -version = "0.5.5" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "yoke" -version = "0.7.5" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ - "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -1136,9 +1095,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.5" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", @@ -1148,18 +1107,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.25" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.25" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" dependencies = [ "proc-macro2", "quote", @@ -1168,18 +1127,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", @@ -1187,11 +1146,22 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + [[package]] name = "zerovec" -version = "0.10.4" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -1200,9 +1170,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.10.3" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 28b6756..a35c701 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "samael" -version = "0.0.20" +version = "0.0.21" authors = ["Nathan Jaremko "] edition = "2021" license = "MIT" From 8ab8d483d64f79ef704b8f22f40e3df63881727c Mon Sep 17 00:00:00 2001 From: Nathan Jaremko Date: Wed, 17 Jun 2026 16:08:01 -0400 Subject: [PATCH 3/3] Fix nix CI flake evaluation Trust the repository flake config in CI and run flake checks with impure evaluation so devenv can resolve the project root for devShell outputs. --- .github/workflows/deploy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 7801af6..f3235f4 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -12,5 +12,5 @@ jobs: with: name: nix-community - run: | - nix build - nix flake check + nix build --accept-flake-config + nix flake check --accept-flake-config --no-pure-eval