ethers_signers/aws/
mod.rs

1//! AWS KMS-based Signer
2
3use ethers_core::{
4    k256::ecdsa::{Error as K256Error, Signature as KSig, VerifyingKey},
5    types::{
6        transaction::{eip2718::TypedTransaction, eip712::Eip712},
7        Address, Signature as EthSig, H256,
8    },
9    utils::hash_message,
10};
11use rusoto_core::RusotoError;
12use rusoto_kms::{
13    GetPublicKeyError, GetPublicKeyRequest, Kms, KmsClient, SignError, SignRequest, SignResponse,
14};
15use tracing::{debug, instrument, trace};
16
17mod utils;
18use utils::{apply_eip155, verifying_key_to_address};
19
20/// An ethers Signer that uses keys held in Amazon AWS KMS.
21///
22/// The AWS Signer passes signing requests to the cloud service. AWS KMS keys
23/// are identified by a UUID, the `key_id`.
24///
25/// Because the public key is unknown, we retrieve it on instantiation of the
26/// signer. This means that the new function is `async` and must be called
27/// within some runtime.
28///
29/// ```compile_fail
30/// use rusoto_core::Client;
31/// use rusoto_kms::{Kms, KmsClient};
32///
33/// user ethers_signers::Signer;
34///
35/// let client = Client::new_with(
36///     EnvironmentProvider::default(),
37///     HttpClient::new().unwrap()
38/// );
39/// let kms_client = KmsClient::new_with_client(client, Region::UsWest1);
40/// let key_id = "...";
41/// let chain_id = 1;
42///
43/// let signer = AwsSigner::new(kms_client, key_id, chain_id).await?;
44/// let sig = signer.sign_message(H256::zero()).await?;
45/// ```
46#[derive(Clone)]
47pub struct AwsSigner {
48    kms: KmsClient,
49    chain_id: u64,
50    key_id: String,
51    pubkey: VerifyingKey,
52    address: Address,
53}
54
55impl std::fmt::Debug for AwsSigner {
56    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57        f.debug_struct("AwsSigner")
58            .field("key_id", &self.key_id)
59            .field("chain_id", &self.chain_id)
60            .field("pubkey", &hex::encode(self.pubkey.to_sec1_bytes()))
61            .field("address", &self.address)
62            .finish()
63    }
64}
65
66impl std::fmt::Display for AwsSigner {
67    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68        write!(
69            f,
70            "AwsSigner {{ address: {}, chain_id: {}, key_id: {} }}",
71            self.address, self.chain_id, self.key_id
72        )
73    }
74}
75
76/// Errors produced by the AwsSigner
77#[derive(thiserror::Error, Debug)]
78pub enum AwsSignerError {
79    #[error("{0}")]
80    SignError(#[from] RusotoError<SignError>),
81    #[error("{0}")]
82    GetPublicKeyError(#[from] RusotoError<GetPublicKeyError>),
83    #[error("{0}")]
84    K256(#[from] K256Error),
85    #[error("{0}")]
86    Spki(spki::Error),
87    #[error("{0}")]
88    Other(String),
89    #[error(transparent)]
90    /// Error when converting from a hex string
91    HexError(#[from] hex::FromHexError),
92    /// Error type from Eip712Error message
93    #[error("error encoding eip712 struct: {0:?}")]
94    Eip712Error(String),
95}
96
97impl From<String> for AwsSignerError {
98    fn from(s: String) -> Self {
99        Self::Other(s)
100    }
101}
102
103impl From<spki::Error> for AwsSignerError {
104    fn from(e: spki::Error) -> Self {
105        Self::Spki(e)
106    }
107}
108
109#[instrument(err, skip(kms, key_id), fields(key_id = %key_id.as_ref()))]
110async fn request_get_pubkey<T>(
111    kms: &KmsClient,
112    key_id: T,
113) -> Result<rusoto_kms::GetPublicKeyResponse, RusotoError<GetPublicKeyError>>
114where
115    T: AsRef<str>,
116{
117    debug!("Dispatching get_public_key");
118
119    let req = GetPublicKeyRequest { grant_tokens: None, key_id: key_id.as_ref().to_owned() };
120    trace!("{:?}", &req);
121    let resp = kms.get_public_key(req).await;
122    trace!("{:?}", &resp);
123    resp
124}
125
126#[instrument(err, skip(kms, digest, key_id), fields(digest = %hex::encode(digest), key_id = %key_id.as_ref()))]
127async fn request_sign_digest<T>(
128    kms: &KmsClient,
129    key_id: T,
130    digest: [u8; 32],
131) -> Result<SignResponse, RusotoError<SignError>>
132where
133    T: AsRef<str>,
134{
135    debug!("Dispatching sign");
136    let req = SignRequest {
137        grant_tokens: None,
138        key_id: key_id.as_ref().to_owned(),
139        message: digest.to_vec().into(),
140        message_type: Some("DIGEST".to_owned()),
141        signing_algorithm: "ECDSA_SHA_256".to_owned(),
142    };
143    trace!("{:?}", &req);
144    let resp = kms.sign(req).await;
145    trace!("{:?}", &resp);
146    resp
147}
148
149impl AwsSigner {
150    /// Instantiate a new signer from an existing `KmsClient` and Key ID.
151    ///
152    /// This function retrieves the public key from AWS and calculates the
153    /// Etheruem address. It is therefore `async`.
154    #[instrument(err, skip(kms, key_id, chain_id), fields(key_id = %key_id.as_ref()))]
155    pub async fn new<T>(
156        kms: KmsClient,
157        key_id: T,
158        chain_id: u64,
159    ) -> Result<AwsSigner, AwsSignerError>
160    where
161        T: AsRef<str>,
162    {
163        let pubkey = request_get_pubkey(&kms, &key_id).await.map(utils::decode_pubkey)??;
164        let address = verifying_key_to_address(&pubkey);
165
166        debug!(
167            "Instantiated AWS signer with pubkey 0x{} and address 0x{}",
168            hex::encode(pubkey.to_sec1_bytes()),
169            hex::encode(address)
170        );
171
172        Ok(Self { kms, chain_id, key_id: key_id.as_ref().to_owned(), pubkey, address })
173    }
174
175    /// Fetch the pubkey associated with a key id
176    pub async fn get_pubkey_for_key<T>(&self, key_id: T) -> Result<VerifyingKey, AwsSignerError>
177    where
178        T: AsRef<str>,
179    {
180        request_get_pubkey(&self.kms, key_id).await.map(utils::decode_pubkey)?
181    }
182
183    /// Fetch the pubkey associated with this signer's key ID
184    pub async fn get_pubkey(&self) -> Result<VerifyingKey, AwsSignerError> {
185        self.get_pubkey_for_key(&self.key_id).await
186    }
187
188    /// Sign a digest with the key associated with a key id
189    pub async fn sign_digest_with_key<T>(
190        &self,
191        key_id: T,
192        digest: [u8; 32],
193    ) -> Result<KSig, AwsSignerError>
194    where
195        T: AsRef<str>,
196    {
197        request_sign_digest(&self.kms, key_id, digest).await.map(utils::decode_signature)?
198    }
199
200    /// Sign a digest with this signer's key
201    pub async fn sign_digest(&self, digest: [u8; 32]) -> Result<KSig, AwsSignerError> {
202        self.sign_digest_with_key(self.key_id.clone(), digest).await
203    }
204
205    /// Sign a digest with this signer's key and add the eip155 `v` value
206    /// corresponding to the input chain_id
207    #[instrument(err, skip(digest), fields(digest = %hex::encode(digest)))]
208    async fn sign_digest_with_eip155(
209        &self,
210        digest: H256,
211        chain_id: u64,
212    ) -> Result<EthSig, AwsSignerError> {
213        let sig = self.sign_digest(digest.into()).await?;
214        let mut sig =
215            utils::sig_from_digest_bytes_trial_recovery(&sig, digest.into(), &self.pubkey);
216        apply_eip155(&mut sig, chain_id);
217        Ok(sig)
218    }
219}
220
221#[async_trait::async_trait]
222impl super::Signer for AwsSigner {
223    type Error = AwsSignerError;
224
225    #[instrument(err, skip(message))]
226    #[allow(clippy::blocks_in_conditions)]
227    async fn sign_message<S: Send + Sync + AsRef<[u8]>>(
228        &self,
229        message: S,
230    ) -> Result<EthSig, Self::Error> {
231        let message = message.as_ref();
232        let message_hash = hash_message(message);
233        trace!("{:?}", message_hash);
234        trace!("{:?}", message);
235
236        self.sign_digest_with_eip155(message_hash, self.chain_id).await
237    }
238
239    #[instrument(err)]
240    #[allow(clippy::blocks_in_conditions)]
241    async fn sign_transaction(&self, tx: &TypedTransaction) -> Result<EthSig, Self::Error> {
242        let mut tx_with_chain = tx.clone();
243        let chain_id = tx_with_chain.chain_id().map(|id| id.as_u64()).unwrap_or(self.chain_id);
244        tx_with_chain.set_chain_id(chain_id);
245
246        let sighash = tx_with_chain.sighash();
247        self.sign_digest_with_eip155(sighash, chain_id).await
248    }
249
250    async fn sign_typed_data<T: Eip712 + Send + Sync>(
251        &self,
252        payload: &T,
253    ) -> Result<EthSig, Self::Error> {
254        let digest =
255            payload.encode_eip712().map_err(|e| Self::Error::Eip712Error(e.to_string()))?;
256
257        let sig = self.sign_digest(digest).await?;
258        let sig = utils::sig_from_digest_bytes_trial_recovery(&sig, digest, &self.pubkey);
259
260        Ok(sig)
261    }
262
263    fn address(&self) -> Address {
264        self.address
265    }
266
267    /// Returns the signer's chain id
268    fn chain_id(&self) -> u64 {
269        self.chain_id
270    }
271
272    /// Sets the signer's chain id
273    fn with_chain_id<T: Into<u64>>(mut self, chain_id: T) -> Self {
274        self.chain_id = chain_id.into();
275        self
276    }
277}
278
279#[cfg(test)]
280mod tests {
281    use super::*;
282    use crate::Signer;
283    use rusoto_core::{
284        credential::{EnvironmentProvider, StaticProvider},
285        Client, HttpClient, Region,
286    };
287
288    #[allow(dead_code)]
289    fn static_client() -> KmsClient {
290        let access_key = "".to_owned();
291        let secret_access_key = "".to_owned();
292
293        let client = Client::new_with(
294            StaticProvider::new(access_key, secret_access_key, None, None),
295            HttpClient::new().unwrap(),
296        );
297        KmsClient::new_with_client(client, Region::UsWest1)
298    }
299
300    #[allow(dead_code)]
301    fn env_client() -> KmsClient {
302        let client = Client::new_with(EnvironmentProvider::default(), HttpClient::new().unwrap());
303        KmsClient::new_with_client(client, Region::UsWest1)
304    }
305
306    #[tokio::test]
307    async fn it_signs_messages() {
308        let chain_id = 1;
309        let key_id = match std::env::var("AWS_KEY_ID") {
310            Ok(id) => id,
311            _ => return,
312        };
313        let client = env_client();
314        let signer = AwsSigner::new(client, key_id, chain_id).await.unwrap();
315
316        let message = vec![0, 1, 2, 3];
317
318        let sig = signer.sign_message(&message).await.unwrap();
319        sig.verify(message, signer.address).expect("valid sig");
320    }
321}