Repository: smithy-lang/smithy-rs (language: rust)

Issue:
Constant time comparison for SigV4a
Use constant time bigint here: https://github.com/awslabs/smithy-rs/blob/a69c346001adad00d689a65487c500acd8e85d2a/aws/rust-runtime/aws-sigv4/src/sign/v4a.rs#L69-L74


Discussion / hints:

Fix patch:
diff --git a/aws/rust-runtime/aws-sigv4/Cargo.toml b/aws/rust-runtime/aws-sigv4/Cargo.toml
index ce91ae5209..1e628da528 100644
--- a/aws/rust-runtime/aws-sigv4/Cargo.toml
+++ b/aws/rust-runtime/aws-sigv4/Cargo.toml
@@ -13,7 +13,7 @@ default = ["sign-http"]
 http0-compat = ["dep:http"]
 sign-http = ["dep:http", "dep:percent-encoding", "dep:form_urlencoded"]
 sign-eventstream = ["dep:aws-smithy-eventstream"]
-sigv4a = ["dep:p256", "dep:num-bigint", "dep:zeroize", "dep:ring"]
+sigv4a = ["dep:p256", "dep:crypto-bigint", "dep:subtle", "dep:zeroize", "dep:ring"]
 
 [dependencies]
 aws-credential-types = { path = "../aws-credential-types" }
@@ -33,6 +33,8 @@ percent-encoding = { version = "2.1", optional = true }
 regex = "1.5"
 ring = { version = "0.17.5", optional = true }
 sha2 = "0.10"
+crypto-bigint = { version = "0.5.4", optional = true }
+subtle = { version = "2.5.0", optional = true }
 time = "0.3.5"
 tracing = "0.1"
 zeroize = { version = "^1", optional = true }
diff --git a/aws/rust-runtime/aws-sigv4/src/sign/v4a.rs b/aws/rust-runtime/aws-sigv4/src/sign/v4a.rs
index 9c2b8cb980..a8cb3f9538 100644
--- a/aws/rust-runtime/aws-sigv4/src/sign/v4a.rs
+++ b/aws/rust-runtime/aws-sigv4/src/sign/v4a.rs
@@ -5,7 +5,7 @@
 
 use aws_smithy_runtime_api::client::identity::Identity;
 use bytes::{BufMut, BytesMut};
-use num_bigint::BigInt;
+use crypto_bigint::{CheckedAdd, CheckedSub, Encoding, U256};
 use once_cell::sync::Lazy;
 use p256::ecdsa::signature::Signer;
 use p256::ecdsa::{Signature, SigningKey};
@@ -14,16 +14,13 @@ use std::time::SystemTime;
 use zeroize::Zeroizing;
 
 const ALGORITHM: &[u8] = b"AWS4-ECDSA-P256-SHA256";
-static BIG_N_MINUS_2: Lazy<BigInt> = Lazy::new(|| {
+static BIG_N_MINUS_2: Lazy<U256> = Lazy::new(|| {
     // The N value from section 3.2.1.3 of https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-186.pdf
     // Used as the N value for the algorithm described in section A.2.2 of https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf
     // *(Basically a prime number blessed by the NSA for use in p256)*
-    const ORDER: &[u32] = &[
-        0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xBCE6FAAD, 0xA7179E84, 0xF3B9CAC2,
-        0xFC632551,
-    ];
-    let big_n = BigInt::from_slice(num_bigint::Sign::Plus, ORDER);
-    big_n - BigInt::from(2i32)
+    const ORDER: U256 =
+        U256::from_be_hex("ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551");
+    ORDER.checked_sub(&U256::from(2u32)).unwrap()
 });
 
 /// Calculates a Sigv4a signature
@@ -64,14 +61,16 @@ pub fn generate_signing_key(access_key: &str, secret_access_key: &str) -> impl A
         let tag = ring::hmac::sign(&key, &buf);
         let tag = &tag.as_ref()[0..32];
 
-        let k0 = BigInt::from_bytes_be(num_bigint::Sign::Plus, tag);
+        let k0 = U256::from_be_bytes(tag.try_into().expect("convert to [u8; 32]"));
 
         // It would be more secure for this to be a constant time comparison, but because this
         // is for client usage, that's not as big a deal.
         if k0 <= *BIG_N_MINUS_2 {
-            let pk = k0 + BigInt::from(1i32);
-            let d = Zeroizing::new(pk.to_bytes_be().1);
-            break SigningKey::from_bytes(&d).unwrap();
+            let pk = k0
+                .checked_add(&U256::ONE)
+                .expect("k0 is always less than U256::MAX");
+            let d = Zeroizing::new(pk.to_be_bytes());
+            break SigningKey::from_bytes(d.as_ref()).unwrap();
         }
 
         *counter = counter


Test patch: