Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .envrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use flake
use flake .

if [[ -f .env ]]; then
dotenv .env
Expand Down
2 changes: 0 additions & 2 deletions Cargo.lock

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

3 changes: 1 addition & 2 deletions integrity-lineage-models/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ serde_jcs = "0.1.0"
serde_json = "1.0"
ssi = { version = "0.7", features = ["w3c", "http-did", "example-http-issuer", "ed25519", "secp256r1"] }
ssi-json-ld = "0.2"
utoipa = { version = "3", features = ["axum_extras", "openapi_extensions", "uuid"] }
uuid = { version = "1.4", features = ["v4", "serde"] }
utoipa = { version = "3", features = ["axum_extras", "openapi_extensions"] }

[target.'cfg(target_arch = "wasm32")'.dependencies]
blake3 = "1.5"
Expand Down
2 changes: 2 additions & 0 deletions integrity-signer/src/signer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ pub mod akv_signer;
pub mod auth_service_signer;
#[cfg(feature = "signer-ed25519")]
pub mod ed25519_signer;
#[cfg(any(feature = "signer-p256", feature = "signer-vcomp-notary"))]
pub(crate) mod p256_jwk;
#[cfg(feature = "signer-p256")]
pub mod p256_signer;
#[cfg(feature = "signer-secp256k1")]
Expand Down
58 changes: 58 additions & 0 deletions integrity-signer/src/signer/p256_jwk.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use anyhow::{anyhow, Result};
use base64::engine::{general_purpose::URL_SAFE_NO_PAD as BASE64_URL_NO_PAD, Engine};
use did_key::{Document, KeyFormat};
use p256::{
ecdsa::{SigningKey, VerifyingKey},
EncodedPoint,
};
#[cfg(feature = "signer-vcomp-notary")]
use p256::{elliptic_curve::sec1::ToEncodedPoint, PublicKey};

pub(crate) fn p256_encoded_point_from_secret_key(secret_key: &[u8]) -> Result<EncodedPoint> {
let signing_key =
SigningKey::from_bytes(secret_key.into()).map_err(|e| anyhow!("Invalid P-256 key: {e}"))?;
let verifying_key = VerifyingKey::from(&signing_key);

Ok(verifying_key.to_encoded_point(false))
}

#[cfg(feature = "signer-vcomp-notary")]
pub(crate) fn p256_encoded_point_from_public_key(public_key: &[u8]) -> Result<EncodedPoint> {
let public_key = PublicKey::from_sec1_bytes(public_key)
.map_err(|e| anyhow!("Invalid P-256 public key: {e}"))?;

Ok(public_key.to_encoded_point(false))
}

pub(crate) fn fix_p256_jwk_from_encoded_point(
did_doc: &mut Document,
encoded_point: &EncodedPoint,
secret_key: Option<&[u8]>,
) -> Result<()> {
let x_bytes = encoded_point
.x()
.ok_or_else(|| anyhow!("Failed to get x coordinate"))?;
let y_bytes = encoded_point
.y()
.ok_or_else(|| anyhow!("Failed to get y coordinate"))?;

let x_b64 = BASE64_URL_NO_PAD.encode(x_bytes);
let y_b64 = BASE64_URL_NO_PAD.encode(y_bytes);
let d_b64 = secret_key.map(|secret_key| BASE64_URL_NO_PAD.encode(secret_key));

for vm in &mut did_doc.verification_method {
if let Some(KeyFormat::JWK(ref mut jwk)) = vm.public_key {
jwk.x = Some(x_b64.clone());
jwk.y = Some(y_b64.clone());
}
if let Some(KeyFormat::JWK(ref mut jwk)) = vm.private_key {
jwk.x = Some(x_b64.clone());
jwk.y = Some(y_b64.clone());
if let Some(ref d_b64) = d_b64 {
jwk.d = Some(d_b64.clone());
}
}
}

Ok(())
}
43 changes: 7 additions & 36 deletions integrity-signer/src/signer/p256_signer.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use base64::engine::{general_purpose::URL_SAFE_NO_PAD as BASE64_URL_NO_PAD, Engine};
use did_key::{CoreSign, DIDCore, Document, Generate, KeyFormat, KeyMaterial, P256KeyPair};
use p256::ecdsa::{SigningKey, VerifyingKey};
use did_key::{CoreSign, DIDCore, Document, Generate, KeyMaterial, P256KeyPair};
use serde::{Deserialize, Serialize};

use crate::signer::Signer;
use crate::signer::{
p256_jwk::{fix_p256_jwk_from_encoded_point, p256_encoded_point_from_secret_key},
Signer,
};

/// Signer implementation using P-256 (secp256r1) elliptic curve.
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
Expand Down Expand Up @@ -67,38 +68,8 @@ impl P256Signer {
/// and omits the y field. This function extracts the correct x and y coordinates from
/// the uncompressed public key and updates the JWK.
fn fix_p256_jwk(did_doc: &mut Document, secret_key: &[u8]) -> Result<()> {
// Derive the public key from the secret key to get uncompressed coordinates
let signing_key =
SigningKey::from_bytes(secret_key.into()).map_err(|e| anyhow!("Invalid P-256 key: {e}"))?;
let verifying_key = VerifyingKey::from(&signing_key);

// Get the uncompressed public key point (65 bytes: 04 prefix + 32 bytes x + 32 bytes y)
let encoded_point = verifying_key.to_encoded_point(false);
let x_bytes = encoded_point
.x()
.ok_or_else(|| anyhow!("Failed to get x coordinate"))?;
let y_bytes = encoded_point
.y()
.ok_or_else(|| anyhow!("Failed to get y coordinate"))?;

let x_b64 = BASE64_URL_NO_PAD.encode(x_bytes);
let y_b64 = BASE64_URL_NO_PAD.encode(y_bytes);
let d_b64 = BASE64_URL_NO_PAD.encode(secret_key);

// Update the verification method's JWK
for vm in &mut did_doc.verification_method {
if let Some(KeyFormat::JWK(ref mut jwk)) = vm.public_key {
jwk.x = Some(x_b64.clone());
jwk.y = Some(y_b64.clone());
}
if let Some(KeyFormat::JWK(ref mut jwk)) = vm.private_key {
jwk.x = Some(x_b64.clone());
jwk.y = Some(y_b64.clone());
jwk.d = Some(d_b64.clone());
}
}

Ok(())
let encoded_point = p256_encoded_point_from_secret_key(secret_key)?;
fix_p256_jwk_from_encoded_point(did_doc, &encoded_point, Some(secret_key))
}

#[async_trait]
Expand Down
68 changes: 61 additions & 7 deletions integrity-signer/src/signer/vcomp_notary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use tokio::net::TcpStream;

use crate::signer::Signer;
use crate::signer::{
p256_jwk::{fix_p256_jwk_from_encoded_point, p256_encoded_point_from_public_key},
Signer,
};

/// Signer implementation for verified computing notary services.
#[derive(Clone, Debug, Serialize, Deserialize)]
Expand All @@ -38,6 +41,18 @@ fn strip_urn_cid(cid: &str) -> &str {
}

impl VCompNotarySigner {
fn did_doc_from_public_key(pub_key: &[u8]) -> Result<Document> {
let key_pair = P256KeyPair::from_public_key(pub_key);
let mut did_doc = key_pair.get_did_document(did_key::Config {
use_jose_format: true,
serialize_secrets: true,
});
let encoded_point = p256_encoded_point_from_public_key(pub_key)?;
fix_p256_jwk_from_encoded_point(&mut did_doc, &encoded_point, None)?;

Ok(did_doc)
}

/// Creates a new VCompNotarySigner by connecting to a verified computing notary service.
///
/// # Arguments
Expand Down Expand Up @@ -69,12 +84,7 @@ impl VCompNotarySigner {
let pub_key = hex::decode(pub_key)?;

log::trace!("Importing a secp256r1 VComp Notary signer");
let key_pair = P256KeyPair::from_public_key(&pub_key);

let did_doc = key_pair.get_did_document(did_key::Config {
use_jose_format: true,
serialize_secrets: true,
});
let did_doc = Self::did_doc_from_public_key(&pub_key)?;

let response = client
.get(format!("{url}/get_dids"))
Expand Down Expand Up @@ -232,3 +242,47 @@ impl Signer for VCompNotarySigner {
Ok(Some(self.did_doc.clone()))
}
}

#[cfg(test)]
mod tests {
use base64::engine::{general_purpose::URL_SAFE_NO_PAD as BASE64_URL_NO_PAD, Engine};
use did_key::KeyFormat;
use p256::ecdsa::SigningKey;

use super::*;

#[test]
fn compressed_vcomp_public_key_repairs_verification_method_jwk() {
let signing_key = SigningKey::from_bytes((&[7u8; 32]).into()).unwrap();
let verifying_key = signing_key.verifying_key();
let compressed_pub_key = verifying_key.to_encoded_point(true);
let uncompressed_pub_key = verifying_key.to_encoded_point(false);

let key_pair = P256KeyPair::from_public_key(compressed_pub_key.as_bytes());
let broken_did_doc = key_pair.get_did_document(did_key::Config {
use_jose_format: true,
serialize_secrets: true,
});

let fixed_did_doc =
VCompNotarySigner::did_doc_from_public_key(compressed_pub_key.as_bytes()).unwrap();

let expected_x = BASE64_URL_NO_PAD.encode(uncompressed_pub_key.x().unwrap());
let expected_y = BASE64_URL_NO_PAD.encode(uncompressed_pub_key.y().unwrap());
let compressed_b64 = BASE64_URL_NO_PAD.encode(compressed_pub_key.as_bytes());

let broken_jwk = match &broken_did_doc.verification_method[0].public_key {
Some(KeyFormat::JWK(jwk)) => jwk,
_ => panic!("expected JWK verification method"),
};
assert_eq!(broken_jwk.x.as_deref(), Some(compressed_b64.as_str()));
assert_eq!(broken_jwk.y, None);

let fixed_jwk = match &fixed_did_doc.verification_method[0].public_key {
Some(KeyFormat::JWK(jwk)) => jwk,
_ => panic!("expected JWK verification method"),
};
assert_eq!(fixed_jwk.x.as_deref(), Some(expected_x.as_str()));
assert_eq!(fixed_jwk.y.as_deref(), Some(expected_y.as_str()));
}
}
Loading