Skip to content

Commit b3c20b8

Browse files
cfrantzpamaury
authored andcommitted
[hsmtool] Add support for prehashing in CloudKMS
CloudKMS is adding support for SLH-DSA signatures using the prehashed domain. Add support for keygen, sign and verify with prehashing. 1. Check the domain during keygen. CloudKMS encodes both the algorithm and domain into their notion of algorithm, so you must provide the domain at keygen time. 2. Check that the `domain` given for sign and verify matches the domain for which the key was generated. Signed-off-by: Chris Frantz <[email protected]> (cherry picked from commit 57fd70b)
1 parent 6e1ec52 commit b3c20b8

File tree

1 file changed

+65
-31
lines changed

1 file changed

+65
-31
lines changed

sw/host/hsmtool/src/extra/spxkms.rs

Lines changed: 65 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ struct KmsDigest {
122122
sha256: String,
123123
}
124124

125-
#[derive(Serialize, Debug)]
125+
#[derive(Serialize, Debug, Default)]
126126
struct KmsSignRequest {
127127
#[serde(skip_serializing_if = "Option::is_none")]
128128
digest: Option<KmsDigest>,
@@ -131,7 +131,8 @@ struct KmsSignRequest {
131131
}
132132

133133
impl SpxKms {
134-
const ALGORITHM: &'static str = "PQ_SIGN_SLH_DSA_SHA2_128S";
134+
const PURE_ALGORITHM: &'static str = "PQ_SIGN_SLH_DSA_SHA2_128S";
135+
const PREHASH_ALGORITHM: &'static str = "PQ_SIGN_HASH_SLH_DSA_SHA2_128S_SHA256";
135136

136137
pub fn new(parameters: &str) -> Result<Box<Self>> {
137138
let output = Command::new("gcloud")
@@ -173,6 +174,21 @@ impl SpxKms {
173174
}
174175
}
175176

177+
fn kms_to_algorithm(kms_algo: &str) -> Result<String> {
178+
match kms_algo {
179+
Self::PURE_ALGORITHM | Self::PREHASH_ALGORITHM => Ok("SLH-DSA-SHA2-128S".into()),
180+
_ => Err(HsmError::Unsupported(format!("algorithm {kms_algo}")).into()),
181+
}
182+
}
183+
184+
fn kms_to_domain(kms_algo: &str) -> Result<SpxDomain> {
185+
match kms_algo {
186+
Self::PURE_ALGORITHM => Ok(SpxDomain::Pure),
187+
Self::PREHASH_ALGORITHM => Ok(SpxDomain::PreHashedSha256),
188+
_ => Err(HsmError::Unsupported(format!("algorithm {kms_algo}")).into()),
189+
}
190+
}
191+
176192
fn get<RSP: DeserializeOwned>(&self, url: impl IntoUrl) -> Result<RSP> {
177193
let client = Client::new();
178194
log::debug!("GET {}", url.as_str());
@@ -216,7 +232,7 @@ impl SpxKms {
216232
match versions
217233
.crypto_key_versions
218234
.iter()
219-
.filter(|v| v.state == "ENABLED" && v.algorithm == Self::ALGORITHM)
235+
.filter(|v| v.state == "ENABLED" && Self::kms_to_algorithm(&v.algorithm).is_ok())
220236
.next_back()
221237
{
222238
Some(key) => Ok(key.clone()),
@@ -250,14 +266,15 @@ impl SpxInterface for SpxKms {
250266
.rsplit_once('/')
251267
.ok_or_else(|| HsmError::ParseError("could not parse key name".into()))
252268
.with_context(|| format!("key name {:?}", k.name))?;
253-
if k.version_template.algorithm != Self::ALGORITHM {
269+
if Self::kms_to_algorithm(&k.version_template.algorithm).is_err() {
254270
continue;
255271
}
256272
let key = self.get_key_version(name)?;
257273
result.push(KeyEntry {
258274
alias: name.into(),
259275
hash: None,
260-
algorithm: key.algorithm.clone(),
276+
algorithm: Self::kms_to_algorithm(&key.algorithm)?,
277+
domain: Some(Self::kms_to_domain(&key.algorithm)?),
261278
..Default::default()
262279
});
263280
}
@@ -267,10 +284,6 @@ impl SpxInterface for SpxKms {
267284
/// Get the public key info.
268285
fn get_key_info(&self, alias: &str) -> Result<KeyInfo> {
269286
let key = self.get_public_key(alias)?;
270-
let algorithm = key
271-
.algorithm
272-
.trim_start_matches("PQ_SIGN_")
273-
.replace('_', "-");
274287
let data = if let Some(pem) = &key.pem {
275288
pem.as_str()
276289
} else if let Some(public_key) = &key.public_key {
@@ -280,8 +293,8 @@ impl SpxInterface for SpxKms {
280293
};
281294
Ok(KeyInfo {
282295
hash: "".into(),
283-
algorithm,
284-
domain: Some(SpxDomain::Pure),
296+
algorithm: Self::kms_to_algorithm(&key.algorithm)?,
297+
domain: Some(Self::kms_to_domain(&key.algorithm)?),
285298
public_key: Base64::decode_vec(data)?,
286299
private_blob: Vec::new(),
287300
})
@@ -292,31 +305,33 @@ impl SpxInterface for SpxKms {
292305
&self,
293306
alias: &str,
294307
_algorithm: &str,
295-
_domain: SpxDomain,
308+
domain: SpxDomain,
296309
_token: &str,
297310
flags: GenerateFlags,
298311
) -> Result<KeyEntry> {
299312
if flags.contains(GenerateFlags::EXPORT_PRIVATE) {
300313
return Err(HsmError::Unsupported("export of private key material".into()).into());
301314
}
315+
let algorithm = match domain {
316+
SpxDomain::Pure => Self::PURE_ALGORITHM,
317+
SpxDomain::PreHashedSha256 => Self::PREHASH_ALGORITHM,
318+
_ => return Err(HsmError::Unsupported(format!("domain {domain}")).into()),
319+
};
302320
let url = self
303321
.keyring
304322
.join(&format!("cryptoKeys?crypto_key_id={alias}"))?;
305323
let template = KmsCreateKey {
306324
purpose: "ASYMMETRIC_SIGN".into(),
307325
version_template: VersionTemplate {
308-
algorithm: Self::ALGORITHM.into(),
326+
algorithm: algorithm.into(),
309327
protection_level: "SOFTWARE".into(),
310328
},
311329
};
312330
let resp = self.post::<KmsKeyRef>(url, &template)?;
313331
Ok(KeyEntry {
314332
alias: alias.into(),
315-
algorithm: resp
316-
.version_template
317-
.algorithm
318-
.trim_start_matches("PQ_SIGN_")
319-
.replace('_', "-"),
333+
algorithm: Self::kms_to_algorithm(&resp.version_template.algorithm)?,
334+
domain: Some(Self::kms_to_domain(&resp.version_template.algorithm)?),
320335
..Default::default()
321336
})
322337
}
@@ -347,28 +362,40 @@ impl SpxInterface for SpxKms {
347362
domain: SpxDomain,
348363
message: &[u8],
349364
) -> Result<Vec<u8>> {
350-
match domain {
351-
SpxDomain::Pure => {}
352-
_ => {
353-
return Err(HsmError::Unsupported(format!(
354-
"domain {domain} not supported by {}",
355-
self.get_version()?
356-
))
357-
.into());
358-
}
359-
};
360365
let alias = alias.ok_or(HsmError::NoSearchCriteria)?;
361366
if key_hash.is_some() {
362367
log::warn!("ignored key_hash {key_hash:?}");
363368
}
364369
let key = self.get_key_version(alias)?;
370+
let keydomain = Self::kms_to_domain(&key.algorithm)?;
371+
if domain != keydomain {
372+
return Err(HsmError::Unsupported(format!(
373+
"domain {domain} not supported by key {alias}",
374+
))
375+
.into());
376+
}
377+
365378
let url = self
366379
.keyring
367380
.join(&format!("/v1/{}:asymmetricSign", key.name))?;
368-
let req = KmsSignRequest {
369-
digest: None,
370-
data: Some(Base64::encode_string(message)),
381+
382+
// Format the signing request:
383+
// - For the "pure" domain, we place the message in the `data` field.
384+
// - For the "prehashed" domain, we place the digest into the `digest` field.
385+
let req = match keydomain {
386+
SpxDomain::Pure => KmsSignRequest {
387+
data: Some(Base64::encode_string(message)),
388+
..Default::default()
389+
},
390+
SpxDomain::PreHashedSha256 => KmsSignRequest {
391+
digest: Some(KmsDigest {
392+
sha256: Base64::encode_string(message),
393+
}),
394+
..Default::default()
395+
},
396+
_ => unreachable!(),
371397
};
398+
372399
let resp = self.post::<IndexMap<String, String>>(url, &req)?;
373400
let signature = Base64::decode_vec(&resp["signature"])?;
374401
Ok(signature)
@@ -388,6 +415,13 @@ impl SpxInterface for SpxKms {
388415
log::warn!("ignored key_hash {key_hash:?}");
389416
}
390417
let info = self.get_key_info(alias)?;
418+
let keydomain = info.domain.expect("kms key domain");
419+
if domain != keydomain {
420+
return Err(HsmError::Unsupported(format!(
421+
"domain {domain} not supported by key {alias}",
422+
))
423+
.into());
424+
}
391425
let pk =
392426
SpxPublicKey::from_bytes(SphincsPlus::from_str(&info.algorithm)?, &info.public_key)?;
393427
match pk.verify(domain, signature, message) {

0 commit comments

Comments
 (0)