From a94a1434cc2a57b330a2ad6f310573fb70e15e8a Mon Sep 17 00:00:00 2001 From: Florian Klink Date: Thu, 26 Jan 2023 14:18:12 +0100 Subject: [PATCH] fix(tvix/cli): handle SRI hashes in outputHash Instead of being called with `md5`, `sha1`, `sha256` or `sha512`, `fetchurl.nix` (from corepkgs / ` Autosubmit: flokli Tested-by: BuildkiteCI --- tvix/Cargo.lock | 129 ++++++++++++++- tvix/Cargo.nix | 318 +++++++++++++++++++++++++++++++++++-- tvix/cli/Cargo.toml | 6 + tvix/cli/src/derivation.rs | 126 +++++++++++++-- tvix/cli/src/errors.rs | 32 +++- 5 files changed, 573 insertions(+), 38 deletions(-) diff --git a/tvix/Cargo.lock b/tvix/Cargo.lock index c0b82eff8..1eb25afbe 100644 --- a/tvix/Cargo.lock +++ b/tvix/Cargo.lock @@ -171,6 +171,15 @@ dependencies = [ "nix 0.23.1", ] +[[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" @@ -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]] @@ -234,6 +264,12 @@ version = "3.11.1" 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" @@ -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", ] @@ -521,13 +557,22 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" 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", ] @@ -616,6 +661,12 @@ dependencies = [ "str-buf", ] +[[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" @@ -768,6 +819,15 @@ dependencies = [ "byteorder", ] +[[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" @@ -856,6 +916,12 @@ dependencies = [ "libc", ] +[[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" @@ -1294,6 +1360,12 @@ version = "11.1.3" 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" @@ -1911,6 +1983,30 @@ dependencies = [ "serde", ] +[[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" @@ -1919,7 +2015,7 @@ checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.6", ] [[package]] @@ -1982,6 +2078,21 @@ dependencies = [ "winapi", ] +[[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" @@ -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 27cc40d2b..3160d0213 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 " + "Marshall Pierce " + ]; + 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 " + "Aaron Trent " + ]; + 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 " + ]; + features = { }; + }; "http" = rec { crateName = "http"; version = "0.2.8"; @@ -3748,6 +3880,16 @@ rec { "Simon Heath " ]; + }; + "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"; @@ -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 " + ]; + 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"; @@ -7439,6 +7713,10 @@ rec { packageId = "clap 4.0.32"; features = [ "derive" "env" ]; } + { + name = "data-encoding"; + packageId = "data-encoding"; + } { name = "dirs"; packageId = "dirs"; @@ -7451,6 +7729,10 @@ rec { name = "smol_str"; 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 45ed05e08..83796855b 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 d57503a69..122330b96 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>( } } +/// 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 { + let sri_parsed = digest.parse::(); + // 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, + ) { + 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 cbf8ed945..5791c5332 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 + ) + } } } }