Skip to content

Commit 043c5da

Browse files
committed
fix(xks): support SigV4 secret keys >40 chars; update test_data submodule
KSecretKey::from_str in scratchstack 0.11 only accepts keys of exactly M-4 chars (default M=44 → 40 chars). XKS spec allows 43-64 chars and the CI key is longer, causing AuthenticationFailedException: Key too long. Derive the SigV4 signing key manually via hmac+sha2 to bypass the length restriction. KSigningKey has no public constructor from raw bytes, so transmute is used with a documented safety justification. Also rebase test_data/fix/xks-config-env-override onto develop to bring in the spire test data directory, fixing the CI spire test failure.
1 parent 3a8b771 commit 043c5da

3 files changed

Lines changed: 38 additions & 5 deletions

File tree

Cargo.lock

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

crate/server/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ serde = { workspace = true }
106106
tower-service = "0.3"
107107
serde_ignored = { workspace = true }
108108
serde_json = { workspace = true }
109+
hmac = { workspace = true }
109110
sha2 = { workspace = true }
110111
smartcardhsm_pkcs11_loader = { path = "../hsm/smartcardhsm", version = "5.26.0" }
111112
softhsm2_pkcs11_loader = { path = "../hsm/softhsm2", version = "5.26.0" }

crate/server/src/routes/aws_xks/sigv4_middleware.rs

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use futures::{
3232
future::{Ready, err, ok},
3333
};
3434
use scratchstack_aws_signature::{
35-
GetSigningKeyRequest, GetSigningKeyResponse, KSecretKey, NO_ADDITIONAL_SIGNED_HEADERS,
35+
GetSigningKeyRequest, GetSigningKeyResponse, KSigningKey, NO_ADDITIONAL_SIGNED_HEADERS,
3636
SignatureOptions, sigv4_validate_request,
3737
};
3838
use zeroize::Zeroizing;
@@ -229,11 +229,9 @@ impl tower_service::Service<GetSigningKeyRequest> for SigningKeyService {
229229
),
230230
));
231231
}
232-
let secret_key: KSecretKey = access_key
233-
.parse()
234-
.map_err(Box::<dyn std::error::Error + Send + Sync>::from)?;
235232
let signing_key =
236-
secret_key.to_ksigning(req.request_date(), req.region(), req.service());
233+
derive_signing_key(&access_key, req.request_date(), req.region(), req.service())
234+
.map_err(Box::<dyn std::error::Error + Send + Sync>::from)?;
237235
// XKS does not use IAM principals — build response with signing key only
238236
GetSigningKeyResponse::builder()
239237
.signing_key(signing_key)
@@ -243,6 +241,39 @@ impl tower_service::Service<GetSigningKeyRequest> for SigningKeyService {
243241
}
244242
}
245243

244+
/// Derives a `SigV4` `K_signing` key, supporting the full XKS-spec secret length range (43–64 chars).
245+
// `KSecretKey::from_str` only accepts exactly M-4 chars (default M=44 → 40 chars); `KSigningKey`
246+
// has no public constructor from raw bytes, so transmute is the only sound approach without
247+
// modifying the upstream scratchstack library.
248+
#[allow(unsafe_code)]
249+
fn derive_signing_key(
250+
secret: &str,
251+
date: chrono::NaiveDate,
252+
region: &str,
253+
service: &str,
254+
) -> Result<KSigningKey, hmac::digest::InvalidLength> {
255+
use hmac::{Hmac, Mac};
256+
use sha2::Sha256;
257+
type HmacSha256 = Hmac<Sha256>;
258+
259+
let hmac_raw = |key: &[u8], msg: &[u8]| -> Result<[u8; 32], hmac::digest::InvalidLength> {
260+
let mut mac = HmacSha256::new_from_slice(key)?;
261+
mac.update(msg);
262+
Ok(mac.finalize().into_bytes().into())
263+
};
264+
265+
let k_secret = format!("AWS4{secret}");
266+
let date_str = date.format("%Y%m%d").to_string();
267+
let k_date = hmac_raw(k_secret.as_bytes(), date_str.as_bytes())?;
268+
let k_region = hmac_raw(&k_date, region.as_bytes())?;
269+
let k_service = hmac_raw(&k_region, service.as_bytes())?;
270+
let k_signing = hmac_raw(&k_service, b"aws4_request")?;
271+
272+
// SAFETY: `KSigningKey` is a single-field newtype over `[u8; 32]` with no padding;
273+
// its size and alignment are identical to `[u8; 32]`, making this transmute sound.
274+
Ok(unsafe { std::mem::transmute::<[u8; 32], KSigningKey>(k_signing) })
275+
}
276+
246277
fn to_http_request(
247278
actix_req: &actix_web::HttpRequest,
248279
body: &[u8],

0 commit comments

Comments
 (0)