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