From b0f563356df1c9206ef43c915375040398d12e2c Mon Sep 17 00:00:00 2001 From: Adam Spofford <93943719+adamspofford-dfinity@users.noreply.github.com> Date: Mon, 20 Mar 2023 13:49:14 -0700 Subject: [PATCH] chore: release HSM-compatible binaries for linux (#180) Our default Linux release target uses musl, which forbids runtime dynamic loading, which prevents PKCS#11 HSMs. This PR adds a third Linux target for glibc, and moves HSM functionality behind a feature flag so that the musl version can have fewer dependencies and produce a more useful error message. --- .github/workflows/release.yml | 22 ++++++++----- Cargo.toml | 5 +-- Makefile | 2 +- src/lib/mod.rs | 22 +++++++++---- src/main.rs | 60 +++++++++++++++++++++++++---------- 5 files changed, 76 insertions(+), 35 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 037f0e65..b9cc0b13 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,9 +14,9 @@ jobs: matrix: include: - os: ubuntu-20.04 - name: linux + name: linux-musl target_file: target/x86_64-unknown-linux-musl/release/quill - asset_name: quill-linux-x86_64 + asset_name: quill-linux-x86_64-musl make_target: musl-static - os: windows-latest name: windows @@ -31,11 +31,17 @@ jobs: make_target: release rust: 1.66.1 - os: ubuntu-latest - name: arm + name: linux-arm32 target_file: target/arm-unknown-linux-gnueabihf/release-arm/quill asset_name: quill-arm_32 make_target: unused rust: 1.66.1 + - os: ubuntu-latest + name: linux + target_file: target/x86_64-unknown-linux-gnu/release/quill + asset_name: quill-linux-x86_64 + make_target: release + rust: 1.66.1 steps: - uses: actions/checkout@master @@ -60,12 +66,12 @@ jobs: echo "OPENSSL_STATIC=Yes" >> $GITHUB_ENV - name: Install toolchain (Linux static) - if: ${{ matrix.name == 'linux' }} + if: ${{ matrix.name == 'linux-musl' }} uses: mariodfinity/rust-musl-action@master with: args: make ${{ matrix.make_target }} - name: Install toolchain (ARM) - if: ${{ matrix.name == 'arm' }} + if: ${{ matrix.name == 'linux-arm32' }} uses: actions-rs/toolchain@v1 with: profile: minimal @@ -73,7 +79,7 @@ jobs: override: true target: arm-unknown-linux-gnueabihf - name: Install toolchain (Non-linux) - if: ${{ matrix.name != 'linux' && matrix.name != 'arm' }} + if: ${{ matrix.name != 'linux-musl' && matrix.name != 'linux-arm32' }} uses: actions-rs/toolchain@v1 with: profile: minimal @@ -81,7 +87,7 @@ jobs: override: true - name: Cross build - if: ${{ matrix.name == 'arm' }} + if: ${{ matrix.name == 'linux-arm32' }} uses: actions-rs/cargo@v1 with: use-cross: true @@ -89,7 +95,7 @@ jobs: args: --target arm-unknown-linux-gnueabihf --features static-ssl --profile release-arm --locked - name: Make - if: ${{ matrix.name != 'linux' && matrix.name != 'arm' }} + if: ${{ matrix.name != 'linux-musl' && matrix.name != 'linux-arm32' }} run: make ${{ matrix.make_target }} - name: Upload binaries to release diff --git a/Cargo.toml b/Cargo.toml index 65ec5334..2ec46103 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ clap = { version = "3.1.18", features = ["derive", "cargo"] } flate2 = "1.0.22" hex = {version = "0.4.2", features = ["serde"] } ic-agent = "0.21.0" -ic-identity-hsm = "0.21.0" +ic-identity-hsm = { version = "0.21.0", optional = true } ic-base-types = { git = "https://github.com/dfinity/ic", rev = "1ce7e5b0bd68760382eb2b3b810a11bd600770be" } ic-ckbtc-minter = { git = "https://github.com/dfinity/ic", rev = "1ce7e5b0bd68760382eb2b3b810a11bd600770be" } ic-icrc1 = { git = "https://github.com/dfinity/ic", rev = "1ce7e5b0bd68760382eb2b3b810a11bd600770be" } @@ -60,7 +60,8 @@ tempfile = "3.3.0" [features] static-ssl = ["openssl/vendored"] -default = ["static-ssl"] +hsm = ["dep:ic-identity-hsm"] +default = ["static-ssl", "hsm"] [profile.release-arm] inherits = "release" diff --git a/Makefile b/Makefile index 75b31500..cede0338 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ release: cargo build --release --locked musl-static: - cargo build --target x86_64-unknown-linux-musl --release --locked + cargo build --target x86_64-unknown-linux-musl --no-default-features --features static-ssl --release --locked check: cargo check --all --all-targets --all-features --tests diff --git a/src/lib/mod.rs b/src/lib/mod.rs index f9fcad31..0332a856 100644 --- a/src/lib/mod.rs +++ b/src/lib/mod.rs @@ -14,6 +14,7 @@ use ic_agent::{ }; use ic_base_types::PrincipalId; use ic_icrc1::Account; +#[cfg(feature = "hsm")] use ic_identity_hsm::HardwareIdentity; use ic_nns_constants::{ GENESIS_TOKEN_CANISTER_ID, GOVERNANCE_CANISTER_ID, LEDGER_CANISTER_ID, REGISTRY_CANISTER_ID, @@ -28,14 +29,15 @@ use simple_asn1::ASN1Block::{ BitString, Explicit, Integer, ObjectIdentifier, OctetString, Sequence, }; use simple_asn1::{oid, to_der, ASN1Class, BigInt, BigUint}; +use std::str::FromStr; +#[cfg(feature = "hsm")] +use std::{cell::RefCell, path::PathBuf}; use std::{ - cell::RefCell, - env::{self, VarError}, + env, fmt::{self, Display, Formatter}, path::Path, time::Duration, }; -use std::{path::PathBuf, str::FromStr}; pub const IC_URL: &str = "https://ic0.app"; @@ -47,6 +49,7 @@ pub mod signing; pub type AnyhowResult = anyhow::Result; +#[cfg(feature = "hsm")] #[derive(Debug)] pub struct HSMInfo { pub libpath: PathBuf, @@ -55,13 +58,14 @@ pub struct HSMInfo { pin: RefCell>, } -#[cfg(target_os = "macos")] +#[cfg(all(target_os = "macos", feature = "hsm"))] const PKCS11_LIBPATH: &str = "/Library/OpenSC/lib/pkcs11/opensc-pkcs11.so"; -#[cfg(target_os = "linux")] +#[cfg(all(target_os = "linux", feature = "hsm"))] const PKCS11_LIBPATH: &str = "/usr/lib/x86_64-linux-gnu/opensc-pkcs11.so"; -#[cfg(target_os = "windows")] +#[cfg(all(target_os = "windows", feature = "hsm"))] const PKCS11_LIBPATH: &str = r"C:\Program Files\OpenSC Project\OpenSC\pkcs11\opensc-pkcs11.dll"; +#[cfg(feature = "hsm")] impl HSMInfo { pub fn new() -> Self { HSMInfo { @@ -87,6 +91,7 @@ pub enum AuthInfo { NoAuth, // No authentication details were provided; // only unsigned queries are allowed. PemFile(String), // --private-pem file specified + #[cfg(feature = "hsm")] Pkcs11Hsm(HSMInfo), } @@ -265,6 +270,7 @@ pub fn get_agent(auth: &AuthInfo) -> AnyhowResult { .map_err(|err| anyhow!(err)) } +#[cfg(feature = "hsm")] fn ask_pkcs11_pin_via_tty() -> Result { rpassword::prompt_password("HSM PIN: ") .context("Cannot read HSM PIN from tty") @@ -272,10 +278,11 @@ fn ask_pkcs11_pin_via_tty() -> Result { .map_err(|e| e.to_string()) } +#[cfg(feature = "hsm")] fn read_pkcs11_pin_env_var() -> Result, String> { match env::var("QUILL_HSM_PIN").or_else(|_| env::var("NITROHSM_PIN")) { Ok(val) => Ok(Some(val)), - Err(VarError::NotPresent) => Ok(None), + Err(env::VarError::NotPresent) => Ok(None), Err(e) => Err(format!("{}", e)), } } @@ -291,6 +298,7 @@ pub fn get_identity(auth: &AuthInfo) -> AnyhowResult> { Err(e) => Err(e).context("couldn't load identity from PEM file"), }, }, + #[cfg(feature = "hsm")] AuthInfo::Pkcs11Hsm(info) => { let pin_fn = || { let user_set_pin = { info.pin.borrow().clone() }; diff --git a/src/main.rs b/src/main.rs index 4537a52b..3bebe4ff 100755 --- a/src/main.rs +++ b/src/main.rs @@ -28,10 +28,12 @@ struct GlobalOpts { pem_file: Option, /// Use a hardware key to sign messages. + #[cfg_attr(feature = "hsm", clap(hidden = true))] #[clap(long, group = "auth", global = true)] hsm: bool, /// Path to the PKCS#11 module to use. + #[cfg_attr(feature = "hsm", clap(hidden = true))] #[cfg_attr( target_os = "windows", doc = r"Defaults to C:\Program Files\OpenSC Project\OpenSC\pkcs11\opensc-pkcs11.dll" @@ -48,10 +50,12 @@ struct GlobalOpts { hsm_libpath: Option, /// The slot that the hardware key is in. If OpenSC is installed, `pkcs11-tool --list-slots` + #[cfg_attr(feature = "hsm", clap(hidden = true))] #[clap(long, global = true)] hsm_slot: Option, /// The ID of the key to use. Consult your hardware key's documentation. + #[cfg_attr(feature = "hsm", clap(hidden = true))] #[clap(long, global = true)] hsm_id: Option, @@ -83,26 +87,48 @@ fn main() -> AnyhowResult { } fn get_auth(opts: GlobalOpts) -> AnyhowResult { - // Get PEM from the file if provided, or try to convert from the seed file - if opts.hsm || opts.hsm_libpath.is_some() || opts.hsm_slot.is_some() || opts.hsm_id.is_some() { - let mut hsm = lib::HSMInfo::new(); - if let Some(path) = opts.hsm_libpath { - hsm.libpath = path; - } - if let Some(slot) = opts.hsm_slot { - hsm.slot = slot; + #[cfg(feature = "hsm")] + { + // Get PEM from the file if provided, or try to convert from the seed file + if opts.hsm + || opts.hsm_libpath.is_some() + || opts.hsm_slot.is_some() + || opts.hsm_id.is_some() + { + let mut hsm = lib::HSMInfo::new(); + if let Some(path) = opts.hsm_libpath { + hsm.libpath = path; + } + if let Some(slot) = opts.hsm_slot { + hsm.slot = slot; + } + if let Some(id) = opts.hsm_id { + hsm.ident = id; + } + Ok(AuthInfo::Pkcs11Hsm(hsm)) + } else { + pem_auth(opts) } - if let Some(id) = opts.hsm_id { - hsm.ident = id; + } + #[cfg(not(feature = "hsm"))] + { + if opts.hsm + || opts.hsm_libpath.is_some() + || opts.hsm_slot.is_some() + || opts.hsm_id.is_some() + { + anyhow::bail!("This build of quill does not support HSM functionality.") } - Ok(AuthInfo::Pkcs11Hsm(hsm)) + pem_auth(opts) + } +} + +fn pem_auth(opts: GlobalOpts) -> AnyhowResult { + let pem = read_pem(opts.pem_file.as_deref(), opts.seed_file.as_deref())?; + if let Some(pem) = pem { + Ok(AuthInfo::PemFile(pem)) } else { - let pem = read_pem(opts.pem_file.as_deref(), opts.seed_file.as_deref())?; - if let Some(pem) = pem { - Ok(AuthInfo::PemFile(pem)) - } else { - Ok(AuthInfo::NoAuth) - } + Ok(AuthInfo::NoAuth) } }