Skip to content

Commit be3ac1a

Browse files
authored
Dev 1533 error key mismatch (#13)
Apply p256k key decoding fix to both vcomp and p256 signers
1 parent 53dff77 commit be3ac1a

7 files changed

Lines changed: 130 additions & 48 deletions

File tree

.envrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use flake
1+
use flake .
22

33
if [[ -f .env ]]; then
44
dotenv .env

Cargo.lock

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

integrity-lineage-models/Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,7 @@ serde_jcs = "0.1.0"
2323
serde_json = "1.0"
2424
ssi = { version = "0.7", features = ["w3c", "http-did", "example-http-issuer", "ed25519", "secp256r1"] }
2525
ssi-json-ld = "0.2"
26-
utoipa = { version = "3", features = ["axum_extras", "openapi_extensions", "uuid"] }
27-
uuid = { version = "1.4", features = ["v4", "serde"] }
26+
utoipa = { version = "3", features = ["axum_extras", "openapi_extensions"] }
2827

2928
[target.'cfg(target_arch = "wasm32")'.dependencies]
3029
blake3 = "1.5"

integrity-signer/src/signer/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ pub mod akv_signer;
1212
pub mod auth_service_signer;
1313
#[cfg(feature = "signer-ed25519")]
1414
pub mod ed25519_signer;
15+
#[cfg(any(feature = "signer-p256", feature = "signer-vcomp-notary"))]
16+
pub(crate) mod p256_jwk;
1517
#[cfg(feature = "signer-p256")]
1618
pub mod p256_signer;
1719
#[cfg(feature = "signer-secp256k1")]
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
use anyhow::{anyhow, Result};
2+
use base64::engine::{general_purpose::URL_SAFE_NO_PAD as BASE64_URL_NO_PAD, Engine};
3+
use did_key::{Document, KeyFormat};
4+
use p256::{
5+
ecdsa::{SigningKey, VerifyingKey},
6+
EncodedPoint,
7+
};
8+
#[cfg(feature = "signer-vcomp-notary")]
9+
use p256::{elliptic_curve::sec1::ToEncodedPoint, PublicKey};
10+
11+
pub(crate) fn p256_encoded_point_from_secret_key(secret_key: &[u8]) -> Result<EncodedPoint> {
12+
let signing_key =
13+
SigningKey::from_bytes(secret_key.into()).map_err(|e| anyhow!("Invalid P-256 key: {e}"))?;
14+
let verifying_key = VerifyingKey::from(&signing_key);
15+
16+
Ok(verifying_key.to_encoded_point(false))
17+
}
18+
19+
#[cfg(feature = "signer-vcomp-notary")]
20+
pub(crate) fn p256_encoded_point_from_public_key(public_key: &[u8]) -> Result<EncodedPoint> {
21+
let public_key = PublicKey::from_sec1_bytes(public_key)
22+
.map_err(|e| anyhow!("Invalid P-256 public key: {e}"))?;
23+
24+
Ok(public_key.to_encoded_point(false))
25+
}
26+
27+
pub(crate) fn fix_p256_jwk_from_encoded_point(
28+
did_doc: &mut Document,
29+
encoded_point: &EncodedPoint,
30+
secret_key: Option<&[u8]>,
31+
) -> Result<()> {
32+
let x_bytes = encoded_point
33+
.x()
34+
.ok_or_else(|| anyhow!("Failed to get x coordinate"))?;
35+
let y_bytes = encoded_point
36+
.y()
37+
.ok_or_else(|| anyhow!("Failed to get y coordinate"))?;
38+
39+
let x_b64 = BASE64_URL_NO_PAD.encode(x_bytes);
40+
let y_b64 = BASE64_URL_NO_PAD.encode(y_bytes);
41+
let d_b64 = secret_key.map(|secret_key| BASE64_URL_NO_PAD.encode(secret_key));
42+
43+
for vm in &mut did_doc.verification_method {
44+
if let Some(KeyFormat::JWK(ref mut jwk)) = vm.public_key {
45+
jwk.x = Some(x_b64.clone());
46+
jwk.y = Some(y_b64.clone());
47+
}
48+
if let Some(KeyFormat::JWK(ref mut jwk)) = vm.private_key {
49+
jwk.x = Some(x_b64.clone());
50+
jwk.y = Some(y_b64.clone());
51+
if let Some(ref d_b64) = d_b64 {
52+
jwk.d = Some(d_b64.clone());
53+
}
54+
}
55+
}
56+
57+
Ok(())
58+
}

integrity-signer/src/signer/p256_signer.rs

Lines changed: 7 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
use anyhow::{anyhow, Result};
22
use async_trait::async_trait;
3-
use base64::engine::{general_purpose::URL_SAFE_NO_PAD as BASE64_URL_NO_PAD, Engine};
4-
use did_key::{CoreSign, DIDCore, Document, Generate, KeyFormat, KeyMaterial, P256KeyPair};
5-
use p256::ecdsa::{SigningKey, VerifyingKey};
3+
use did_key::{CoreSign, DIDCore, Document, Generate, KeyMaterial, P256KeyPair};
64
use serde::{Deserialize, Serialize};
75

8-
use crate::signer::Signer;
6+
use crate::signer::{
7+
p256_jwk::{fix_p256_jwk_from_encoded_point, p256_encoded_point_from_secret_key},
8+
Signer,
9+
};
910

1011
/// Signer implementation using P-256 (secp256r1) elliptic curve.
1112
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
@@ -67,38 +68,8 @@ impl P256Signer {
6768
/// and omits the y field. This function extracts the correct x and y coordinates from
6869
/// the uncompressed public key and updates the JWK.
6970
fn fix_p256_jwk(did_doc: &mut Document, secret_key: &[u8]) -> Result<()> {
70-
// Derive the public key from the secret key to get uncompressed coordinates
71-
let signing_key =
72-
SigningKey::from_bytes(secret_key.into()).map_err(|e| anyhow!("Invalid P-256 key: {e}"))?;
73-
let verifying_key = VerifyingKey::from(&signing_key);
74-
75-
// Get the uncompressed public key point (65 bytes: 04 prefix + 32 bytes x + 32 bytes y)
76-
let encoded_point = verifying_key.to_encoded_point(false);
77-
let x_bytes = encoded_point
78-
.x()
79-
.ok_or_else(|| anyhow!("Failed to get x coordinate"))?;
80-
let y_bytes = encoded_point
81-
.y()
82-
.ok_or_else(|| anyhow!("Failed to get y coordinate"))?;
83-
84-
let x_b64 = BASE64_URL_NO_PAD.encode(x_bytes);
85-
let y_b64 = BASE64_URL_NO_PAD.encode(y_bytes);
86-
let d_b64 = BASE64_URL_NO_PAD.encode(secret_key);
87-
88-
// Update the verification method's JWK
89-
for vm in &mut did_doc.verification_method {
90-
if let Some(KeyFormat::JWK(ref mut jwk)) = vm.public_key {
91-
jwk.x = Some(x_b64.clone());
92-
jwk.y = Some(y_b64.clone());
93-
}
94-
if let Some(KeyFormat::JWK(ref mut jwk)) = vm.private_key {
95-
jwk.x = Some(x_b64.clone());
96-
jwk.y = Some(y_b64.clone());
97-
jwk.d = Some(d_b64.clone());
98-
}
99-
}
100-
101-
Ok(())
71+
let encoded_point = p256_encoded_point_from_secret_key(secret_key)?;
72+
fix_p256_jwk_from_encoded_point(did_doc, &encoded_point, Some(secret_key))
10273
}
10374

10475
#[async_trait]

integrity-signer/src/signer/vcomp_notary.rs

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ use serde::{Deserialize, Serialize};
1212
use serde_json::{json, Value};
1313
use tokio::net::TcpStream;
1414

15-
use crate::signer::Signer;
15+
use crate::signer::{
16+
p256_jwk::{fix_p256_jwk_from_encoded_point, p256_encoded_point_from_public_key},
17+
Signer,
18+
};
1619

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

4043
impl VCompNotarySigner {
44+
fn did_doc_from_public_key(pub_key: &[u8]) -> Result<Document> {
45+
let key_pair = P256KeyPair::from_public_key(pub_key);
46+
let mut did_doc = key_pair.get_did_document(did_key::Config {
47+
use_jose_format: true,
48+
serialize_secrets: true,
49+
});
50+
let encoded_point = p256_encoded_point_from_public_key(pub_key)?;
51+
fix_p256_jwk_from_encoded_point(&mut did_doc, &encoded_point, None)?;
52+
53+
Ok(did_doc)
54+
}
55+
4156
/// Creates a new VCompNotarySigner by connecting to a verified computing notary service.
4257
///
4358
/// # Arguments
@@ -69,12 +84,7 @@ impl VCompNotarySigner {
6984
let pub_key = hex::decode(pub_key)?;
7085

7186
log::trace!("Importing a secp256r1 VComp Notary signer");
72-
let key_pair = P256KeyPair::from_public_key(&pub_key);
73-
74-
let did_doc = key_pair.get_did_document(did_key::Config {
75-
use_jose_format: true,
76-
serialize_secrets: true,
77-
});
87+
let did_doc = Self::did_doc_from_public_key(&pub_key)?;
7888

7989
let response = client
8090
.get(format!("{url}/get_dids"))
@@ -232,3 +242,47 @@ impl Signer for VCompNotarySigner {
232242
Ok(Some(self.did_doc.clone()))
233243
}
234244
}
245+
246+
#[cfg(test)]
247+
mod tests {
248+
use base64::engine::{general_purpose::URL_SAFE_NO_PAD as BASE64_URL_NO_PAD, Engine};
249+
use did_key::KeyFormat;
250+
use p256::ecdsa::SigningKey;
251+
252+
use super::*;
253+
254+
#[test]
255+
fn compressed_vcomp_public_key_repairs_verification_method_jwk() {
256+
let signing_key = SigningKey::from_bytes((&[7u8; 32]).into()).unwrap();
257+
let verifying_key = signing_key.verifying_key();
258+
let compressed_pub_key = verifying_key.to_encoded_point(true);
259+
let uncompressed_pub_key = verifying_key.to_encoded_point(false);
260+
261+
let key_pair = P256KeyPair::from_public_key(compressed_pub_key.as_bytes());
262+
let broken_did_doc = key_pair.get_did_document(did_key::Config {
263+
use_jose_format: true,
264+
serialize_secrets: true,
265+
});
266+
267+
let fixed_did_doc =
268+
VCompNotarySigner::did_doc_from_public_key(compressed_pub_key.as_bytes()).unwrap();
269+
270+
let expected_x = BASE64_URL_NO_PAD.encode(uncompressed_pub_key.x().unwrap());
271+
let expected_y = BASE64_URL_NO_PAD.encode(uncompressed_pub_key.y().unwrap());
272+
let compressed_b64 = BASE64_URL_NO_PAD.encode(compressed_pub_key.as_bytes());
273+
274+
let broken_jwk = match &broken_did_doc.verification_method[0].public_key {
275+
Some(KeyFormat::JWK(jwk)) => jwk,
276+
_ => panic!("expected JWK verification method"),
277+
};
278+
assert_eq!(broken_jwk.x.as_deref(), Some(compressed_b64.as_str()));
279+
assert_eq!(broken_jwk.y, None);
280+
281+
let fixed_jwk = match &fixed_did_doc.verification_method[0].public_key {
282+
Some(KeyFormat::JWK(jwk)) => jwk,
283+
_ => panic!("expected JWK verification method"),
284+
};
285+
assert_eq!(fixed_jwk.x.as_deref(), Some(expected_x.as_str()));
286+
assert_eq!(fixed_jwk.y.as_deref(), Some(expected_y.as_str()));
287+
}
288+
}

0 commit comments

Comments
 (0)