Skip to content

Commit 639a1ec

Browse files
taltenbachnordicjm
authored andcommitted
sim: Make TLV size for ECDSA signature constant
ECDSA signatures are encoded as ASN.1 and the size of the ASN.1 representation can vary depending on the value of the two integers the signature is composed of. This means that when ECDSA is used, the size of the TLV area is not always equal to the size that was estimated by the simulator when attempting to determine the maximum image size. Indeed, the estimate gives the maximum possible size of the TLV area and depending on its actual size, the generated images might be in fact a bit smaller than expected. This is not a big issue but adds a bit of randomness in the simulation and make difficult to generate precisely oversized images when desired for example. This commit ensures an ECDSA signature with the largest possible size is always used, making the size of the corresponding TLV entry constant in the simulator. Signed-off-by: Thomas Altenbach <thomas.altenbach@legrand.com>
1 parent b671a8f commit 639a1ec

File tree

1 file changed

+51
-29
lines changed

1 file changed

+51
-29
lines changed

sim/src/tlv.rs

Lines changed: 51 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -579,20 +579,57 @@ impl ManifestGen for TlvGen {
579579
}
580580

581581
if self.kinds.contains(&TlvKinds::ECDSASIG) {
582+
let keyhash;
583+
let key_bytes;
584+
let sign_algo;
585+
let key_pair;
586+
let keyhash_size;
587+
let max_sig_size;
588+
589+
if self.kinds.contains(&TlvKinds::SHA384) {
590+
keyhash = digest::digest(&digest::SHA384, ECDSAP384_PUB_KEY);
591+
keyhash_size = 48;
592+
key_bytes = pem::parse(include_bytes!("../../root-ec-p384-pkcs8.pem").as_ref()).unwrap();
593+
sign_algo = &ECDSA_P384_SHA384_ASN1_SIGNING;
594+
key_pair = EcdsaKeyPair::from_pkcs8(sign_algo, &key_bytes.contents).unwrap();
595+
max_sig_size = 104; // 96 bytes for "raw" r and s + at most 8 bytes for ASN.1 encoding
596+
} else {
597+
keyhash = digest::digest(&digest::SHA256, ECDSA256_PUB_KEY);
598+
keyhash_size = 32;
599+
key_bytes = pem::parse(include_bytes!("../../root-ec-p256-pkcs8.pem").as_ref()).unwrap();
600+
sign_algo = &ECDSA_P256_SHA256_ASN1_SIGNING;
601+
key_pair = EcdsaKeyPair::from_pkcs8(sign_algo, &key_bytes.contents).unwrap();
602+
max_sig_size = 72; // 64 bytes for "raw" r and s + at most 8 bytes for ASN.1 encoding
603+
}
604+
605+
// ECDSA signatures are encoded as ASN.1 with the r and s values stored as signed
606+
// integers. As such, the size can vary depending on the value of the high bits of r and
607+
// s. The maximum size is obtained when the high bit of both r and s is '1'. To generate
608+
// the largest possible image, the size of the TLV area was estimated assuming the
609+
// signature has the maximal size. To obtain a TLV area size exactly equal to the
610+
// estimated size, the signing process is repeated multiple times until a signature
611+
// having the largest possible size is obtained. This is taking advantage of the fact
612+
// ECDSA signing uses a randomly-generated nonce and is therefore non-deterministic.
613+
// Theoretically, four tries should be needed on average and the probability of not
614+
// having obtained a signature of the desired size after 100 tries is lower than 10e-12.
582615
let rng = rand::SystemRandom::new();
583-
let (signature, keyhash, keyhash_size) = if self.kinds.contains(&TlvKinds::SHA384) {
584-
let keyhash = digest::digest(&digest::SHA384, ECDSAP384_PUB_KEY);
585-
let key_bytes = pem::parse(include_bytes!("../../root-ec-p384-pkcs8.pem").as_ref()).unwrap();
586-
let sign_algo = &ECDSA_P384_SHA384_ASN1_SIGNING;
587-
let key_pair = EcdsaKeyPair::from_pkcs8(sign_algo, &key_bytes.contents).unwrap();
588-
(key_pair.sign(&rng, &sig_payload).unwrap(), keyhash, 48)
589-
} else {
590-
let keyhash = digest::digest(&digest::SHA256, ECDSA256_PUB_KEY);
591-
let key_bytes = pem::parse(include_bytes!("../../root-ec-p256-pkcs8.pem").as_ref()).unwrap();
592-
let sign_algo = &ECDSA_P256_SHA256_ASN1_SIGNING;
593-
let key_pair = EcdsaKeyPair::from_pkcs8(sign_algo, &key_bytes.contents).unwrap();
594-
(key_pair.sign(&rng, &sig_payload).unwrap(), keyhash, 32)
595-
};
616+
let max_tries = 100;
617+
let mut signature;
618+
let mut tries = 0;
619+
620+
loop {
621+
signature = key_pair.sign(&rng, &sig_payload).unwrap();
622+
623+
if signature.as_ref().len() == max_sig_size {
624+
break;
625+
}
626+
627+
tries += 1;
628+
629+
if tries >= max_tries {
630+
panic!("Failed to generate signature of correct size");
631+
}
632+
}
596633

597634
// Write public key
598635
let keyhash_slice = keyhash.as_ref();
@@ -781,23 +818,8 @@ impl ManifestGen for TlvGen {
781818
let mut size_buf = &mut result[npro_pos + 2 .. npro_pos + 4];
782819
size_buf.write_u16::<LittleEndian>(size).unwrap();
783820

784-
// ECDSA is stored as an ASN.1 integer. For a 128-bit value, this maximally results in 33
785-
// bytes of storage for each of the two values. If the high bit is zero, it will take 32
786-
// bytes, if the top 8 bits are zero, it will take 31 bits, and so on. The smaller size
787-
// will occur with decreasing likelihood. We'll allow this to get a bit smaller, hopefully
788-
// allowing the tests to pass with false failures rare. For this case, we'll handle up to
789-
// the top 16 bits of both numbers being all zeros (1 in 2^32).
790-
if !Caps::has_ecdsa() {
791-
if size_estimate != result.len() {
792-
panic!("Incorrect size estimate: {} (actual {})", size_estimate, result.len());
793-
}
794-
} else {
795-
if size_estimate < result.len() || size_estimate > result.len() + 6 {
796-
panic!("Incorrect size estimate: {} (actual {})", size_estimate, result.len());
797-
}
798-
}
799821
if size_estimate != result.len() {
800-
log::warn!("Size off: {} actual {}", size_estimate, result.len());
822+
panic!("Incorrect size estimate: {} (actual {})", size_estimate, result.len());
801823
}
802824

803825
result

0 commit comments

Comments
 (0)