From 2c1423763bf4328b6f77a1cf9c86a2341257c51b Mon Sep 17 00:00:00 2001 From: Nicolas Mattia Date: Thu, 3 Feb 2022 11:35:54 +0100 Subject: [PATCH 1/2] Compute asset hashes in post_upgrade (#523) * Compute asset hashes in post_upgrade This removes the build-time hash computation and make it happen when `for_each_asset` is called, effectively in `post_upgrade` (and `init`). The interface is still a bit cumbersome (e.g. `for_each_asset` is still callback based) but this makes sure that `main.rs` is not changed; the focus here is the asset hashes. This is the first step in removing `CANISTER_ID` from the build process; we want to inject it in the assets directly, and this will change `main.rs` and `for_each_asset`. I've tested this locally and assets (at least `index.js`) have the same ic-certificate header as they have on `main`. * Comment * Remove sha2 from build dep * Fix typo * Grab index.html once * Simplify hash_content Co-authored-by: Frederik Rothenberger <94825501+frederikrothenberger@users.noreply.github.com> --- src/internet_identity/Cargo.toml | 3 - src/internet_identity/build.rs | 142 ---------------------------- src/internet_identity/src/assets.rs | 88 ++++++++++++++++- 3 files changed, 87 insertions(+), 146 deletions(-) delete mode 100644 src/internet_identity/build.rs diff --git a/src/internet_identity/Cargo.toml b/src/internet_identity/Cargo.toml index 68bc1685f7..d03ce3de65 100644 --- a/src/internet_identity/Cargo.toml +++ b/src/internet_identity/Cargo.toml @@ -26,9 +26,6 @@ captcha = { git = 'https://github.com/nmattia/captcha', rev = '4751b6fa4e56229c2 hex-literal = "0.2.1" rand = "0.8.3" -[build-dependencies] -sha2 = "0.9.1" - [features] # the dummy_captcha feature which ensures the captcha string is always "a" # (needed for tests) diff --git a/src/internet_identity/build.rs b/src/internet_identity/build.rs deleted file mode 100644 index 102b7249da..0000000000 --- a/src/internet_identity/build.rs +++ /dev/null @@ -1,142 +0,0 @@ -use sha2::Digest; -use std::env; -use std::fs; -use std::io::Write; -use std::path::Path; - -#[derive(Debug)] -pub enum ContentEncoding { - Identity, - GZip, -} - -#[derive(Debug)] -pub enum ContentType { - HTML, - JS, - ICO, - WEBP, - SVG, -} - -fn hash_file(path: &str) -> [u8; 32] { - let bytes = fs::read(path).unwrap_or_else(|e| panic!("failed to read file {}: {}", path, e)); - let mut hasher = sha2::Sha256::new(); - hasher.update(&bytes); - hasher.finalize().into() -} - -fn main() -> Result<(), String> { - let out_dir = env::var_os("OUT_DIR").unwrap(); - let assets_module_path = Path::new(&out_dir).join("assets.rs"); - let asset_rel_paths = [ - ( - "/", - "../../dist/index.html", - ContentEncoding::Identity, - ContentType::HTML, - ), - // The FAQ and about pages are the same webapp, but the webapp routes to the correct page - ( - "/faq", - "../../dist/index.html", - ContentEncoding::Identity, - ContentType::HTML, - ), - ( - "/about", - "../../dist/index.html", - ContentEncoding::Identity, - ContentType::HTML, - ), - ( - "/index.html", - "../../dist/index.html", - ContentEncoding::Identity, - ContentType::HTML, - ), - ( - "/index.js", - "../../dist/index.js.gz", - ContentEncoding::GZip, - ContentType::JS, - ), - ( - "/loader.webp", - "../../dist/loader.webp", - ContentEncoding::Identity, - ContentType::WEBP, - ), - ( - "/favicon.ico", - "../../dist/favicon.ico", - ContentEncoding::Identity, - ContentType::ICO, - ), - ( - "/ic-badge.svg", - "../../dist/ic-badge.svg", - ContentEncoding::Identity, - ContentType::SVG, - ), - ]; - - for (_, path, _, _) in asset_rel_paths.iter() { - if !Path::new(path).exists() { - return Err(format!("asset file {} doesn't exist", path)); - } - } - - let mut assets_module = fs::File::create(&assets_module_path).map_err(|e| { - format!( - "failed to create file {}: {}", - assets_module_path.display(), - e - ) - })?; - writeln!( - assets_module, - r#" -#[derive(Debug, PartialEq, Eq)] -pub enum ContentEncoding {{ - Identity, - GZip, -}} - -#[derive(Debug, PartialEq, Eq)] -pub enum ContentType {{ - HTML, - JS, - ICO, - WEBP, - SVG -}} - -pub fn for_each_asset(mut f: impl FnMut(&'static str, ContentEncoding, ContentType, &'static [u8], &[u8; 32])) {{ -"# - ) - .unwrap(); - - for (name, path, encoding, content_type) in asset_rel_paths.iter() { - let hash = hash_file(path); - let abs_path = Path::new(path).canonicalize().unwrap(); - writeln!( - assets_module, - " f(\"{}\", ContentEncoding::{:?}, ContentType::{:?}, &include_bytes!(\"{}\")[..], &{:?});", - name, - encoding, - content_type, - abs_path.display(), - hash - ) - .unwrap(); - } - writeln!(assets_module, "}}").unwrap(); - - println!("cargo:rerun-if-changed=build.rs"); - for (_, path, _, _) in asset_rel_paths.iter() { - println!("cargo:rerun-if-changed={}", path); - } - - Ok(()) -} diff --git a/src/internet_identity/src/assets.rs b/src/internet_identity/src/assets.rs index d7b75bb51d..7a7d8fe7ff 100644 --- a/src/internet_identity/src/assets.rs +++ b/src/internet_identity/src/assets.rs @@ -1 +1,87 @@ -include!(concat!(env!("OUT_DIR"), "/assets.rs")); +// All assets +// +// This file describes which assets are used and how (content, content type and content encoding). + +use sha2::Digest; + +#[derive(Debug, PartialEq, Eq)] +pub enum ContentEncoding { + Identity, + GZip, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum ContentType { + HTML, + JS, + ICO, + WEBP, + SVG +} + +pub fn for_each_asset(mut f: impl FnMut(&'static str, ContentEncoding, ContentType, &'static [u8], &[u8; 32])) { + + let index_html = include_bytes!("../../../dist/index.html"); + + let assets: [ (&str, &[u8], ContentEncoding, ContentType); 8] = [ + ("/", + index_html, + ContentEncoding::Identity, + ContentType::HTML, + ), + // The FAQ and about pages are the same webapp, but the webapp routes to the correct page + ( + "/faq", + index_html, + ContentEncoding::Identity, + ContentType::HTML, + ), + ( + "/about", + index_html, + ContentEncoding::Identity, + ContentType::HTML, + ), + ( + "/index.html", + index_html, + ContentEncoding::Identity, + ContentType::HTML, + ), + ( + "/index.js", + include_bytes!("../../../dist/index.js.gz"), + ContentEncoding::GZip, + ContentType::JS, + ), + ( + "/loader.webp", + include_bytes!("../../../dist/loader.webp"), + ContentEncoding::Identity, + ContentType::WEBP, + ), + ( + "/favicon.ico", + include_bytes!("../../../dist/favicon.ico"), + ContentEncoding::Identity, + ContentType::ICO, + ), + ( + "/ic-badge.svg", + include_bytes!("../../../dist/ic-badge.svg"), + ContentEncoding::Identity, + ContentType::SVG, + ), + ]; + + for (name, content, encoding, content_type) in assets { + let hash = hash_content(content); + f(name, encoding, content_type, content, &hash); + } +} + + +// Hash the content of an asset in an `ic_certified_map` friendly way +fn hash_content(bytes: &[u8]) -> [u8; 32] { + sha2::Sha256::digest(bytes).into() +} From 83a273d70fc75bd8f922a256c2081dc7b51ece37 Mon Sep 17 00:00:00 2001 From: Frederik Rothenberger <94825501+frederikrothenberger@users.noreply.github.com> Date: Thu, 3 Feb 2022 14:30:12 +0100 Subject: [PATCH 2/2] Allow ic0.app in CSP (#525) --- backend-tests/backend-tests.hs | 2 +- src/internet_identity/src/main.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend-tests/backend-tests.hs b/backend-tests/backend-tests.hs index ccb977c867..2525e777f2 100644 --- a/backend-tests/backend-tests.hs +++ b/backend-tests/backend-tests.hs @@ -437,7 +437,7 @@ validateSecurityHeaders resp = do \window-placement=(),\ \xr-spatial-tracking=()" validateHeaderMatches resp "Content-Security-Policy" "^default-src 'none';\ - \connect-src 'self';\ + \connect-src 'self' https://ic0.app;\ \img-src 'self' data:;\ \script-src 'sha256-[a-zA-Z0-9\\/=+]+' 'unsafe-inline' 'unsafe-eval' 'strict-dynamic' https:;\ \base-uri 'none';\ diff --git a/src/internet_identity/src/main.rs b/src/internet_identity/src/main.rs index 941e1df525..60dc42c08b 100644 --- a/src/internet_identity/src/main.rs +++ b/src/internet_identity/src/main.rs @@ -835,7 +835,7 @@ fn security_headers() -> Vec { ( "Content-Security-Policy".to_string(), "default-src 'none';\ - connect-src 'self';\ + connect-src 'self' https://ic0.app;\ img-src 'self' data:;\ script-src 'sha256-syYd+YuWeLD80uCtKwbaGoGom63a0pZE5KqgtA7W1d8=' 'unsafe-inline' 'unsafe-eval' 'strict-dynamic' https:;\ base-uri 'none';\