Skip to content

Commit 48a5f7e

Browse files
committed
Fix kms signing
1 parent b39dceb commit 48a5f7e

File tree

5 files changed

+134
-18
lines changed

5 files changed

+134
-18
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ aws-config = { version = "1.8.0", features = ["behavior-version-latest"] }
1111
aws-sdk-kms = "1.76.0"
1212
borsh = "0.9.3"
1313
clap = { version = "4.5.39", features = ["derive", "env"] }
14+
der = { version = "0.7.10", features = ["oid", "derive"] }
1415
hex = { version = "0.4.3", features = ["serde"] }
1516
prost = "0.14.1"
1617
reqwest = { version = "0.12.19", features = ["json"] }

src/config.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use clap::Parser;
2-
use reqwest::Url;
32

43
#[derive(clap::ValueEnum, Clone, Debug)]
54
pub enum Mode {
@@ -26,7 +25,7 @@ pub struct RunOptions {
2625
/// URI for the signer.
2726
/// https://github.com/wormhole-foundation/wormhole/blob/main/docs/guardian_signer.md
2827
#[arg(long = "signer-uri", env = "SIGNER_URI")]
29-
pub signer_uri: Url,
28+
pub signer_uri: String,
3029
}
3130

3231
#[derive(Parser, Clone, Debug)]

src/main.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use {
88
clap::Parser,
99
posted_message::PostedMessageUnreliableData,
1010
prost::Message,
11+
reqwest::Url,
1112
secp256k1::{rand::rngs::OsRng, Secp256k1},
1213
sequoia_openpgp::armor::{Kind, Writer},
1314
serde_wormhole::RawMessage,
@@ -172,11 +173,14 @@ async fn run_listener(input: RunListenerInput) -> Result<(), PubsubClientError>
172173
}
173174

174175
async fn get_signer(run_options: config::RunOptions) -> anyhow::Result<Arc<dyn Signer>> {
175-
match run_options.signer_uri.scheme() {
176+
let scheme = run_options.signer_uri.split("://").next().unwrap_or("");
177+
println!("Using signer URI: {}", scheme);
178+
match scheme {
176179
"file" => {
177180
let signer = signer::FileSigner::try_new(
178181
run_options
179182
.signer_uri
183+
.parse::<Url>()?
180184
.to_file_path()
181185
.map_err(|_| anyhow::anyhow!("Invalid file path in signer URI"))?,
182186
run_options.mode,
@@ -187,9 +191,14 @@ async fn get_signer(run_options: config::RunOptions) -> anyhow::Result<Arc<dyn S
187191
let arn_string = run_options
188192
.signer_uri
189193
.as_str()
190-
.strip_prefix(&format!("{}://", run_options.signer_uri.scheme()))
194+
.strip_prefix("amazonkms://")
191195
.ok_or_else(|| anyhow::anyhow!("Invalid Amazon KMS ARN in signer URI"))?;
192-
let signer = signer::KMSSigner::try_new(arn_string.to_string()).await?;
196+
println!("Using Amazon KMS signer with ARN: {}", arn_string);
197+
let mut signer = signer::KMSSigner::try_new(arn_string.to_string()).await?;
198+
signer
199+
.get_and_cache_public_key()
200+
.await
201+
.map_err(|e| anyhow::anyhow!("Failed to get public key: {}", e))?;
193202
Ok(Arc::new(signer))
194203
}
195204
_ => Err(anyhow::anyhow!("Unsupported signer URI scheme")),

src/signer.rs

Lines changed: 92 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
use der::{
2+
asn1::{AnyRef, BitStringRef, UintRef},
3+
oid::ObjectIdentifier,
4+
Decode, Sequence,
5+
};
16
use std::{
27
fs,
38
io::{Cursor, Read},
@@ -7,7 +12,10 @@ use std::{
712

813
use async_trait::async_trait;
914
use prost::Message as ProstMessage;
10-
use secp256k1::{Message, PublicKey, Secp256k1, SecretKey};
15+
use secp256k1::{
16+
ecdsa::{RecoverableSignature, RecoveryId},
17+
Message, PublicKey, Secp256k1, SecretKey,
18+
};
1119
use sequoia_openpgp::armor::{Kind, Reader, ReaderMode};
1220
use sha3::{Digest, Keccak256};
1321

@@ -109,15 +117,53 @@ impl Signer for FileSigner {
109117
pub struct KMSSigner {
110118
client: aws_sdk_kms::Client,
111119
arn: aws_arn::ResourceName,
120+
public_key: Option<(PublicKey, [u8; 20])>,
112121
}
113122

114123
impl KMSSigner {
115124
pub async fn try_new(arn_string: String) -> anyhow::Result<Self> {
116125
let config = aws_config::load_from_env().await;
117126
let client = aws_sdk_kms::Client::new(&config);
118127
let arn = aws_arn::ResourceName::from_str(&arn_string)?;
119-
Ok(KMSSigner { client, arn })
128+
Ok(KMSSigner {
129+
client,
130+
arn,
131+
public_key: None,
132+
})
120133
}
134+
135+
pub async fn get_and_cache_public_key(&mut self) -> anyhow::Result<()> {
136+
let (public_key, pubkey_evm) = self.get_public_key().await?;
137+
self.public_key = Some((public_key, pubkey_evm));
138+
Ok(())
139+
}
140+
}
141+
142+
/// X.509 `AlgorithmIdentifier` (same as above)
143+
#[derive(Copy, Clone, Debug, Eq, PartialEq, Sequence)] // NOTE: added `Sequence`
144+
pub struct AlgorithmIdentifier<'a> {
145+
/// This field contains an ASN.1 `OBJECT IDENTIFIER`, a.k.a. OID.
146+
pub algorithm: ObjectIdentifier,
147+
148+
/// This field is `OPTIONAL` and contains the ASN.1 `ANY` type, which
149+
/// in this example allows arbitrary algorithm-defined parameters.
150+
pub parameters: Option<AnyRef<'a>>,
151+
}
152+
153+
/// X.509 `SubjectPublicKeyInfo` (SPKI)
154+
#[derive(Copy, Clone, Debug, Eq, PartialEq, Sequence)]
155+
pub struct SubjectPublicKeyInfo<'a> {
156+
/// X.509 `AlgorithmIdentifier`
157+
pub algorithm: AlgorithmIdentifier<'a>,
158+
159+
/// Public key data
160+
pub subject_public_key: BitStringRef<'a>,
161+
}
162+
163+
#[derive(Sequence)]
164+
struct EcdsaSignature<'a> {
165+
r: UintRef<'a>,
166+
s: UintRef<'a>,
121167
}
122168

123169
#[async_trait]
@@ -128,23 +174,50 @@ impl Signer for KMSSigner {
128174
.sign()
129175
.key_id(self.arn.to_string())
130176
.message(data.to_vec().into())
177+
.message_type(aws_sdk_kms::types::MessageType::Digest)
131178
.signing_algorithm(aws_sdk_kms::types::SigningAlgorithmSpec::EcdsaSha256)
132179
.send()
133180
.await
134181
.map_err(|e| anyhow::anyhow!("Failed to sign data with KMS: {}", e))?;
135-
result
182+
let kms_signature = result
136183
.signature
137-
.ok_or_else(|| anyhow::anyhow!("KMS did not return a signature"))
138-
.and_then(|sig| {
139-
let sig = sig.into_inner();
140-
let signature: [u8; 65] = sig
141-
.try_into()
142-
.map_err(|e| anyhow::anyhow!("Failed to convert KMS signature: {:?}", e))?;
143-
Ok(signature)
144-
})
184+
.ok_or_else(|| anyhow::anyhow!("KMS did not return a signature"))?;
185+
186+
let decoded_signature = EcdsaSignature::from_der(kms_signature.as_ref())
187+
.map_err(|e| anyhow::anyhow!("Failed to decode SubjectPublicKeyInfo: {}", e))?;
188+
189+
let r_bytes = decoded_signature.r.as_bytes();
190+
let s_bytes = decoded_signature.s.as_bytes();
191+
let mut signature = [0u8; 65];
192+
signature[(32 - r_bytes.len())..32].copy_from_slice(r_bytes);
193+
signature[(64 - s_bytes.len())..64].copy_from_slice(decoded_signature.s.as_bytes());
194+
195+
let public_key = self.get_public_key().await?;
196+
for raw_id in 0..4 {
197+
let secp = Secp256k1::new();
198+
let recid = RecoveryId::try_from(raw_id)
199+
.map_err(|e| anyhow::anyhow!("Failed to create RecoveryId: {}", e))?;
200+
if let Ok(recovered_public_key) = secp.recover_ecdsa(
201+
&Message::from_digest(data),
202+
&RecoverableSignature::from_compact(&signature[..64], recid)
203+
.map_err(|e| anyhow::anyhow!("Failed to create RecoverableSignature: {}", e))?,
204+
) {
205+
if recovered_public_key == public_key.0 {
206+
signature[64] = raw_id as u8;
207+
return Ok(signature);
208+
}
209+
}
210+
}
211+
Err(anyhow::anyhow!(
212+
"Failed to recover public key from signature"
213+
))
145214
}
146215

147216
async fn get_public_key(&self) -> anyhow::Result<(PublicKey, [u8; 20])> {
217+
if let Some((public_key, pubkey_evm)) = &self.public_key {
218+
return Ok((*public_key, *pubkey_evm));
219+
}
220+
148221
let result = self
149222
.client
150223
.get_public_key()
@@ -155,9 +228,15 @@ impl Signer for KMSSigner {
155228
let public_key = result
156229
.public_key
157230
.ok_or(anyhow::anyhow!("KMS did not return a public key"))?;
158-
let public_key = PublicKey::from_slice(public_key.as_ref())
159-
.map_err(|e| anyhow::anyhow!("Failed to create PublicKey from KMS: {}", e))?;
231+
let decoded_algorithm_identifier = SubjectPublicKeyInfo::from_der(public_key.as_ref())
232+
.map_err(|e| {
233+
anyhow::anyhow!("Failed to decode SubjectPublicKeyInfo from KMS: {}", e)
234+
})?;
235+
let public_key =
236+
PublicKey::from_slice(decoded_algorithm_identifier.subject_public_key.raw_bytes())
237+
.map_err(|e| anyhow::anyhow!("Failed to create PublicKey from KMS: {}", e))?;
160238
let pubkey_evm = get_evm_public_key(&public_key)?;
239+
161240
Ok((public_key, pubkey_evm))
162241
}
163242
}

0 commit comments

Comments
 (0)