diff options
author | Florian Klink <flokli@flokli.de> | 2023-01-26T13·18+0100 |
---|---|---|
committer | clbot <clbot@tvl.fyi> | 2023-01-27T14·06+0000 |
commit | a94a1434cc2a57b330a2ad6f310573fb70e15e8a (patch) | |
tree | bb9b4c32c03252ae32e409fe9bd893de19568f21 | |
parent | bda5fc58d01d7513180da4456eb279a33f76bc1c (diff) |
fix(tvix/cli): handle SRI hashes in outputHash r/5772
Instead of being called with `md5`, `sha1`, `sha256` or `sha512`, `fetchurl.nix` (from corepkgs / `<nix`) can also be called with a `hash` attribute, being an SRI hash. In that case, `builtin.derivation` is called with `outputHashAlgo` being an empty string, and `outputHash` being an SRI hash string. In other cases, an SRI hash is passed as outputHash, but outputHashAlgo is set too. Nix does modify these values in (single, fixed) output specification it serializes to ATerm, but keeps it unharmed in `env`. Move this into a construct_output_hash helper function, that can be tested better in isolation. Change-Id: Id9d716a119664c44ea7747540399966752e20187 Reviewed-on: https://cl.tvl.fyi/c/depot/+/7933 Reviewed-by: tazjin <tazjin@tvl.su> Autosubmit: flokli <flokli@flokli.de> Tested-by: BuildkiteCI
-rw-r--r-- | tvix/Cargo.lock | 129 | ||||
-rw-r--r-- | tvix/Cargo.nix | 318 | ||||
-rw-r--r-- | tvix/cli/Cargo.toml | 6 | ||||
-rw-r--r-- | tvix/cli/src/derivation.rs | 126 | ||||
-rw-r--r-- | tvix/cli/src/errors.rs | 32 |
5 files changed, 573 insertions, 38 deletions
diff --git a/tvix/Cargo.lock b/tvix/Cargo.lock index c0b82eff86dc..1eb25afbe8ed 100644 --- a/tvix/Cargo.lock +++ b/tvix/Cargo.lock @@ -173,6 +173,15 @@ dependencies = [ [[package]] name = "base64" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" +dependencies = [ + "byteorder", +] + +[[package]] +name = "base64" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" @@ -215,17 +224,38 @@ dependencies = [ "cc", "cfg-if", "constant_time_eq", - "digest", + "digest 0.10.6", "rayon", ] [[package]] name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding", + "byte-tools", + "byteorder", + "generic-array 0.12.4", +] + +[[package]] +name = "block-buffer" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" dependencies = [ - "generic-array", + "generic-array 0.14.6", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", ] [[package]] @@ -235,6 +265,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" [[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + +[[package]] name = "byteorder" version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -495,7 +531,7 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array", + "generic-array 0.14.6", "typenum", ] @@ -523,11 +559,20 @@ checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" [[package]] name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.4", +] + +[[package]] +name = "digest" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ - "block-buffer", + "block-buffer 0.10.3", "crypto-common", "subtle", ] @@ -617,6 +662,12 @@ dependencies = [ ] [[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + +[[package]] name = "fastcdc" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -770,6 +821,15 @@ dependencies = [ [[package]] name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + +[[package]] +name = "generic-array" version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" @@ -857,6 +917,12 @@ dependencies = [ ] [[package]] +name = "hex" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" + +[[package]] name = "http" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1295,6 +1361,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + +[[package]] name = "os_str_bytes" version = "6.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1912,6 +1984,30 @@ dependencies = [ ] [[package]] +name = "sha-1" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +dependencies = [ + "block-buffer 0.7.3", + "digest 0.8.1", + "fake-simd", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" +dependencies = [ + "block-buffer 0.7.3", + "digest 0.8.1", + "fake-simd", + "opaque-debug", +] + +[[package]] name = "sha2" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1919,7 +2015,7 @@ checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.6", ] [[package]] @@ -1983,6 +2079,21 @@ dependencies = [ ] [[package]] +name = "ssri" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9cec0d388f39fbe79d7aa600e8d38053bf97b1bc8d350da7c0ba800d0f423f2" +dependencies = [ + "base64 0.10.1", + "digest 0.8.1", + "hex", + "serde", + "sha-1", + "sha2 0.8.2", + "thiserror", +] + +[[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2302,7 +2413,7 @@ dependencies = [ "async-stream", "async-trait", "axum", - "base64", + "base64 0.13.1", "bytes", "futures-core", "futures-util", @@ -2498,11 +2609,15 @@ version = "0.1.0" dependencies = [ "aho-corasick", "clap 4.0.32", + "data-encoding", "dirs", "rustyline", "smol_str", + "ssri", + "test-case", "tvix-derivation", "tvix-eval", + "tvix-store-bin", ] [[package]] @@ -2514,7 +2629,7 @@ dependencies = [ "glob", "serde", "serde_json", - "sha2", + "sha2 0.10.6", "test-case", "test-generator", "thiserror", diff --git a/tvix/Cargo.nix b/tvix/Cargo.nix index 27cc40d2bb86..3160d02132ca 100644 --- a/tvix/Cargo.nix +++ b/tvix/Cargo.nix @@ -635,7 +635,24 @@ rec { ]; }; - "base64" = rec { + "base64 0.10.1" = rec { + crateName = "base64"; + version = "0.10.1"; + edition = "2015"; + sha256 = "13k6bvd3n6dm7jqn9x918w65dd9xhx454bqphbnv0bkd6n9dj98b"; + authors = [ + "Alice Maz <alice@alicemaz.com>" + "Marshall Pierce <marshall@mpierce.org>" + ]; + dependencies = [ + { + name = "byteorder"; + packageId = "byteorder"; + } + ]; + + }; + "base64 0.13.1" = rec { crateName = "base64"; version = "0.13.1"; edition = "2018"; @@ -743,7 +760,7 @@ rec { } { name = "digest"; - packageId = "digest"; + packageId = "digest 0.10.6"; optional = true; features = [ "mac" ]; } @@ -768,7 +785,7 @@ rec { }; resolvedDefaultFeatures = [ "default" "digest" "rayon" "std" ]; }; - "block-buffer" = rec { + "block-buffer 0.10.3" = rec { crateName = "block-buffer"; version = "0.10.3"; edition = "2018"; @@ -779,7 +796,52 @@ rec { dependencies = [ { name = "generic-array"; - packageId = "generic-array"; + packageId = "generic-array 0.14.6"; + } + ]; + + }; + "block-buffer 0.7.3" = rec { + crateName = "block-buffer"; + version = "0.7.3"; + edition = "2015"; + sha256 = "12v8wizynqin0hqf140kmp9s38q223mp1b0hkqk8j5pk8720v560"; + authors = [ + "RustCrypto Developers" + ]; + dependencies = [ + { + name = "block-padding"; + packageId = "block-padding"; + } + { + name = "byte-tools"; + packageId = "byte-tools"; + } + { + name = "byteorder"; + packageId = "byteorder"; + usesDefaultFeatures = false; + } + { + name = "generic-array"; + packageId = "generic-array 0.12.4"; + } + ]; + + }; + "block-padding" = rec { + crateName = "block-padding"; + version = "0.1.5"; + edition = "2015"; + sha256 = "1xbkmysiz23vimd17rnsjpw9bgjxipwfslwyygqlkx4in3dxwygs"; + authors = [ + "RustCrypto Developers" + ]; + dependencies = [ + { + name = "byte-tools"; + packageId = "byte-tools"; } ]; @@ -795,6 +857,16 @@ rec { features = { }; resolvedDefaultFeatures = [ "default" ]; }; + "byte-tools" = rec { + crateName = "byte-tools"; + version = "0.3.1"; + edition = "2015"; + sha256 = "1mqi29wsm8njpl51pfwr31wmpzs5ahlcb40wsjyd92l90ixcmdg3"; + authors = [ + "RustCrypto Developers" + ]; + + }; "byteorder" = rec { crateName = "byteorder"; version = "1.4.3"; @@ -1503,7 +1575,7 @@ rec { dependencies = [ { name = "generic-array"; - packageId = "generic-array"; + packageId = "generic-array 0.14.6"; features = [ "more_lengths" ]; } { @@ -1564,7 +1636,7 @@ rec { ]; }; - "digest" = rec { + "digest 0.10.6" = rec { crateName = "digest"; version = "0.10.6"; edition = "2018"; @@ -1575,7 +1647,7 @@ rec { dependencies = [ { name = "block-buffer"; - packageId = "block-buffer"; + packageId = "block-buffer 0.10.3"; optional = true; } { @@ -1604,6 +1676,26 @@ rec { }; resolvedDefaultFeatures = [ "alloc" "block-buffer" "core-api" "default" "mac" "std" "subtle" ]; }; + "digest 0.8.1" = rec { + crateName = "digest"; + version = "0.8.1"; + edition = "2015"; + sha256 = "1madjl27f3kj5ql7kwgvb9c8b7yb7bv7yfgx7rqzj4i3fp4cil7k"; + authors = [ + "RustCrypto Developers" + ]; + dependencies = [ + { + name = "generic-array"; + packageId = "generic-array 0.12.4"; + } + ]; + features = { + "blobby" = [ "dep:blobby" ]; + "dev" = [ "blobby" ]; + }; + resolvedDefaultFeatures = [ "std" ]; + }; "dirs" = rec { crateName = "dirs"; version = "4.0.0"; @@ -1806,6 +1898,16 @@ rec { ]; features = { }; }; + "fake-simd" = rec { + crateName = "fake-simd"; + version = "0.1.2"; + edition = "2015"; + sha256 = "1vfylvk4va2ivqx85603lyqqp0zk52cgbs4n5nfbbbqx577qm2p8"; + authors = [ + "The Rust-Crypto Project Developers" + ]; + + }; "fastcdc" = rec { crateName = "fastcdc"; version = "2.0.0"; @@ -2214,7 +2316,27 @@ rec { ]; }; - "generic-array" = rec { + "generic-array 0.12.4" = rec { + crateName = "generic-array"; + version = "0.12.4"; + edition = "2015"; + sha256 = "1gfpay78vijl9vrwl1k9v7fbvbhkhcmnrk4kfg9l6x24y4s9zpzz"; + libName = "generic_array"; + authors = [ + "Bartłomiej Kamiński <fizyk20@gmail.com>" + "Aaron Trent <novacrazy@gmail.com>" + ]; + dependencies = [ + { + name = "typenum"; + packageId = "typenum"; + } + ]; + features = { + "serde" = [ "dep:serde" ]; + }; + }; + "generic-array 0.14.6" = rec { crateName = "generic-array"; version = "0.14.6"; edition = "2015"; @@ -2478,6 +2600,16 @@ rec { }; resolvedDefaultFeatures = [ "default" ]; }; + "hex" = rec { + crateName = "hex"; + version = "0.3.2"; + edition = "2015"; + sha256 = "0xsdcjiik5j750j67zk42qdnmm4ahirk3gmkmcqgq7qls2jjcl40"; + authors = [ + "KokaKiwi <kokakiwi@kokakiwi.net>" + ]; + features = { }; + }; "http" = rec { crateName = "http"; version = "0.2.8"; @@ -3749,6 +3881,16 @@ rec { ]; }; + "opaque-debug" = rec { + crateName = "opaque-debug"; + version = "0.2.3"; + edition = "2015"; + sha256 = "172j6bs8ndclqxa2m64qc0y1772rr73g4l9fg2svscgicnbfff98"; + authors = [ + "RustCrypto Developers" + ]; + + }; "os_str_bytes" = rec { crateName = "os_str_bytes"; version = "6.4.1"; @@ -5514,7 +5656,51 @@ rec { }; resolvedDefaultFeatures = [ "serde" ]; }; - "sha2" = rec { + "sha-1" = rec { + crateName = "sha-1"; + version = "0.8.2"; + edition = "2015"; + sha256 = "1pv387q0r7llk2cqzyq0nivzvkgqgzsiygqzlv7b68z9xl5lvngp"; + libName = "sha1"; + authors = [ + "RustCrypto Developers" + ]; + dependencies = [ + { + name = "block-buffer"; + packageId = "block-buffer 0.7.3"; + } + { + name = "digest"; + packageId = "digest 0.8.1"; + } + { + name = "fake-simd"; + packageId = "fake-simd"; + } + { + name = "opaque-debug"; + packageId = "opaque-debug"; + } + ]; + devDependencies = [ + { + name = "digest"; + packageId = "digest 0.8.1"; + features = [ "dev" ]; + } + ]; + features = { + "asm" = [ "sha1-asm" ]; + "asm-aarch64" = [ "asm" "libc" ]; + "default" = [ "std" ]; + "libc" = [ "dep:libc" ]; + "sha1-asm" = [ "dep:sha1-asm" ]; + "std" = [ "digest/std" ]; + }; + resolvedDefaultFeatures = [ "default" "std" ]; + }; + "sha2 0.10.6" = rec { crateName = "sha2"; version = "0.10.6"; edition = "2018"; @@ -5534,13 +5720,13 @@ rec { } { name = "digest"; - packageId = "digest"; + packageId = "digest 0.10.6"; } ]; devDependencies = [ { name = "digest"; - packageId = "digest"; + packageId = "digest 0.10.6"; features = [ "dev" ]; } ]; @@ -5554,6 +5740,49 @@ rec { }; resolvedDefaultFeatures = [ "default" "std" ]; }; + "sha2 0.8.2" = rec { + crateName = "sha2"; + version = "0.8.2"; + edition = "2015"; + sha256 = "0s9yddvyg6anaikdl86wmwfim25c0d4m0xq0y2ghs34alxpg8mm2"; + authors = [ + "RustCrypto Developers" + ]; + dependencies = [ + { + name = "block-buffer"; + packageId = "block-buffer 0.7.3"; + } + { + name = "digest"; + packageId = "digest 0.8.1"; + } + { + name = "fake-simd"; + packageId = "fake-simd"; + } + { + name = "opaque-debug"; + packageId = "opaque-debug"; + } + ]; + devDependencies = [ + { + name = "digest"; + packageId = "digest 0.8.1"; + features = [ "dev" ]; + } + ]; + features = { + "asm" = [ "sha2-asm" ]; + "asm-aarch64" = [ "asm" "libc" ]; + "default" = [ "std" ]; + "libc" = [ "dep:libc" ]; + "sha2-asm" = [ "dep:sha2-asm" ]; + "std" = [ "digest/std" ]; + }; + resolvedDefaultFeatures = [ "default" "std" ]; + }; "sharded-slab" = rec { crateName = "sharded-slab"; version = "0.1.4"; @@ -5730,6 +5959,51 @@ rec { features = { }; resolvedDefaultFeatures = [ "all" ]; }; + "ssri" = rec { + crateName = "ssri"; + version = "7.0.0"; + edition = "2018"; + sha256 = "1wi3yk801a0bgkd51ly83dxzjfq5726hwq5asxwvx7zki39w1km9"; + authors = [ + "Kat Marchán <kzm@zkat.tech>" + ]; + dependencies = [ + { + name = "base64"; + packageId = "base64 0.10.1"; + } + { + name = "digest"; + packageId = "digest 0.8.1"; + } + { + name = "hex"; + packageId = "hex"; + } + { + name = "serde"; + packageId = "serde"; + optional = true; + } + { + name = "sha-1"; + packageId = "sha-1"; + } + { + name = "sha2"; + packageId = "sha2 0.8.2"; + } + { + name = "thiserror"; + packageId = "thiserror"; + } + ]; + features = { + "default" = [ "serde" ]; + "serde" = [ "dep:serde" ]; + }; + resolvedDefaultFeatures = [ "default" "serde" ]; + }; "static_assertions" = rec { crateName = "static_assertions"; version = "1.1.0"; @@ -6635,7 +6909,7 @@ rec { } { name = "base64"; - packageId = "base64"; + packageId = "base64 0.13.1"; } { name = "bytes"; @@ -7440,6 +7714,10 @@ rec { features = [ "derive" "env" ]; } { + name = "data-encoding"; + packageId = "data-encoding"; + } + { name = "dirs"; packageId = "dirs"; } @@ -7452,6 +7730,10 @@ rec { packageId = "smol_str"; } { + name = "ssri"; + packageId = "ssri"; + } + { name = "tvix-derivation"; packageId = "tvix-derivation"; } @@ -7459,6 +7741,16 @@ rec { name = "tvix-eval"; packageId = "tvix-eval"; } + { + name = "tvix-store-bin"; + packageId = "tvix-store-bin"; + } + ]; + devDependencies = [ + { + name = "test-case"; + packageId = "test-case"; + } ]; }; @@ -7492,7 +7784,7 @@ rec { } { name = "sha2"; - packageId = "sha2"; + packageId = "sha2 0.10.6"; } { name = "thiserror"; diff --git a/tvix/cli/Cargo.toml b/tvix/cli/Cargo.toml index 45ed05e089d3..83796855b672 100644 --- a/tvix/cli/Cargo.toml +++ b/tvix/cli/Cargo.toml @@ -12,6 +12,12 @@ tvix-eval = { path = "../eval" } tvix-derivation = { path = "../derivation" } rustyline = "10.0.0" clap = { version = "4.0", features = ["derive", "env"] } +tvix-store-bin = { path = "../store" } dirs = "4.0.0" smol_str = "0.1" aho-corasick = "0.7" +ssri = "7.0.0" +data-encoding = "2.3.3" + +[dev-dependencies] +test-case = "2.2.2" diff --git a/tvix/cli/src/derivation.rs b/tvix/cli/src/derivation.rs index d57503a696d4..122330b963ef 100644 --- a/tvix/cli/src/derivation.rs +++ b/tvix/cli/src/derivation.rs @@ -1,5 +1,6 @@ //! Implements `builtins.derivation`, the core of what makes Nix build packages. +use data_encoding::BASE64; use std::cell::RefCell; use std::collections::{btree_map, BTreeSet}; use std::rc::Rc; @@ -79,6 +80,82 @@ fn populate_inputs<I: IntoIterator<Item = String>>( } } +/// Due to the support for SRI hashes, and how these are passed along to +/// builtins.derivation, outputHash and outputHashAlgo can have values which +/// need to be further modified before constructing the Derivation struct. +/// +/// If outputHashAlgo is an SRI hash, outputHashAlgo must either be an empty +/// string, or the hash algorithm as specified in the (single) SRI (entry). +/// SRI strings with multiple hash algorithms are not supported. +/// +/// In case an SRI string was used, the (single) fixed output is populated +/// with the hash algo name, and the hash digest is populated with the +/// (lowercase) hex encoding of the digest. +/// +/// These values are only rewritten for the outputs, not what's passed to env. +fn construct_output_hash(digest: &str, algo: &str, hash_mode: Option<&str>) -> Result<Hash, Error> { + let sri_parsed = digest.parse::<ssri::Integrity>(); + // SRI strings can embed multiple hashes with different algos, but that's probably not supported + + let (digest, algo): (String, String) = match sri_parsed { + Err(e) => { + // unable to parse as SRI, but algo not set + if algo.is_empty() { + // InvalidSRIString doesn't implement PartialEq, but our error does + return Err(Error::InvalidSRIString(e.to_string())); + } + + // algo is set. Assume the digest is set to some nixbase32. + // TODO: more validation here + + (digest.to_string(), algo.to_string()) + } + Ok(sri_parsed) => { + // We don't support more than one SRI hash + if sri_parsed.hashes.len() != 1 { + return Err(Error::UnsupportedSRIMultiple(sri_parsed.hashes.len()).into()); + } + + // grab the first (and only hash) + let sri_parsed_hash = &sri_parsed.hashes[0]; + + // ensure the algorithm in the SRI is supported + if !(sri_parsed_hash.algorithm == ssri::Algorithm::Sha1 + || sri_parsed_hash.algorithm == ssri::Algorithm::Sha256 + || sri_parsed_hash.algorithm == ssri::Algorithm::Sha512) + { + Error::UnsupportedSRIAlgo(sri_parsed_hash.algorithm.to_string()); + } + + // if algo is set, it needs to match what the SRI says + if !algo.is_empty() && algo != sri_parsed_hash.algorithm.to_string() { + return Err(Error::ConflictingSRIHashAlgo( + algo.to_string(), + sri_parsed_hash.algorithm.to_string(), + )); + } + + // the digest comes base64-encoded. We need to decode, and re-encode as hexlower. + match BASE64.decode(sri_parsed_hash.digest.as_bytes()) { + Err(e) => return Err(Error::InvalidSRIDigest(e).into()), + Ok(sri_digest) => ( + data_encoding::HEXLOWER.encode(&sri_digest), + sri_parsed_hash.algorithm.to_string(), + ), + } + } + }; + + // mutate the algo string a bit more, depending on hashMode + let algo = match hash_mode { + None | Some("flat") => algo, + Some("recursive") => format!("r:{}", algo), + Some(other) => return Err(Error::InvalidOutputHashMode(other.to_string()).into()), + }; + + Ok(Hash { algo, digest }) +} + /// Populate the output configuration of a derivation based on the /// parameters passed to the call, flipping the required /// parameters for a fixed-output derivation if necessary. @@ -102,6 +179,12 @@ fn populate_output_configuration( .as_str() .to_string(); + let digest_str = hash + .force(vm)? + .coerce_to_string(CoercionKind::Strong, vm)? + .as_str() + .to_string(); + let hash_mode = match hash_mode { None => None, Some(mode) => Some( @@ -112,23 +195,12 @@ fn populate_output_configuration( ), }; - let algo = match hash_mode.as_deref() { - None | Some("flat") => algo, - Some("recursive") => format!("r:{}", algo), - Some(other) => { - return Err(Error::InvalidOutputHashMode(other.to_string()).into()) - } - }; - - out.hash = Some(Hash { - algo, - - digest: hash - .force(vm)? - .coerce_to_string(CoercionKind::Strong, vm)? - .as_str() - .to_string(), - }); + // construct out.hash + out.hash = Some(construct_output_hash( + &digest_str, + &algo, + hash_mode.as_deref(), + )?); } }, @@ -371,6 +443,7 @@ pub use derivation_builtins::builtins as derivation_builtins; #[cfg(test)] mod tests { use super::*; + use test_case::test_case; use tvix_eval::observer::NoOpObserver; static mut OBSERVER: NoOpObserver = NoOpObserver {}; @@ -576,4 +649,23 @@ mod tests { vec!["--foo".to_string(), "42".to_string(), "--bar".to_string()] ); } + + #[test_case( + "sha256-swapHA/ZO8QoDPwumMt6s5gf91oYe+oyk4EfRSyJqMg=", "sha256", Some("flat"), + Ok(Hash { algo: "sha256".to_string(), digest: "b306a91c0fd93bc4280cfc2e98cb7ab3981ff75a187bea3293811f452c89a8c8".to_string() }); + "sha256 and SRI" + )] + #[test_case( + "sha256-s6JN6XqP28g1uYMxaVAQMLiXcDG8tUs7OsE3QPhGqzA=", "", Some("flat"), + Ok(Hash { algo: "sha256".to_string(), digest: "b3a24de97a8fdbc835b9833169501030b8977031bcb54b3b3ac13740f846ab30".to_string() }); + "SRI only" + )] + fn test_construct_output_hash( + digest: &str, + algo: &str, + hash_mode: Option<&str>, + result: Result<Hash, Error>, + ) { + assert_eq!(construct_output_hash(digest, algo, hash_mode), result); + } } diff --git a/tvix/cli/src/errors.rs b/tvix/cli/src/errors.rs index cbf8ed94579d..5791c5332bfc 100644 --- a/tvix/cli/src/errors.rs +++ b/tvix/cli/src/errors.rs @@ -1,7 +1,7 @@ use std::{error, fmt::Display, rc::Rc}; use tvix_derivation::DerivationError; -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum Error { // Errors related to derivation construction DuplicateOutput(String), @@ -10,6 +10,11 @@ pub enum Error { ShadowedOutput(String), InvalidDerivation(DerivationError), InvalidOutputHashMode(String), + UnsupportedSRIAlgo(String), + UnsupportedSRIMultiple(usize), + InvalidSRIDigest(data_encoding::DecodeError), + InvalidSRIString(String), + ConflictingSRIHashAlgo(String, String), } impl Display for Error { @@ -38,6 +43,31 @@ impl Display for Error { f, "invalid output hash mode: '{mode}', only 'recursive' and 'flat` are supported" ), + Error::UnsupportedSRIAlgo(algo) => { + write!( + f, + "unsupported sri algorithm: {algo}, only sha1, sha256 or sha512 is supported" + ) + } + Error::UnsupportedSRIMultiple(n) => { + write!( + f, + "invalid number of sri hashes in string ({n}), only one hash is supported" + ) + } + Error::InvalidSRIDigest(err) => { + write!(f, "invalid sri digest: {}", err) + } + Error::InvalidSRIString(err) => { + write!(f, "failed to parse SRI string: {}", err) + } + Error::ConflictingSRIHashAlgo(algo, sri_algo) => { + write!( + f, + "outputHashAlgo is set to {}, but outputHash contains SRI with algo {}", + algo, sri_algo + ) + } } } } |