From eb1bdb8a625b489fca65133ddd978c06e48935f8 Mon Sep 17 00:00:00 2001 From: Flavio Castelli Date: Wed, 27 Oct 2021 18:44:43 +0200 Subject: [PATCH] Implement flexible parsing of ECDSA keys TUF specification suggests to encode ECDSA keys using PEM. This is the behaviour of the python reference implementation. However, `go-tuf` encodes the keys as Hex numbers. This commit makes the decoding of ECDSA keys more relaxed, the code will try to decode them as PEM strings and it will resort to Hex when the first decode fails. Signed-off-by: Flavio Castelli Co-authored-by: Matthew James Briggs --- tough/src/schema/de.rs | 11 ++ tough/src/schema/decoded.rs | 26 ++++ tough/src/schema/key.rs | 6 +- .../data/hex-encoded-ecdsa-sig-keys/root.json | 144 ++++++++++++++++++ 4 files changed, 184 insertions(+), 3 deletions(-) create mode 100644 tough/tests/data/hex-encoded-ecdsa-sig-keys/root.json diff --git a/tough/src/schema/de.rs b/tough/src/schema/de.rs index 445f9d15..4f409411 100644 --- a/tough/src/schema/de.rs +++ b/tough/src/schema/de.rs @@ -86,4 +86,15 @@ mod tests { )) .is_err()); } + + /// Ensure that we can deserialize a root.json file that has hex-encoded ECDSA keys. This uses + /// sigstore's root.json file taken from here: + /// https://sigstore-tuf-root.storage.googleapis.com/2.root.json + #[test] + fn ecdsa_hex_encoded_keys() { + assert!(serde_json::from_str::>(include_str!( + "../../tests/data/hex-encoded-ecdsa-sig-keys/root.json" + )) + .is_ok()); + } } diff --git a/tough/src/schema/decoded.rs b/tough/src/schema/decoded.rs index 3ab06b28..a8655640 100644 --- a/tough/src/schema/decoded.rs +++ b/tough/src/schema/decoded.rs @@ -126,6 +126,32 @@ impl Encode for EcdsaPem { } } +/// [`Decode`]/[`Encode`] implementation for ECDSA public keys. +/// This is a flexible implementation, it will try to decode the key assuming +/// it is PEM encoded, if the decode fails it will then try to decode it +/// assuming it's Hex encoded. +/// The official TUF specification suggests ECDSA keys to be PEM encoded, +/// however the go-tuf implementation encodes them as Hex numbers. +/// This flexible decoder tries to cover both cases in a transparent way. +#[derive(Debug, Clone, Copy)] +pub struct EcdsaFlex {} + +impl Decode for EcdsaFlex { + fn decode(s: &str) -> Result, Error> { + if s.starts_with("-----BEGIN ") { + EcdsaPem::decode(s) + } else { + Hex::decode(s) + } + } +} + +impl Encode for EcdsaFlex { + fn encode(b: &[u8]) -> String { + EcdsaPem::encode(b) + } +} + // =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= impl<'de, T: Decode> Deserialize<'de> for Decoded { diff --git a/tough/src/schema/key.rs b/tough/src/schema/key.rs index 89c5ef5b..c7c8d2bd 100644 --- a/tough/src/schema/key.rs +++ b/tough/src/schema/key.rs @@ -2,7 +2,7 @@ //! Handles cryptographic keys and their serialization in TUF metadata files. -use crate::schema::decoded::{Decoded, EcdsaPem, Hex, RsaPem}; +use crate::schema::decoded::{Decoded, EcdsaFlex, Hex, RsaPem}; use crate::schema::error::{self, Result}; use olpc_cjson::CanonicalFormatter; use ring::digest::{digest, SHA256}; @@ -121,7 +121,7 @@ pub enum EcdsaScheme { #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] pub struct EcdsaKey { /// The public key. - pub public: Decoded, + pub public: Decoded, /// Any additional fields read during deserialization; will not be used. #[serde(flatten)] @@ -204,7 +204,7 @@ impl FromStr for Key { } else { Err(KeyParseError(())) } - } else if let Ok(public) = serde_plain::from_str::>(s) { + } else if let Ok(public) = serde_plain::from_str::>(s) { Ok(Key::Ecdsa { keyval: EcdsaKey { public, diff --git a/tough/tests/data/hex-encoded-ecdsa-sig-keys/root.json b/tough/tests/data/hex-encoded-ecdsa-sig-keys/root.json new file mode 100644 index 00000000..386ebe62 --- /dev/null +++ b/tough/tests/data/hex-encoded-ecdsa-sig-keys/root.json @@ -0,0 +1,144 @@ +{ + "signatures": [ + { + "keyid": "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", + "sig": "3046022100d3ea59490b253beae0926c6fa63f54336dea1ed700555be9f27ff55cd347639c0221009157d1ba012cead81948a4ab777d355451d57f5c4a2d333fc68d2e3f358093c2" + }, + { + "keyid": "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62", + "sig": "304502206eaef40564403ce572c6d062e0c9b0aab5e0223576133e081e1b495e8deb9efd02210080fd6f3464d759601b4afec596bbd5952f3a224cd06ed1cdfc3c399118752ba2" + }, + { + "keyid": "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", + "sig": "304502207baace02f56d8e6069f10b6ff098a26e7f53a7f9324ad62cffa0557bdeb9036c022100fb3032baaa090d0040c3f2fd872571c84479309b773208601d65948df87a9720" + }, + { + "keyid": "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb", + "sig": "304402205180c01905505dd88acd7a2dad979dd75c979b3722513a7bdedac88c6ae8dbeb022056d1ddf7a192f0b1c2c90ff487de2fb3ec9f0c03f66ea937c78d3b6a493504ca" + }, + { + "keyid": "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209", + "sig": "3046022100c8806d4647c514d80fd8f707d3369444c4fd1d0812a2d25f828e564c99790e3f022100bb51f12e862ef17a7d3da2ac103bebc5c7e792237006c4cafacd76267b249c2f" + } + ], + "signed": { + "_type": "root", + "consistent_snapshot": false, + "expires": "2022-05-11T19:09:02.663975009Z", + "keys": { + "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04cbc5cab2684160323c25cd06c3307178a6b1d1c9b949328453ae473c5ba7527e35b13f298b41633382241f3fd8526c262d43b45adee5c618fa0642c82b8a9803" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "b6710623a30c010738e64c5209d367df1c0a18cf90e6ab5292fb01680f83453d": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04fa1a3e42f2300cd3c5487a61509348feb1e936920fef2f83b7cd5dbe7ba045f538725ab8f18a666e6233edb7e0db8766c8dc336633449c5e1bbe0c182b02df0b" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04a71aacd835dc170ba6db3fa33a1a33dee751d4f8b0217b805b9bd3242921ee93672fdcfd840576c5bb0dc0ed815edf394c1ee48c2b5e02485e59bfc512f3adc7" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04117b33dd265715bf23315e368faa499728db8d1f0a377070a1c7b1aba2cc21be6ab1628e42f2cdd7a35479f2dce07b303a8ba646c55569a8d2a504ba7e86e447" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04cc1cd53a61c23e88cc54b488dfae168a257c34fac3e88811c55962b24cffbfecb724447999c54670e365883716302e49da57c79a33cd3e16f81fbc66f0bcdf48" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "048a78a44ac01099890d787e5e62afc29c8ccb69a70ec6549a6b04033b0a8acbfb42ab1ab9c713d225cdb52b858886cf46c8e90a7f3b9e6371882f370c259e1c5b" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "fc61191ba8a516fe386c7d6c97d918e1d241e1589729add09b122725b8c32451": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "044c7793ab74b9ddd713054e587b8d9c75c5f6025633d0fef7ca855ed5b8d5a474b23598fe33eb4a63630d526f74d4bdaec8adcb51993ed65652d651d7c49203eb" + }, + "scheme": "ecdsa-sha2-nistp256" + } + }, + "roles": { + "root": { + "keyids": [ + "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", + "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62", + "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", + "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb", + "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209" + ], + "threshold": 3 + }, + "snapshot": { + "keyids": [ + "fc61191ba8a516fe386c7d6c97d918e1d241e1589729add09b122725b8c32451" + ], + "threshold": 1 + }, + "targets": { + "keyids": [ + "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", + "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62", + "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", + "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb", + "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209" + ], + "threshold": 3 + }, + "timestamp": { + "keyids": [ + "b6710623a30c010738e64c5209d367df1c0a18cf90e6ab5292fb01680f83453d" + ], + "threshold": 1 + } + }, + "spec_version": "1.0", + "version": 2 + } +} \ No newline at end of file