diff --git a/Cargo.lock b/Cargo.lock index 9cb7c2bd75..e122c65a19 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1024,6 +1024,7 @@ dependencies = [ "fwdansi", "glibc_version", "glob", + "hex", "http", "hyper 0.14.27", "import_map", @@ -1588,9 +1589,9 @@ dependencies = [ [[package]] name = "deno_npm" -version = "0.15.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b266a37deae9bc36785d44e9cc1c52d504940b89e5fea63b65119bef44b128a3" +checksum = "7aba69155a585297af4b9ba8d204567bcd51b25af77b5f4c8856b867019093ba" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 22b6aa4e85..966f9a899f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,7 +48,7 @@ deno_bench_util = { version = "0.112.0", path = "./bench_util" } test_util = { path = "./test_util" } deno_lockfile = "0.17.1" deno_media_type = { version = "0.1.1", features = ["module_specifier"] } -deno_npm = "0.15.0" +deno_npm = "0.15.1" deno_semver = "0.5.0" # exts diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 8c67d7b292..92251e78b5 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -85,6 +85,7 @@ fastwebsockets.workspace = true flate2.workspace = true fs3.workspace = true glob = "0.3.1" +hex.workspace = true http.workspace = true hyper.workspace = true import_map = "=0.15.0" diff --git a/cli/npm/resolution.rs b/cli/npm/resolution.rs index 10ff5fd92b..5595a71efa 100644 --- a/cli/npm/resolution.rs +++ b/cli/npm/resolution.rs @@ -10,6 +10,7 @@ use deno_core::parking_lot::RwLock; use deno_lockfile::NpmPackageDependencyLockfileInfo; use deno_lockfile::NpmPackageLockfileInfo; use deno_npm::registry::NpmPackageInfo; +use deno_npm::registry::NpmPackageVersionDistInfoIntegrity; use deno_npm::registry::NpmRegistryApi; use deno_npm::resolution::NpmPackageVersionResolutionError; use deno_npm::resolution::NpmPackagesPartitioned; @@ -391,6 +392,21 @@ fn populate_lockfile_from_snapshot( fn npm_package_to_lockfile_info( pkg: &NpmResolutionPackage, ) -> NpmPackageLockfileInfo { + fn integrity_for_lockfile( + integrity: NpmPackageVersionDistInfoIntegrity, + ) -> String { + match integrity { + NpmPackageVersionDistInfoIntegrity::Integrity { + algorithm, + base64_hash, + } => format!("{}-{}", algorithm, base64_hash), + NpmPackageVersionDistInfoIntegrity::UnknownIntegrity(integrity) => { + integrity.to_string() + } + NpmPackageVersionDistInfoIntegrity::LegacySha1Hex(hex) => hex.to_string(), + } + } + let dependencies = pkg .dependencies .iter() @@ -403,7 +419,7 @@ fn npm_package_to_lockfile_info( NpmPackageLockfileInfo { display_id: pkg.id.nv.to_string(), serialized_id: pkg.id.as_serialized(), - integrity: pkg.dist.integrity().to_string(), + integrity: integrity_for_lockfile(pkg.dist.integrity()), dependencies, } } diff --git a/cli/npm/tarball.rs b/cli/npm/tarball.rs index f2f8d1ba4b..e72b1afc85 100644 --- a/cli/npm/tarball.rs +++ b/cli/npm/tarball.rs @@ -8,6 +8,7 @@ use std::path::PathBuf; use deno_core::anyhow::bail; use deno_core::error::AnyError; use deno_npm::registry::NpmPackageVersionDistInfo; +use deno_npm::registry::NpmPackageVersionDistInfoIntegrity; use deno_semver::package::PackageNv; use flate2::read::GzDecoder; use tar::Archive; @@ -31,12 +32,15 @@ pub fn verify_and_extract_tarball( fn verify_tarball_integrity( package: &PackageNv, data: &[u8], - npm_integrity: &str, + npm_integrity: &NpmPackageVersionDistInfoIntegrity, ) -> Result<(), AnyError> { use ring::digest::Context; - let (algo, expected_checksum) = match npm_integrity.split_once('-') { - Some((hash_kind, checksum)) => { - let algo = match hash_kind { + let (tarball_checksum, expected_checksum) = match npm_integrity { + NpmPackageVersionDistInfoIntegrity::Integrity { + algorithm, + base64_hash, + } => { + let algo = match *algorithm { "sha512" => &ring::digest::SHA512, "sha1" => &ring::digest::SHA1_FOR_LEGACY_USE_ONLY, hash_kind => bail!( @@ -45,19 +49,28 @@ fn verify_tarball_integrity( hash_kind ), }; - (algo, checksum.to_lowercase()) + let mut hash_ctx = Context::new(algo); + hash_ctx.update(data); + let digest = hash_ctx.finish(); + let tarball_checksum = base64::encode(digest.as_ref()).to_lowercase(); + (tarball_checksum, base64_hash.to_lowercase()) + } + NpmPackageVersionDistInfoIntegrity::LegacySha1Hex(hex) => { + let mut hash_ctx = Context::new(&ring::digest::SHA1_FOR_LEGACY_USE_ONLY); + hash_ctx.update(data); + let digest = hash_ctx.finish(); + let tarball_checksum = hex::encode(digest.as_ref()).to_lowercase(); + (tarball_checksum, hex.to_lowercase()) + } + NpmPackageVersionDistInfoIntegrity::UnknownIntegrity(integrity) => { + bail!( + "Not implemented integrity kind for {}: {}", + package, + integrity + ) } - None => bail!( - "Not implemented integrity kind for {}: {}", - package, - npm_integrity - ), }; - let mut hash_ctx = Context::new(algo); - hash_ctx.update(data); - let digest = hash_ctx.finish(); - let tarball_checksum = base64::encode(digest.as_ref()).to_lowercase(); if tarball_checksum != expected_checksum { bail!( "Tarball checksum did not match what was provided by npm registry for {}.\n\nExpected: {}\nActual: {}", @@ -147,36 +160,81 @@ mod test { let actual_checksum = "z4phnx7vul3xvchq1m2ab9yg5aulvxxcg/spidns6c5h0ne8xyxysp+dgnkhfuwvy7kxvudbeoglodj6+sfapg=="; assert_eq!( - verify_tarball_integrity(&package, &Vec::new(), "test") - .unwrap_err() - .to_string(), + verify_tarball_integrity( + &package, + &Vec::new(), + &NpmPackageVersionDistInfoIntegrity::UnknownIntegrity("test") + ) + .unwrap_err() + .to_string(), "Not implemented integrity kind for package@1.0.0: test", ); assert_eq!( - verify_tarball_integrity(&package, &Vec::new(), "notimplemented-test") - .unwrap_err() - .to_string(), + verify_tarball_integrity( + &package, + &Vec::new(), + &NpmPackageVersionDistInfoIntegrity::Integrity { + algorithm: "notimplemented", + base64_hash: "test" + } + ) + .unwrap_err() + .to_string(), "Not implemented hash function for package@1.0.0: notimplemented", ); assert_eq!( - verify_tarball_integrity(&package, &Vec::new(), "sha1-test") - .unwrap_err() - .to_string(), + verify_tarball_integrity( + &package, + &Vec::new(), + &NpmPackageVersionDistInfoIntegrity::Integrity { + algorithm: "sha1", + base64_hash: "test" + } + ) + .unwrap_err() + .to_string(), concat!( "Tarball checksum did not match what was provided by npm ", "registry for package@1.0.0.\n\nExpected: test\nActual: 2jmj7l5rsw0yvb/vlwaykk/ybwk=", ), ); assert_eq!( - verify_tarball_integrity(&package, &Vec::new(), "sha512-test") - .unwrap_err() - .to_string(), + verify_tarball_integrity( + &package, + &Vec::new(), + &NpmPackageVersionDistInfoIntegrity::Integrity { + algorithm: "sha512", + base64_hash: "test" + } + ) + .unwrap_err() + .to_string(), format!("Tarball checksum did not match what was provided by npm registry for package@1.0.0.\n\nExpected: test\nActual: {actual_checksum}"), ); assert!(verify_tarball_integrity( &package, &Vec::new(), - &format!("sha512-{actual_checksum}") + &NpmPackageVersionDistInfoIntegrity::Integrity { + algorithm: "sha512", + base64_hash: actual_checksum, + }, + ) + .is_ok()); + let actual_hex = "da39a3ee5e6b4b0d3255bfef95601890afd80709"; + assert_eq!( + verify_tarball_integrity( + &package, + &Vec::new(), + &NpmPackageVersionDistInfoIntegrity::LegacySha1Hex("test"), + ) + .unwrap_err() + .to_string(), + format!("Tarball checksum did not match what was provided by npm registry for package@1.0.0.\n\nExpected: test\nActual: {actual_hex}"), + ); + assert!(verify_tarball_integrity( + &package, + &Vec::new(), + &NpmPackageVersionDistInfoIntegrity::LegacySha1Hex(actual_hex), ) .is_ok()); }