Skip to content

Commit bc79bcb

Browse files
authored
Support for sign/verify w/ digest (#826)
1 parent ceb1151 commit bc79bcb

File tree

15 files changed

+712
-222
lines changed

15 files changed

+712
-222
lines changed

aws-lc-rs/src/digest.rs

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,30 @@ pub struct Digest {
218218
}
219219

220220
impl Digest {
221+
/// Imports a digest value provide by an external source. This allows for the signing of
222+
/// content that might not be directly accessible.
223+
///
224+
/// WARNING: Ensure that the digest is provided by a trusted source.
225+
/// When possible, prefer to directly compute the digest of content.
226+
///
227+
/// # Errors
228+
/// Returns `Unspecified` if the imported value is the wrong length for the specified algorithm.
229+
pub fn import_less_safe(
230+
digest: &[u8],
231+
algorithm: &'static Algorithm,
232+
) -> Result<Self, Unspecified> {
233+
if digest.len() != algorithm.output_len {
234+
return Err(Unspecified);
235+
}
236+
let mut my_digest = [0u8; MAX_OUTPUT_LEN];
237+
my_digest[0..digest.len()].copy_from_slice(&digest[0..digest.len()]);
238+
Ok(Digest {
239+
message: my_digest,
240+
len: digest.len(),
241+
algorithm,
242+
})
243+
}
244+
221245
/// The algorithm that was used to calculate the digest value.
222246
#[inline]
223247
#[must_use]
@@ -359,6 +383,7 @@ pub(crate) fn match_digest_type(algorithm_id: &AlgorithmID) -> ConstPointer<'_,
359383

360384
#[cfg(test)]
361385
mod tests {
386+
use crate::digest;
362387
#[cfg(feature = "fips")]
363388
mod fips;
364389

@@ -439,8 +464,6 @@ mod tests {
439464

440465
#[test]
441466
fn digest_coverage() {
442-
use crate::digest;
443-
444467
for alg in [
445468
&digest::SHA1_FOR_LEGACY_USE_ONLY,
446469
&digest::SHA224,
@@ -463,4 +486,14 @@ mod tests {
463486
assert_eq!(orig_digest.clone().as_ref(), clone_digest.as_ref());
464487
}
465488
}
489+
490+
#[test]
491+
fn test_import_less_safe() {
492+
let digest = digest::digest(&digest::SHA256, b"hello, world");
493+
let digest_copy =
494+
digest::Digest::import_less_safe(digest.as_ref(), &digest::SHA256).unwrap();
495+
496+
assert_eq!(digest.as_ref(), digest_copy.as_ref());
497+
assert_eq!(digest.algorithm, digest_copy.algorithm);
498+
}
466499
}

aws-lc-rs/src/ec/key_pair.rs

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44
// SPDX-License-Identifier: Apache-2.0 OR ISC
55

66
use crate::aws_lc::{EVP_PKEY, EVP_PKEY_EC};
7-
use core::fmt;
8-
use core::fmt::{Debug, Formatter};
9-
7+
use crate::digest::Digest;
108
use crate::ec::evp_key_generate;
119
use crate::ec::signature::{EcdsaSignatureFormat, EcdsaSigningAlgorithm, PublicKey};
1210
#[cfg(feature = "fips")]
1311
use crate::ec::validate_ec_evp_key;
1412
#[cfg(not(feature = "fips"))]
1513
use crate::ec::verify_evp_key_nid;
14+
use core::fmt;
15+
use core::fmt::{Debug, Formatter};
1616

1717
use crate::ec;
1818
use crate::ec::encoding::rfc5915::{marshal_rfc5915_private_key, parse_rfc5915_private_key};
@@ -207,7 +207,7 @@ impl EcdsaKeyPair {
207207
self.algorithm
208208
}
209209

210-
/// Returns the signature of the message using a random nonce.
210+
/// Returns a signature for the message.
211211
///
212212
/// # *ring* Compatibility
213213
/// Our implementation ignores the `SecureRandom` parameter.
@@ -235,6 +235,31 @@ impl EcdsaKeyPair {
235235
EcdsaSignatureFormat::Fixed => ec::ecdsa_asn1_to_fixed(self.algorithm.id, &out_sig)?,
236236
})
237237
}
238+
239+
/// Returns a signature for the message corresponding to the provided digest.
240+
///
241+
/// # Errors
242+
/// `error::Unspecified` on internal error.
243+
//
244+
// # FIPS
245+
// Not allowed.
246+
#[inline]
247+
pub fn sign_digest(&self, digest: &Digest) -> Result<Signature, Unspecified> {
248+
let out_sig = self
249+
.evp_pkey
250+
.sign_digest(digest, No_EVP_PKEY_CTX_consumer)?;
251+
if self.algorithm.digest != digest.algorithm() {
252+
return Err(Unspecified);
253+
}
254+
255+
Ok(match self.algorithm.sig_format {
256+
EcdsaSignatureFormat::ASN1 => Signature::new(|slice| {
257+
slice[..out_sig.len()].copy_from_slice(&out_sig);
258+
out_sig.len()
259+
}),
260+
EcdsaSignatureFormat::Fixed => ec::ecdsa_asn1_to_fixed(self.algorithm.id, &out_sig)?,
261+
})
262+
}
238263
}
239264

240265
/// Elliptic curve private key.

aws-lc-rs/src/ec/signature.rs

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -198,19 +198,41 @@ impl VerificationAlgorithm for EcdsaVerificationAlgorithm {
198198
verify_asn1_signature(self.id, self.digest, public_key, msg, signature)
199199
}
200200
EcdsaSignatureFormat::Fixed => {
201-
verify_fixed_signature(self.id, self.digest, public_key, msg, signature)
201+
let (out_bytes, out_bytes_len) = convert_fixed_signature(self.id, signature)?;
202+
verify_asn1_signature(self.id, self.digest, public_key, msg, unsafe {
203+
out_bytes.as_slice(out_bytes_len)
204+
})
205+
}
206+
}
207+
}
208+
209+
fn verify_digest_sig(
210+
&self,
211+
public_key: &[u8],
212+
digest: &digest::Digest,
213+
signature: &[u8],
214+
) -> Result<(), Unspecified> {
215+
if self.digest != digest.algorithm() {
216+
return Err(Unspecified);
217+
}
218+
match self.sig_format {
219+
EcdsaSignatureFormat::ASN1 => {
220+
verify_asn1_digest_signature(self.id, digest, public_key, signature)
221+
}
222+
EcdsaSignatureFormat::Fixed => {
223+
let (out_bytes, out_bytes_len) = convert_fixed_signature(self.id, signature)?;
224+
verify_asn1_digest_signature(self.id, digest, public_key, unsafe {
225+
out_bytes.as_slice(out_bytes_len)
226+
})
202227
}
203228
}
204229
}
205230
}
206231

207-
fn verify_fixed_signature(
232+
fn convert_fixed_signature(
208233
alg: &'static AlgorithmID,
209-
digest: &'static digest::Algorithm,
210-
public_key: &[u8],
211-
msg: &[u8],
212234
signature: &[u8],
213-
) -> Result<(), Unspecified> {
235+
) -> Result<(LcPtr<u8>, usize), Unspecified> {
214236
let mut out_bytes = null_mut::<u8>();
215237
let mut out_bytes_len = MaybeUninit::<usize>::uninit();
216238
let sig = unsafe { ecdsa_sig_from_fixed(alg, signature)? };
@@ -219,20 +241,30 @@ fn verify_fixed_signature(
219241
} {
220242
return Err(Unspecified);
221243
}
222-
let out_bytes = LcPtr::new(out_bytes)?;
223-
let signature = unsafe { out_bytes.as_slice(out_bytes_len.assume_init()) };
224-
verify_asn1_signature(alg, digest, public_key, msg, signature)
244+
Ok((LcPtr::new(out_bytes)?, unsafe {
245+
out_bytes_len.assume_init()
246+
}))
225247
}
226248

227249
fn verify_asn1_signature(
228250
alg: &'static AlgorithmID,
229-
digest: &'static digest::Algorithm,
251+
digest_alg: &'static digest::Algorithm,
230252
public_key: &[u8],
231253
msg: &[u8],
232254
signature: &[u8],
233255
) -> Result<(), Unspecified> {
234256
let evp_pkey = parse_ec_public_key(public_key, alg.nid())?;
235-
evp_pkey.verify(msg, Some(digest), No_EVP_PKEY_CTX_consumer, signature)
257+
evp_pkey.verify(msg, Some(digest_alg), No_EVP_PKEY_CTX_consumer, signature)
258+
}
259+
260+
fn verify_asn1_digest_signature(
261+
alg: &'static AlgorithmID,
262+
digest: &digest::Digest,
263+
public_key: &[u8],
264+
signature: &[u8],
265+
) -> Result<(), Unspecified> {
266+
let evp_pkey = parse_ec_public_key(public_key, alg.nid())?;
267+
evp_pkey.verify_digest_sig(digest, No_EVP_PKEY_CTX_consumer, signature)
236268
}
237269

238270
#[inline]

aws-lc-rs/src/ed25519.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use crate::pkcs8::{Document, Version};
2222
use crate::ptr::LcPtr;
2323
use crate::rand::SecureRandom;
2424
use crate::signature::{KeyPair, Signature, VerificationAlgorithm};
25-
use crate::{constant_time, hex, sealed};
25+
use crate::{constant_time, digest, hex, sealed};
2626

2727
/// The length of an Ed25519 public key.
2828
pub const ED25519_PUBLIC_KEY_LEN: usize = crate::aws_lc::ED25519_PUBLIC_KEY_LEN as usize;
@@ -53,6 +53,10 @@ impl VerificationAlgorithm for EdDSAParameters {
5353
)
5454
}
5555

56+
/// Verify `signature` for `msg` using `public_key`.
57+
///
58+
/// # Errors
59+
/// Returns `Unspecified` if the `msg` cannot be verified using `public_key`.
5660
fn verify_sig(
5761
&self,
5862
public_key: &[u8],
@@ -62,6 +66,19 @@ impl VerificationAlgorithm for EdDSAParameters {
6266
let evp_pkey = try_ed25519_public_key_from_bytes(public_key)?;
6367
evp_pkey.verify(msg, None, No_EVP_PKEY_CTX_consumer, signature)
6468
}
69+
70+
/// DO NOT USE. This function is required by `VerificationAlgorithm` but cannot be used w/ Ed25519.
71+
///
72+
/// # Errors
73+
/// Always returns `Unspecified`.
74+
fn verify_digest_sig(
75+
&self,
76+
_public_key: &[u8],
77+
_digest: &digest::Digest,
78+
_signature: &[u8],
79+
) -> Result<(), Unspecified> {
80+
Err(Unspecified)
81+
}
6582
}
6683

6784
fn try_ed25519_public_key_from_bytes(key_bytes: &[u8]) -> Result<LcPtr<EVP_PKEY>, KeyRejected> {

aws-lc-rs/src/evp_pkey.rs

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ use crate::aws_lc::{
55
EVP_DigestSign, EVP_DigestSignInit, EVP_DigestVerify, EVP_DigestVerifyInit, EVP_PKEY_CTX_new,
66
EVP_PKEY_CTX_new_id, EVP_PKEY_bits, EVP_PKEY_cmp, EVP_PKEY_get0_EC_KEY, EVP_PKEY_get0_RSA,
77
EVP_PKEY_get_raw_private_key, EVP_PKEY_get_raw_public_key, EVP_PKEY_id, EVP_PKEY_keygen,
8-
EVP_PKEY_keygen_init, EVP_PKEY_new_raw_private_key, EVP_PKEY_new_raw_public_key, EVP_PKEY_size,
9-
EVP_PKEY_up_ref, EVP_marshal_private_key, EVP_marshal_private_key_v2, EVP_marshal_public_key,
8+
EVP_PKEY_keygen_init, EVP_PKEY_new_raw_private_key, EVP_PKEY_new_raw_public_key, EVP_PKEY_sign,
9+
EVP_PKEY_sign_init, EVP_PKEY_size, EVP_PKEY_up_ref, EVP_PKEY_verify, EVP_PKEY_verify_init,
10+
EVP_marshal_private_key, EVP_marshal_private_key_v2, EVP_marshal_public_key,
1011
EVP_parse_private_key, EVP_parse_public_key, EC_KEY, EVP_PKEY, EVP_PKEY_CTX, EVP_PKEY_ED25519,
1112
RSA,
1213
};
@@ -23,6 +24,7 @@ use crate::{cbs, digest};
2324
// TODO: Uncomment when MSRV >= 1.64
2425
// use core::ffi::c_int;
2526
use crate::digest::digest_ctx::DigestContext;
27+
use crate::digest::Digest;
2628
use crate::fips::indicator_check;
2729
use std::os::raw::c_int;
2830
use std::ptr::{null, null_mut};
@@ -354,6 +356,55 @@ impl LcPtr<EVP_PKEY> {
354356
Ok(signature.into_boxed_slice())
355357
}
356358

359+
pub(crate) fn sign_digest<F>(
360+
&self,
361+
digest: &Digest,
362+
padding_fn: Option<F>,
363+
) -> Result<Box<[u8]>, Unspecified>
364+
where
365+
F: EVP_PKEY_CTX_consumer,
366+
{
367+
let mut pctx = LcPtr::new(unsafe { EVP_PKEY_CTX_new(*self.as_mut_unsafe(), null_mut()) })?;
368+
369+
if 1 != unsafe { EVP_PKEY_sign_init(*pctx.as_mut()) } {
370+
return Err(Unspecified);
371+
}
372+
373+
if let Some(pad_fn) = padding_fn {
374+
pad_fn(*pctx.as_mut())?;
375+
}
376+
377+
let msg_digest = digest.as_ref();
378+
let mut sig_len = 0;
379+
if 1 != unsafe {
380+
EVP_PKEY_sign(
381+
*pctx.as_mut(),
382+
null_mut(),
383+
&mut sig_len,
384+
msg_digest.as_ptr(),
385+
msg_digest.len(),
386+
)
387+
} {
388+
return Err(Unspecified);
389+
}
390+
391+
let mut signature = vec![0u8; sig_len];
392+
if 1 != indicator_check!(unsafe {
393+
EVP_PKEY_sign(
394+
*pctx.as_mut(),
395+
signature.as_mut_ptr(),
396+
&mut sig_len,
397+
msg_digest.as_ptr(),
398+
msg_digest.len(),
399+
)
400+
}) {
401+
return Err(Unspecified);
402+
}
403+
signature.truncate(sig_len);
404+
405+
Ok(signature.into_boxed_slice())
406+
}
407+
357408
pub(crate) fn verify<F>(
358409
&self,
359410
msg: &[u8],
@@ -404,6 +455,42 @@ impl LcPtr<EVP_PKEY> {
404455
Ok(())
405456
}
406457

458+
pub(crate) fn verify_digest_sig<F>(
459+
&self,
460+
digest: &Digest,
461+
padding_fn: Option<F>,
462+
signature: &[u8],
463+
) -> Result<(), Unspecified>
464+
where
465+
F: EVP_PKEY_CTX_consumer,
466+
{
467+
let mut pctx = LcPtr::new(unsafe { EVP_PKEY_CTX_new(*self.as_mut_unsafe(), null_mut()) })?;
468+
469+
if 1 != unsafe { EVP_PKEY_verify_init(*pctx.as_mut()) } {
470+
return Err(Unspecified);
471+
}
472+
473+
if let Some(pad_fn) = padding_fn {
474+
pad_fn(*pctx.as_mut())?;
475+
}
476+
477+
let msg_digest = digest.as_ref();
478+
479+
if 1 == unsafe {
480+
indicator_check!(EVP_PKEY_verify(
481+
*pctx.as_mut(),
482+
signature.as_ptr(),
483+
signature.len(),
484+
msg_digest.as_ptr(),
485+
msg_digest.len(),
486+
))
487+
} {
488+
Ok(())
489+
} else {
490+
Err(Unspecified)
491+
}
492+
}
493+
407494
pub(crate) fn generate<F>(pkey_type: c_int, params_fn: Option<F>) -> Result<Self, Unspecified>
408495
where
409496
F: EVP_PKEY_CTX_consumer,

aws-lc-rs/src/pqdsa/key_pair.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,14 +175,16 @@ pub(crate) fn evp_key_pqdsa_generate(nid: c_int) -> Result<LcPtr<EVP_PKEY>, Unsp
175175
mod tests {
176176
use super::*;
177177

178-
use crate::signature::{UnparsedPublicKey, VerificationAlgorithm};
178+
use crate::signature::UnparsedPublicKey;
179179
use crate::unstable::signature::{ML_DSA_44_SIGNING, ML_DSA_65_SIGNING, ML_DSA_87_SIGNING};
180180

181181
const TEST_ALGORITHMS: &[&PqdsaSigningAlgorithm] =
182182
&[&ML_DSA_44_SIGNING, &ML_DSA_65_SIGNING, &ML_DSA_87_SIGNING];
183183

184184
#[test]
185185
fn test_public_key_serialization() {
186+
#[cfg(feature = "ring-sig-verify")]
187+
use crate::signature::VerificationAlgorithm;
186188
for &alg in TEST_ALGORITHMS {
187189
// Generate a new key pair
188190
let keypair = PqdsaKeyPair::generate(alg).unwrap();

0 commit comments

Comments
 (0)