Skip to content

Commit

Permalink
moved the keyring functions back into warg_client (#275)
Browse files Browse the repository at this point in the history
- This allows us to have a simpler
`FileSystemClient::new_with_default_config()` that gets the expected
`auth_token`.

Would simplify
bytecodealliance/cargo-component#288,
bytecodealliance/wac#103,
bytecodealliance/wasm-pkg-tools#6,
esoterra/wow#3

- Added client download as a stream methods
- Validates the content digest as downloading from the registry
  • Loading branch information
calvinrp authored May 9, 2024
1 parent 0f40aa6 commit 0c96ff7
Show file tree
Hide file tree
Showing 12 changed files with 445 additions and 258 deletions.
10 changes: 3 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,10 @@ wit-parser = "0.13.1"
testresult = "0.3.0"

[features]
default = ["cli-interactive"]
default = ["cli-interactive", "keyring"]
postgres = ["warg-server/postgres"]
cli-interactive = ["warg-client/cli-interactive"]
keyring = ["warg-client/keyring"]

[workspace]
members = ["crates/server"]
Expand Down
4 changes: 3 additions & 1 deletion crates/client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ homepage = { workspace = true }
repository = { workspace = true}

[features]
default = ["cli-interactive"]
default = ["cli-interactive", "keyring"]
native-tls-vendored = ["reqwest/native-tls-vendored"]
cli-interactive = ["dep:dialoguer"]
keyring = ["dep:keyring"]

[dependencies]
warg-crypto = { workspace = true }
Expand Down Expand Up @@ -51,6 +52,7 @@ wasmprinter = "0.2.75"
sha256 = "1.4.0"
ptree = { workspace = true }
secrecy= { workspace = true }
keyring = { workspace = true, optional = true }

[target.'cfg(windows)'.dependencies.windows-sys]
version = "0.52"
Expand Down
38 changes: 36 additions & 2 deletions crates/client/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use anyhow::{anyhow, Result};
use bytes::Bytes;
use futures_util::{Stream, TryStreamExt};
use futures_util::{future::ready, stream::once, Stream, StreamExt, TryStreamExt};
use indexmap::IndexMap;
use reqwest::{
header::{HeaderMap, HeaderValue},
Expand Down Expand Up @@ -441,7 +441,10 @@ impl Client {
continue;
}

return Ok(response.bytes_stream().map_err(|e| anyhow!(e)));
return Ok(validate_stream(
digest,
response.bytes_stream().map_err(|e| anyhow!(e)),
));
}

Err(ClientError::AllSourcesFailed(digest.clone()))
Expand Down Expand Up @@ -627,3 +630,34 @@ impl Client {
Ok(())
}
}

fn validate_stream(
digest: &AnyHash,
stream: impl Stream<Item = Result<Bytes>>,
) -> impl Stream<Item = Result<Bytes>> {
let hasher = Some(digest.algorithm().hasher());
let expected = digest.clone();
stream
.map_ok(Some)
.chain(once(async { Ok(None) }))
.scan(hasher, move |hasher, res| {
ready(match res {
Ok(Some(bytes)) => {
hasher.as_mut().unwrap().update(&bytes);
Some(Ok(bytes))
}
Ok(None) => {
let hasher = std::mem::take(hasher).unwrap();
let computed = hasher.finalize();
if expected == computed {
None
} else {
Some(Err(anyhow!(
"expected digest `{expected}` but computed digest `{computed}`"
)))
}
}
Err(err) => Some(Err(err)),
})
})
}
201 changes: 201 additions & 0 deletions crates/client/src/keyring.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
//! Utilities for interacting with keyring and performing signing operations.
use crate::RegistryUrl;
use anyhow::{bail, Context, Result};
use indexmap::IndexSet;
use secrecy::{self, Secret};
use warg_crypto::signing::PrivateKey;

/// Gets the auth token entry for the given registry and key name.
pub fn get_auth_token_entry(registry_url: &RegistryUrl) -> Result<keyring::Entry> {
let label = format!("warg-auth-token:{}", registry_url.safe_label());
keyring::Entry::new(&label, &registry_url.safe_label()).context("failed to get keyring entry")
}

/// Gets the auth token
pub fn get_auth_token(registry_url: &RegistryUrl) -> Result<Option<Secret<String>>> {
let entry = get_auth_token_entry(registry_url)?;
match entry.get_password() {
Ok(secret) => Ok(Some(Secret::from(secret))),
Err(keyring::Error::NoEntry) => Ok(None),
Err(keyring::Error::Ambiguous(_)) => {
bail!("more than one auth token for registry `{registry_url}`");
}
Err(e) => {
bail!("failed to get auth token for registry `{registry_url}`: {e}");
}
}
}

/// Deletes the auth token
pub fn delete_auth_token(registry_url: &RegistryUrl) -> Result<()> {
let entry = get_auth_token_entry(registry_url)?;
match entry.delete_password() {
Ok(()) => Ok(()),
Err(keyring::Error::NoEntry) => {
bail!("no auth token found for registry `{registry_url}`");
}
Err(keyring::Error::Ambiguous(_)) => {
bail!("more than one auth token found for registry `{registry_url}`");
}
Err(e) => {
bail!("failed to delete auth torkn for registry `{registry_url}`: {e}");
}
}
}

/// Sets the auth token
pub fn set_auth_token(registry_url: &RegistryUrl, token: &str) -> Result<()> {
let entry = get_auth_token_entry(registry_url)?;
match entry.set_password(token) {
Ok(()) => Ok(()),
Err(keyring::Error::NoEntry) => {
bail!("no auth token found for registry `{registry_url}`");
}
Err(keyring::Error::Ambiguous(_)) => {
bail!("more than one auth token for registry `{registry_url}`");
}
Err(e) => {
bail!("failed to set auth token for registry `{registry_url}`: {e}");
}
}
}

/// Gets the signing key entry for the given registry and key name.
pub fn get_signing_key_entry(
registry_url: Option<&str>,
keys: &IndexSet<String>,
home_url: Option<&str>,
) -> Result<keyring::Entry> {
if let Some(registry_url) = registry_url {
if keys.contains(registry_url) {
keyring::Entry::new("warg-signing-key", registry_url)
.context("failed to get keyring entry")
} else {
keyring::Entry::new("warg-signing-key", "default")
.context("failed to get keyring entry")
}
} else {
if let Some(url) = home_url {
if keys.contains(url) {
return keyring::Entry::new(
"warg-signing-key",
&RegistryUrl::new(url)?.safe_label(),
)
.context("failed to get keyring entry");
}
}
if keys.contains("default") {
keyring::Entry::new("warg-signing-key", "default")
.context("failed to get keyring entry")
} else {
bail!(
"error: Please set a default signing key by typing `warg key set <alg:base64>` or `warg key new`"
)
}
}
}

/// Gets the signing key for the given registry registry_label and key name.
pub fn get_signing_key(
// If being called by a cli key command, this will always be a cli flag
// If being called by a client publish command, this could also be supplied by namespace map config
registry_url: Option<&str>,
keys: &IndexSet<String>,
home_url: Option<&str>,
) -> Result<PrivateKey> {
let entry = get_signing_key_entry(registry_url, keys, home_url)?;

match entry.get_password() {
Ok(secret) => PrivateKey::decode(secret).context("failed to parse signing key"),
Err(keyring::Error::NoEntry) => {
if let Some(registry_url) = registry_url {
bail!("no signing key found for registry `{registry_url}`");
} else {
bail!("no signing key found");
}
}
Err(keyring::Error::Ambiguous(_)) => {
if let Some(registry_url) = registry_url {
bail!("more than one signing key found for registry `{registry_url}`");
} else {
bail!("more than one signing key found");
}
}
Err(e) => {
if let Some(registry_url) = registry_url {
bail!("failed to get signing key for registry `{registry_url}`: {e}");
} else {
bail!("failed to get signing key`");
}
}
}
}

/// Sets the signing key for the given registry host and key name.
pub fn set_signing_key(
registry_url: Option<&str>,
key: &PrivateKey,
keys: &mut IndexSet<String>,
home_url: Option<&str>,
) -> Result<()> {
let entry = get_signing_key_entry(registry_url, keys, home_url)?;
match entry.set_password(&key.encode()) {
Ok(()) => Ok(()),
Err(keyring::Error::NoEntry) => {
if let Some(registry_url) = registry_url {
bail!("no signing key found for registry `{registry_url}`");
} else {
bail!("no signing key found`");
}
}
Err(keyring::Error::Ambiguous(_)) => {
if let Some(registry_url) = registry_url {
bail!("more than one signing key found for registry `{registry_url}`");
} else {
bail!("more than one signing key found");
}
}
Err(e) => {
if let Some(registry_url) = registry_url {
bail!("failed to get signing key for registry `{registry_url}`: {e}");
} else {
bail!("failed to get signing: {e}");
}
}
}
}

/// Deletes the signing key for the given registry host and key name.
pub fn delete_signing_key(
registry_url: Option<&str>,
keys: &IndexSet<String>,
home_url: Option<&str>,
) -> Result<()> {
let entry = get_signing_key_entry(registry_url, keys, home_url)?;

match entry.delete_password() {
Ok(()) => Ok(()),
Err(keyring::Error::NoEntry) => {
if let Some(registry_url) = registry_url {
bail!("no signing key found for registry `{registry_url}`");
} else {
bail!("no signing key found");
}
}
Err(keyring::Error::Ambiguous(_)) => {
if let Some(registry_url) = registry_url {
bail!("more than one signing key found for registry `{registry_url}`");
} else {
bail!("more than one signing key found`");
}
}
Err(e) => {
if let Some(registry_url) = registry_url {
bail!("failed to delete signing key for registry `{registry_url}`: {e}");
} else {
bail!("failed to delete signing key");
}
}
}
}
Loading

0 comments on commit 0c96ff7

Please sign in to comment.