Skip to content

Commit

Permalink
chore: release HSM-compatible binaries for linux (#180)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
adamspofford-dfinity authored Mar 20, 2023
1 parent 866283b commit b0f5633
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 35 deletions.
22 changes: 14 additions & 8 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -60,36 +66,36 @@ 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
toolchain: ${{ matrix.rust }}
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
toolchain: ${{ matrix.rust }}
override: true

- name: Cross build
if: ${{ matrix.name == 'arm' }}
if: ${{ matrix.name == 'linux-arm32' }}
uses: actions-rs/cargo@v1
with:
use-cross: true
command: build
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
Expand Down
5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 15 additions & 7 deletions src/lib/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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";

Expand All @@ -47,6 +49,7 @@ pub mod signing;

pub type AnyhowResult<T = ()> = anyhow::Result<T>;

#[cfg(feature = "hsm")]
#[derive(Debug)]
pub struct HSMInfo {
pub libpath: PathBuf,
Expand All @@ -55,13 +58,14 @@ pub struct HSMInfo {
pin: RefCell<Option<String>>,
}

#[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 {
Expand All @@ -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),
}

Expand Down Expand Up @@ -265,17 +270,19 @@ pub fn get_agent(auth: &AuthInfo) -> AnyhowResult<Agent> {
.map_err(|err| anyhow!(err))
}

#[cfg(feature = "hsm")]
fn ask_pkcs11_pin_via_tty() -> Result<String, String> {
rpassword::prompt_password("HSM PIN: ")
.context("Cannot read HSM PIN from tty")
// TODO: better error string
.map_err(|e| e.to_string())
}

#[cfg(feature = "hsm")]
fn read_pkcs11_pin_env_var() -> Result<Option<String>, 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)),
}
}
Expand All @@ -291,6 +298,7 @@ pub fn get_identity(auth: &AuthInfo) -> AnyhowResult<Box<dyn Identity>> {
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() };
Expand Down
60 changes: 43 additions & 17 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ struct GlobalOpts {
pem_file: Option<PathBuf>,

/// 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"
Expand All @@ -48,10 +50,12 @@ struct GlobalOpts {
hsm_libpath: Option<PathBuf>,

/// 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<usize>,

/// 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<String>,

Expand Down Expand Up @@ -83,26 +87,48 @@ fn main() -> AnyhowResult {
}

fn get_auth(opts: GlobalOpts) -> AnyhowResult<AuthInfo> {
// 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<AuthInfo> {
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)
}
}

Expand Down

0 comments on commit b0f5633

Please sign in to comment.