Skip to content

Commit d9e1217

Browse files
authored
Merge pull request #174 from auths-dev/dev-curveAgnosticism114
Dev curve agnosticism114
2 parents 98f54cc + 1d244c4 commit d9e1217

78 files changed

Lines changed: 1308 additions & 465 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

clippy.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,19 @@ disallowed-methods = [
1010
{ path = "auths_verifier::types::CanonicalDid::new_unchecked", reason = "Use CanonicalDid::parse() for external input. Use #[allow(clippy::disallowed_methods)] with INVARIANT comment for proven-safe paths.", allow-invalid = true },
1111
{ path = "auths_verifier::core::CommitOid::new_unchecked", reason = "Use CommitOid::parse() for external input. Use #[allow(clippy::disallowed_methods)] with INVARIANT comment for proven-safe paths.", allow-invalid = true },
1212
{ path = "auths_verifier::core::PublicKeyHex::new_unchecked", reason = "Use PublicKeyHex::parse() for external input. Use #[allow(clippy::disallowed_methods)] with INVARIANT comment for proven-safe paths.", allow-invalid = true },
13+
14+
# === Curve-agnostic refactor (fn-114) — ban Ed25519-hardcoded APIs ===
15+
# Removed in fn-114.40 cleanup after all sweeps complete.
16+
{ path = "ring::signature::Ed25519KeyPair::from_pkcs8", reason = "use TypedSignerKey::from_pkcs8 — dispatches on curve.", allow-invalid = true },
17+
{ path = "ring::signature::Ed25519KeyPair::from_seed_unchecked", reason = "use auths_crypto::sign(&TypedSeed, msg).", allow-invalid = true },
18+
{ path = "ring::signature::Ed25519KeyPair::generate_pkcs8", reason = "use inception::generate_keypair_for_init(curve).", allow-invalid = true },
19+
{ path = "ring::signature::UnparsedPublicKey::new", reason = "use DevicePublicKey::verify — dispatches on curve.", allow-invalid = true },
20+
{ path = "auths_crypto::parse_ed25519_seed", reason = "use parse_key_material — returns TypedSeed.", allow-invalid = true },
21+
{ path = "auths_crypto::parse_ed25519_key_material", reason = "use parse_key_material — returns TypedSeed.", allow-invalid = true },
22+
{ path = "auths_core::crypto::provider_bridge::sign_ed25519_sync", reason = "use auths_crypto::sign(&TypedSeed, msg).", allow-invalid = true },
23+
{ path = "auths_core::crypto::provider_bridge::ed25519_public_key_from_seed_sync", reason = "use auths_crypto::public_key(&TypedSeed) or TypedSignerKey::public_key().", allow-invalid = true },
24+
{ path = "auths_crypto::did_key_to_ed25519", reason = "use did_key_decode — returns DecodedDidKey.", allow-invalid = true },
25+
{ path = "auths_id::identity::resolve::ed25519_to_did_key", reason = "use did_key_decode + DecodedDidKey::P256/Ed25519.", allow-invalid = true },
1326
]
1427
allow-unwrap-in-tests = true
1528
allow-expect-in-tests = true

crates/auths-cli/clippy.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,17 @@ disallowed-methods = [
1818
{ path = "auths_verifier::types::CanonicalDid::new_unchecked", reason = "Use CanonicalDid::parse() for external input. Use #[allow(clippy::disallowed_methods)] with INVARIANT comment for proven-safe paths.", allow-invalid = true },
1919
{ path = "auths_verifier::core::CommitOid::new_unchecked", reason = "Use CommitOid::parse() for external input. Use #[allow(clippy::disallowed_methods)] with INVARIANT comment for proven-safe paths.", allow-invalid = true },
2020
{ path = "auths_verifier::core::PublicKeyHex::new_unchecked", reason = "Use PublicKeyHex::parse() for external input. Use #[allow(clippy::disallowed_methods)] with INVARIANT comment for proven-safe paths.", allow-invalid = true },
21+
22+
# === Curve-agnostic refactor (fn-114) — ban Ed25519-hardcoded APIs ===
23+
# Removed in fn-114.40 cleanup after all sweeps complete.
24+
{ path = "ring::signature::Ed25519KeyPair::from_pkcs8", reason = "use TypedSignerKey::from_pkcs8 — dispatches on curve.", allow-invalid = true },
25+
{ path = "ring::signature::Ed25519KeyPair::from_seed_unchecked", reason = "use auths_crypto::sign(&TypedSeed, msg).", allow-invalid = true },
26+
{ path = "ring::signature::Ed25519KeyPair::generate_pkcs8", reason = "use inception::generate_keypair_for_init(curve).", allow-invalid = true },
27+
{ path = "ring::signature::UnparsedPublicKey::new", reason = "use DevicePublicKey::verify — dispatches on curve.", allow-invalid = true },
28+
{ path = "auths_crypto::parse_ed25519_seed", reason = "use parse_key_material — returns TypedSeed.", allow-invalid = true },
29+
{ path = "auths_crypto::parse_ed25519_key_material", reason = "use parse_key_material — returns TypedSeed.", allow-invalid = true },
30+
{ path = "auths_core::crypto::provider_bridge::sign_ed25519_sync", reason = "use auths_crypto::sign(&TypedSeed, msg).", allow-invalid = true },
31+
{ path = "auths_core::crypto::provider_bridge::ed25519_public_key_from_seed_sync", reason = "use auths_crypto::public_key(&TypedSeed) or TypedSignerKey::public_key().", allow-invalid = true },
32+
{ path = "auths_crypto::did_key_to_ed25519", reason = "use did_key_decode — returns DecodedDidKey.", allow-invalid = true },
33+
{ path = "auths_id::identity::resolve::ed25519_to_did_key", reason = "use did_key_decode + DecodedDidKey::P256/Ed25519.", allow-invalid = true },
2134
]

crates/auths-cli/src/bin/sign.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
#![allow(clippy::print_stdout, clippy::print_stderr, clippy::exit)]
2+
// allow during curve-agnostic refactor
3+
#![allow(clippy::disallowed_methods)]
24
//! auths-sign: Git SSH signing program compatible with `gpg.ssh.program`
35
//!
46
//! Git calls this binary with ssh-keygen compatible arguments:

crates/auths-cli/src/bin/verify.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// allow during curve-agnostic refactor
2+
#![allow(clippy::disallowed_methods)]
13
#![allow(clippy::print_stdout, clippy::print_stderr, clippy::exit)]
24
//! auths-verify: SSH signature verification for Auths identities
35
//!

crates/auths-cli/src/commands/artifact/mod.rs

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -345,23 +345,25 @@ pub fn handle_artifact(
345345
None => bail!("--ci requires --commit <sha>. Pass the commit SHA explicitly."),
346346
};
347347

348-
let ci_env = match detect_ci_environment() {
349-
Some(env) => env,
350-
None => match ci_platform.as_deref() {
351-
Some("local") => CiEnvironment {
352-
platform: CiPlatform::Local,
353-
workflow_ref: None,
354-
run_id: None,
355-
actor: None,
356-
runner_os: None,
357-
},
358-
Some(name) => CiEnvironment {
359-
platform: CiPlatform::Generic,
360-
workflow_ref: None,
361-
run_id: None,
362-
actor: None,
363-
runner_os: Some(name.to_string()),
364-
},
348+
// Explicit --ci-platform takes precedence over auto-detection so
349+
// tests can opt out of the CI runner's auto-detected platform.
350+
let ci_env = match ci_platform.as_deref() {
351+
Some("local") => CiEnvironment {
352+
platform: CiPlatform::Local,
353+
workflow_ref: None,
354+
run_id: None,
355+
actor: None,
356+
runner_os: None,
357+
},
358+
Some(name) => CiEnvironment {
359+
platform: CiPlatform::Generic,
360+
workflow_ref: None,
361+
run_id: None,
362+
actor: None,
363+
runner_os: Some(name.to_string()),
364+
},
365+
None => match detect_ci_environment() {
366+
Some(env) => env,
365367
None => bail!(
366368
"No CI environment detected. If this is intentional (e.g., testing), \
367369
pass --ci-platform local. Otherwise run inside GitHub Actions, \

crates/auths-cli/src/commands/device/authorization.rs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -491,8 +491,35 @@ fn list_devices(
491491
.last()
492492
.expect("Grouped attestations should not be empty");
493493

494-
let verification_result =
495-
auths_sdk::attestation::verify_with_resolver(now, &resolver, latest, None);
494+
// single verifier path via auths_verifier::verify_with_keys.
495+
// Callers resolve the DID and pass the typed key directly.
496+
let verification_result: Result<(), auths_verifier::AttestationError> = {
497+
use auths_sdk::identity::DidResolver;
498+
use auths_verifier::AttestationError;
499+
match resolver.resolve(latest.issuer.as_str()) {
500+
Ok(resolved) => {
501+
let pk_bytes: Vec<u8> = resolved.public_key_bytes().to_vec();
502+
match auths_verifier::decode_public_key_bytes(&pk_bytes) {
503+
Ok(issuer_pk) => {
504+
#[allow(clippy::expect_used)]
505+
let rt = tokio::runtime::Builder::new_current_thread()
506+
.enable_all()
507+
.build()
508+
.expect("tokio runtime");
509+
rt.block_on(auths_verifier::verify_with_keys(latest, &issuer_pk))
510+
.map(|_| ())
511+
}
512+
Err(e) => Err(AttestationError::DidResolutionError(format!(
513+
"invalid issuer key: {e}"
514+
))),
515+
}
516+
}
517+
Err(e) => Err(AttestationError::DidResolutionError(format!(
518+
"Resolver error for {}: {}",
519+
latest.issuer, e
520+
))),
521+
}
522+
};
496523

497524
let status_string = match verification_result {
498525
Ok(()) => {

crates/auths-cli/src/commands/device/verify_attestation.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ fn resolve_issuer_key(
235235
let pin = PinnedIdentity {
236236
did: did.to_string(),
237237
public_key_hex: root.public_key_hex.clone(),
238+
curve: root.curve,
238239
kel_tip_said: root.kel_tip_said.clone(),
239240
kel_sequence: None,
240241
first_seen: now,

crates/auths-cli/src/commands/org.rs

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use serde_json;
99
use std::fs;
1010
use std::path::PathBuf;
1111

12-
use auths_sdk::attestation::{AttestationGroup, AttestationSink, verify_with_resolver};
12+
use auths_sdk::attestation::{AttestationGroup, AttestationSink};
1313
use auths_sdk::identity::DefaultDidResolver;
1414
use auths_sdk::keychain::{KeyAlias, get_platform_keychain};
1515
use auths_sdk::ports::{AttestationMetadata, AttestationSource, IdentityStorage};
@@ -194,6 +194,36 @@ pub enum OrgSubcommand {
194194
},
195195
}
196196

197+
/// single-verifier helper. Resolves the issuer DID,
198+
/// constructs a typed `DevicePublicKey`, and calls `auths_verifier::verify_with_keys`.
199+
/// Returns one of: "✅ valid", "🛑 revoked", "⌛ expired", "❌ invalid".
200+
fn verify_attestation_via_resolver(
201+
att: &auths_verifier::Attestation,
202+
resolver: &auths_sdk::identity::DefaultDidResolver,
203+
) -> &'static str {
204+
use auths_sdk::identity::DidResolver;
205+
let resolved = match resolver.resolve(att.issuer.as_str()) {
206+
Ok(r) => r,
207+
Err(_) => return "❌ invalid",
208+
};
209+
let pk_bytes: Vec<u8> = resolved.public_key_bytes().to_vec();
210+
let issuer_pk = match auths_verifier::decode_public_key_bytes(&pk_bytes) {
211+
Ok(pk) => pk,
212+
Err(_) => return "❌ invalid",
213+
};
214+
#[allow(clippy::expect_used)]
215+
let rt = tokio::runtime::Builder::new_current_thread()
216+
.enable_all()
217+
.build()
218+
.expect("tokio runtime");
219+
match rt.block_on(auths_verifier::verify_with_keys(att, &issuer_pk)) {
220+
Ok(_) => "✅ valid",
221+
Err(e) if e.to_string().contains("revoked") => "🛑 revoked",
222+
Err(e) if e.to_string().contains("expired") => "⌛ expired",
223+
Err(_) => "❌ invalid",
224+
}
225+
}
226+
197227
/// Handles `org` commands for issuing or revoking member authorizations.
198228
pub fn handle_org(
199229
cmd: OrgCommand,
@@ -589,12 +619,7 @@ pub fn handle_org(
589619
continue;
590620
}
591621

592-
let status = match verify_with_resolver(now, &resolver, att, None) {
593-
Ok(_) => "✅ valid",
594-
Err(e) if e.to_string().contains("revoked") => "🛑 revoked",
595-
Err(e) if e.to_string().contains("expired") => "⌛ expired",
596-
Err(_) => "❌ invalid",
597-
};
622+
let status = verify_attestation_via_resolver(att, &resolver);
598623

599624
println!("{i}. [{}] @ {}", status, att.timestamp.unwrap_or(now));
600625
if let Some(note) = &att.note {
@@ -626,12 +651,7 @@ pub fn handle_org(
626651
continue;
627652
}
628653

629-
let status = match verify_with_resolver(now, &resolver, latest, None) {
630-
Ok(_) => "✅ valid",
631-
Err(e) if e.to_string().contains("revoked") => "🛑 revoked",
632-
Err(e) if e.to_string().contains("expired") => "⌛ expired",
633-
Err(_) => "❌ invalid",
634-
};
654+
let status = verify_attestation_via_resolver(latest, &resolver);
635655

636656
println!("- {} [{}]", subject, status);
637657
}

crates/auths-cli/src/commands/trust.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,13 @@ fn handle_pin(cmd: TrustPinCommand, now: DateTime<Utc>) -> Result<()> {
195195

196196
let pin = PinnedIdentity {
197197
did: cmd.did.clone(),
198-
public_key_hex,
198+
public_key_hex: public_key_hex.clone(),
199+
curve: auths_crypto::CurveType::from_public_key_len(
200+
hex::decode(public_key_hex.as_str())
201+
.map(|b| b.len())
202+
.unwrap_or(0),
203+
)
204+
.unwrap_or(auths_crypto::CurveType::Ed25519),
199205
kel_tip_said: cmd.kel_tip,
200206
kel_sequence: None,
201207
first_seen: now,

0 commit comments

Comments
 (0)