From 7c3de147c020396201ac099c41d4c2ac03eb4cd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AsPulse=20/=20=E3=81=82=E3=81=99=E3=81=B1=E3=82=8B?= Date: Mon, 30 Dec 2024 14:15:16 +0900 Subject: [PATCH 01/13] cleanup existing code --- sutera-lib/src/lib.rs | 1 - sutera-lib/src/signature/identity.rs | 234 --------------------------- sutera-lib/src/signature/message.rs | 169 ------------------- sutera-lib/src/signature/mod.rs | 2 - 4 files changed, 406 deletions(-) delete mode 100644 sutera-lib/src/signature/identity.rs delete mode 100644 sutera-lib/src/signature/message.rs delete mode 100644 sutera-lib/src/signature/mod.rs diff --git a/sutera-lib/src/lib.rs b/sutera-lib/src/lib.rs index 4917bfb..e69de29 100644 --- a/sutera-lib/src/lib.rs +++ b/sutera-lib/src/lib.rs @@ -1 +0,0 @@ -pub mod signature; diff --git a/sutera-lib/src/signature/identity.rs b/sutera-lib/src/signature/identity.rs deleted file mode 100644 index 9cc5ead..0000000 --- a/sutera-lib/src/signature/identity.rs +++ /dev/null @@ -1,234 +0,0 @@ -use std::str::FromStr; - -use ring_compat::signature::ed25519; -use thiserror::Error; - -/// An error that occurs when parsing a Sutera identity string. -/// Sutera-identity-stringをパースする際に起きるエラー。 -#[derive(Debug, Error, PartialEq, Eq)] -pub enum SuteraIdentityStringParseError { - #[error("invalid identity string")] - InvalidFormat, - #[error("invalid identity string, version {0} is not supported")] - VersionMismatch(String), - #[error("invalid identity string, kind {0} is not supported")] - UnsupportedKind(String), -} - -#[derive(Debug, PartialEq, Eq, Clone, strum::EnumString, strum::AsRefStr)] -pub enum SuteraIdentityKind { - #[strum(serialize = "user")] - User, -} - -/// A struct representing an identity in the Sutera network. -/// Suteraネットワークにおけるidentityを表す構造体。 -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct SuteraIdentity { - /// The kind of object treated in the Sutera network (e.g., user, server, world etc.) - /// Suteraネットワークで扱うオブジェクトの種類 (例: ユーザー、サーバー、ワールドなど) - pub kind: SuteraIdentityKind, - - /// The display name of the identity. - /// This is only designated for human-readable purposes and plays no role in authentication. - /// display_name can only contain alphanumeric characters (0-9, a-z) - /// identityの表示名。 - /// これは人間の理解を促進するためだけに定義されており、認証プロセスにおいて何の役目も果たしません。 - /// 表示名には英数字(0-9, a-z)のみを利用することができます。 - pub display_name: Option, - - /// The ed25519 public key of the identity. - /// This is used to verify the signature of the identity. - /// identityのed25519公開鍵です。 - /// identityの署名を検証されるために使用されます。 - pub pub_signature: ed25519::VerifyingKey, -} - -/// Convert SuteraIdentity to String. -/// The format is `{type}@{display_name}.sutera-identity-v1.{pub_signature}`. -/// Because pub_signature is 32byte, so the part `{pub_signature}` is 64 letters hexadecimal string. -/// SuteraIdentityを文字列に変換します。 -/// 形式は `{type}@{display_name}.sutera-identity-v1.{pub_signature}` です。 -/// pub_signatureは32バイトなので、`{pub_signature}`は64文字の16進数文字列になります。 -/// -/// ## Example -/// ```no_test -/// user.sutera-identity-v1.fffffff..... -/// user@alice.sutera-identity-v1.fffffff..... -/// ``` -impl From for String { - fn from(identity: SuteraIdentity) -> String { - format!( - "{}.sutera-identity-v1.{}", - match identity.display_name { - Some(display_name) => format!("{}@{}", identity.kind.as_ref(), display_name), - None => identity.kind.as_ref().to_string(), - }, - identity - .pub_signature - .0 - .iter() - .fold(String::with_capacity(64), |acc, byte| acc - + &format!("{:02x}", byte)) - ) - } -} - -impl TryFrom for SuteraIdentity { - type Error = SuteraIdentityStringParseError; - - fn try_from(value: String) -> Result { - let parts: Vec<&str> = value.split('.').collect(); - if parts.len() != 3 { - return Err(SuteraIdentityStringParseError::InvalidFormat); - } - - let (kind, version, pub_key) = (parts[0].to_string(), parts[1].to_string(), parts[2]); - - if kind.is_empty() { - return Err(SuteraIdentityStringParseError::InvalidFormat); - } - - if version != "sutera-identity-v1" { - return Err(SuteraIdentityStringParseError::VersionMismatch(version)); - } - - if pub_key.len() != 64 { - return Err(SuteraIdentityStringParseError::InvalidFormat); - } - - let (kind, display_name) = match kind.find('@') { - Some(index) => ( - SuteraIdentityKind::from_str(&kind[..index]).map_err(|_| { - SuteraIdentityStringParseError::UnsupportedKind(kind[..index].to_string()) - })?, - Some(kind[index + 1..].to_string()), - ), - None => ( - SuteraIdentityKind::from_str(&kind).map_err(|_| { - SuteraIdentityStringParseError::UnsupportedKind(kind.to_string()) - })?, - None, - ), - }; - - let pub_key_bytes = parts[2] - .as_bytes() - .chunks(2) - .map(|chunk| u8::from_str_radix(std::str::from_utf8(chunk).unwrap(), 16)) - .collect::, std::num::ParseIntError>>() - .or(Err(SuteraIdentityStringParseError::InvalidFormat))?; - - Ok(SuteraIdentity { - kind, - display_name, - pub_signature: ed25519::VerifyingKey(pub_key_bytes.try_into().unwrap()), - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use pretty_assertions::assert_eq; - use ring_compat::signature::ed25519; - - #[test] - fn sutera_identity_string() { - // ダミーの公開鍵(全てのビットが0)のSuteraIdentityを作成, 文字列に変換 - let identity = SuteraIdentity { - kind: SuteraIdentityKind::User, - display_name: Some("see2et".to_string()), - pub_signature: ed25519::VerifyingKey([0; 32]), - }; - - let identity_str: String = identity.clone().into(); - assert_eq!( - identity_str, - "user@see2et.sutera-identity-v1.0000000000000000000000000000000000000000000000000000000000000000" - ); - - // 変換した文字列をSuteraIdentityに戻し, オリジナルのSuteraIdentityと一致するか検証 - let parsed_identity: SuteraIdentity = identity_str.try_into().unwrap(); - assert_eq!(identity, parsed_identity); - } - - #[test] - fn sutera_identity_string_without_name() { - // ダミーの公開鍵(全てのビットが0)のSuteraIdentityを作成, 文字列に変換 - let identity = SuteraIdentity { - kind: SuteraIdentityKind::User, - display_name: None, - pub_signature: ed25519::VerifyingKey([0; 32]), - }; - - let identity_str: String = identity.clone().into(); - assert_eq!( - identity_str, - "user.sutera-identity-v1.0000000000000000000000000000000000000000000000000000000000000000" - ); - - // 変換した文字列をSuteraIdentityに戻し, オリジナルのSuteraIdentityと一致するか検証 - let parsed_identity: SuteraIdentity = identity_str.try_into().unwrap(); - assert_eq!(identity, parsed_identity); - } - - #[test] - fn sutera_identity_string_version_mismatch() { - // バージョンが異なる文字列をSuteraIdentityに変換しようとした場合のエラーを検証 - let invalid_identity_str = "see2et.sutera-identity-v2.xxx"; - assert_eq!( - SuteraIdentity::try_from(invalid_identity_str.to_string()), - Err(SuteraIdentityStringParseError::VersionMismatch( - "sutera-identity-v2".to_string() - )) - ); - } - - #[test] - fn sutera_identity_string_invalid() { - // 不正な文字列をSuteraIdentityに変換しようとした場合にエラーにちゃんとなるか検証 - - let invalid_identity_str = "user.sutera-identity-v1"; - assert_eq!( - SuteraIdentity::try_from(invalid_identity_str.to_string()), - Err(SuteraIdentityStringParseError::InvalidFormat) - ); - let invalid_identity_str = "user.sutera-identity-v1.abc"; - assert_eq!( - SuteraIdentity::try_from(invalid_identity_str.to_string()), - Err(SuteraIdentityStringParseError::InvalidFormat) - ); - let invalid_identity_str = "user.sutera-identity-v1.x000000000000000000000000000000000000000000000000000000000000000"; - assert_eq!( - SuteraIdentity::try_from(invalid_identity_str.to_string()), - Err(SuteraIdentityStringParseError::InvalidFormat) - ); - let invalid_identity_str = "unknown.sutera-identity-v1.x000000000000000000000000000000000000000000000000000000000000000"; - assert_eq!( - SuteraIdentity::try_from(invalid_identity_str.to_string()), - Err(SuteraIdentityStringParseError::UnsupportedKind( - "unknown".to_string() - )) - ); - let invalid_identity_str = "unknown@hello.sutera-identity-v1.x000000000000000000000000000000000000000000000000000000000000000"; - assert_eq!( - SuteraIdentity::try_from(invalid_identity_str.to_string()), - Err(SuteraIdentityStringParseError::UnsupportedKind( - "unknown".to_string() - )) - ); - let invalid_identity_str = - "sutera-identity-v1.0000000000000000000000000000000000000000000000000000000000000000"; - assert_eq!( - SuteraIdentity::try_from(invalid_identity_str.to_string()), - Err(SuteraIdentityStringParseError::InvalidFormat) - ); - let invalid_identity_str = - ".sutera-identity-v1.0000000000000000000000000000000000000000000000000000000000000000"; - assert_eq!( - SuteraIdentity::try_from(invalid_identity_str.to_string()), - Err(SuteraIdentityStringParseError::InvalidFormat) - ); - } -} diff --git a/sutera-lib/src/signature/message.rs b/sutera-lib/src/signature/message.rs deleted file mode 100644 index cf04af0..0000000 --- a/sutera-lib/src/signature/message.rs +++ /dev/null @@ -1,169 +0,0 @@ -use std::str::FromStr; - -use super::identity::SuteraIdentity; -use ring_compat::signature::{ed25519, Signer, Verifier}; -use serde::{Deserialize, Serialize}; -use thiserror::Error; - -/// A common structure of message exchanged within the Sutera network. -/// Suteraネットワークにおけるメッセージの送受信に使用される構造体です。 -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct SuteraSignedMessage { - pub author: SuteraIdentity, - pub message: String, - pub signature: ed25519::Signature, -} - -/// An error that occurs when signing a message. -/// メッセージの署名時に起きるエラー。 -#[derive(Debug, Error)] -pub enum SuteraMessageSigningError { - #[error("the signing key does not match the author's verifying key")] - SigningKeyMismatch, -} - -impl SuteraSignedMessage { - /// Sign a message with the [`author`]'s signing key. - /// [`author`]の署名鍵でメッセージを署名する。 - /// - /// ## Parameters / 引数 - /// - `author`: The author of the message. / メッセージの署名者。 - /// - `message`: The content of the message. / メッセージの内容。 - /// - `signer`: The signing key of the author. / 署名者の署名鍵。 - /// - /// ## Returns / 戻り値 - /// A new signed message. - /// if the signing key does not match the author's verifying key, return `Err`. - /// 新たに署名されたメッセージが返却されます。 - /// 署名鍵が署名者の認証鍵と合致しない場合、`Err`が返却されます。 - pub fn new( - author: SuteraIdentity, - message: String, - signer: ed25519::SigningKey, - ) -> Result { - let verifying_key = signer.verifying_key(); - if verifying_key != author.pub_signature { - return Err(SuteraMessageSigningError::SigningKeyMismatch); - } - - let signature = signer.sign(message.as_bytes()); - - Ok(SuteraSignedMessage { - author, - message, - signature, - }) - } - - /// Check if the signature is valid. - /// 署名が有効かどうかを確認します。 - /// - /// **SuteraSignedMessage should be verified before processing the message.** - /// **SuteraSignedMessageはメッセージが処理される前に検証されなければいけません。** - /// - /// **The signature is once checked at the time of creation in normal scenario,** - /// **so this method is used as a assertion.** - /// **署名は通常の場合、作成されたタイミングで一度検証されます。** - /// **そのため、このメソッドはアサーションとして利用されます。** - /// - /// ## Return / 戻り値 - /// `true` if the signature is valid, otherwise `false`. - /// 署名が有効な場合は`true`、そうでない場合は`false`を返します。 - pub fn verify(&self) -> bool { - self.author - .pub_signature - .verify(self.message.as_bytes(), &self.signature) - .is_ok() - } -} - -impl Serialize for SuteraSignedMessage { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let payload = SuteraSignedMessagePayload { - author: self.author.clone().into(), - message: self.message.clone(), - signature: self.signature.to_string(), - }; - payload.serialize(serializer) - } -} - -impl<'de> Deserialize<'de> for SuteraSignedMessage { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let payload = SuteraSignedMessagePayload::deserialize(deserializer)?; - let author = SuteraIdentity::try_from(payload.author).map_err(serde::de::Error::custom)?; - let signature = - ed25519::Signature::from_str(&payload.signature).map_err(serde::de::Error::custom)?; - - Ok(SuteraSignedMessage { - author, - message: payload.message, - signature, - }) - } -} - -#[derive(Serialize, Deserialize)] -struct SuteraSignedMessagePayload { - pub author: String, - pub message: String, - pub signature: String, -} - -#[cfg(test)] -mod tests { - use crate::signature::identity::SuteraIdentityKind; - - use super::*; - use pretty_assertions::assert_eq; - use rand_core::{OsRng, RngCore}; - - fn test_signed_message() -> SuteraSignedMessage { - // ランダムな秘密鍵を生成 - let mut ed25519_seed = [0u8; 32]; - OsRng.fill_bytes(&mut ed25519_seed); - let secret = ed25519::SigningKey::from_bytes(&ed25519_seed); - - // 秘密鍵からSuteraIdentityを生成 - let identity = SuteraIdentity { - kind: SuteraIdentityKind::User, - display_name: Some("see2et".to_string()), - pub_signature: secret.verifying_key(), - }; - - // 適当なStringをメッセージとして用意し,署名する - let message = "Hello, Sutera!"; - SuteraSignedMessage::new(identity, message.to_string(), secret).unwrap() - } - - #[test] - fn sign_message() { - // ランダムな秘密鍵で署名されたメッセージを生成 - let mut signed_message = test_signed_message(); - - // 署名検証を通過することを確認 - assert!(signed_message.verify()); - - // 内容を変更し,改竄されたメッセージとして署名検証に失敗することを確認 - signed_message.message = "Hello, Sutera?".to_string(); - assert!(!signed_message.verify()); - } - - #[test] - fn signed_message_serializable() { - // ランダムな秘密鍵で署名されたメッセージを生成 - let signed_message = test_signed_message(); - - // シリアライズ -> デシリアライズしても内容が変わらないことを確認 - let serialized = serde_json::to_string(&signed_message).unwrap(); - let deserialized: SuteraSignedMessage = serde_json::from_str(&serialized).unwrap(); - - assert_eq!(signed_message, deserialized); - } -} diff --git a/sutera-lib/src/signature/mod.rs b/sutera-lib/src/signature/mod.rs deleted file mode 100644 index 674a14b..0000000 --- a/sutera-lib/src/signature/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod identity; -pub mod message; From 1ffc4003c251cf0f6619d9afbe3ab026f6cf88a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AsPulse=20/=20=E3=81=82=E3=81=99=E3=81=B1=E3=82=8B?= Date: Mon, 30 Dec 2024 15:08:56 +0900 Subject: [PATCH 02/13] Add Signed, Signature, SuteraIdentity --- Cargo.lock | 375 ++------------------- sutera-lib/Cargo.toml | 5 +- sutera-lib/src/lib.rs | 1 + sutera-lib/src/signature/algorithms/mod.rs | 4 + sutera-lib/src/signature/mod.rs | 24 ++ 5 files changed, 52 insertions(+), 357 deletions(-) create mode 100644 sutera-lib/src/signature/algorithms/mod.rs create mode 100644 sutera-lib/src/signature/mod.rs diff --git a/Cargo.lock b/Cargo.lock index a80cd00..3b01025 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,16 +17,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "aead" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" -dependencies = [ - "crypto-common", - "generic-array", -] - [[package]] name = "autocfg" version = "1.3.0" @@ -48,33 +38,12 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "base16ct" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" - -[[package]] -name = "base64ct" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" - [[package]] name = "bitflags" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - [[package]] name = "bytes" version = "1.6.0" @@ -93,109 +62,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - -[[package]] -name = "crypto-bigint" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" -dependencies = [ - "generic-array", - "rand_core", - "subtle", - "zeroize", -] - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "der" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" -dependencies = [ - "const-oid", - "zeroize", -] - [[package]] name = "diff" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - -[[package]] -name = "ecdsa" -version = "0.16.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" -dependencies = [ - "der", - "elliptic-curve", - "signature", -] - -[[package]] -name = "ed25519" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" -dependencies = [ - "pkcs8", - "signature", -] - -[[package]] -name = "elliptic-curve" -version = "0.13.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" -dependencies = [ - "base16ct", - "crypto-bigint", - "digest", - "ff", - "generic-array", - "group", - "rand_core", - "sec1", - "subtle", - "zeroize", -] - -[[package]] -name = "ff" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" -dependencies = [ - "rand_core", - "subtle", -] - [[package]] name = "gateway" version = "0.1.0" @@ -204,51 +76,12 @@ dependencies = [ "tokio", ] -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", - "zeroize", -] - -[[package]] -name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - [[package]] name = "gimli" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" -[[package]] -name = "group" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" -dependencies = [ - "ff", - "rand_core", - "subtle", -] - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - [[package]] name = "hermit-abi" version = "0.3.9" @@ -257,9 +90,9 @@ checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "libc" @@ -322,27 +155,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "p256" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" -dependencies = [ - "ecdsa", - "elliptic-curve", -] - -[[package]] -name = "p384" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" -dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", -] - [[package]] name = "parking_lot" version = "0.12.3" @@ -372,16 +184,6 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - [[package]] name = "pretty_assertions" version = "1.4.0" @@ -392,15 +194,6 @@ dependencies = [ "yansi", ] -[[package]] -name = "primeorder" -version = "0.13.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" -dependencies = [ - "elliptic-curve", -] - [[package]] name = "proc-macro2" version = "1.0.85" @@ -419,15 +212,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - [[package]] name = "redox_syscall" version = "0.5.1" @@ -437,52 +221,12 @@ dependencies = [ "bitflags", ] -[[package]] -name = "ring" -version = "0.17.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" -dependencies = [ - "cc", - "cfg-if", - "getrandom", - "libc", - "spin", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "ring-compat" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccce7bae150b815f0811db41b8312fcb74bffa4cab9cee5429ee00f356dd5bd4" -dependencies = [ - "aead", - "digest", - "ecdsa", - "ed25519", - "generic-array", - "p256", - "p384", - "pkcs8", - "rand_core", - "ring", - "signature", -] - [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" -[[package]] -name = "rustversion" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" - [[package]] name = "ryu" version = "1.0.18" @@ -490,23 +234,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] -name = "scopeguard" -version = "1.2.0" +name = "ryu-js" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +checksum = "ad97d4ce1560a5e27cec89519dc8300d1aa6035b099821261c651486a19e44d5" [[package]] -name = "sec1" -version = "0.7.3" +name = "scopeguard" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" -dependencies = [ - "base16ct", - "der", - "generic-array", - "subtle", - "zeroize", -] +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" @@ -530,31 +267,34 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] [[package]] -name = "signal-hook-registry" -version = "1.4.2" +name = "serde_json_canonicalizer" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +checksum = "18032888bfda612a88f6b9c7c7a12e8168686936702fe584e3f1b1fc7848443a" dependencies = [ - "libc", + "ryu-js", + "serde", + "serde_json", ] [[package]] -name = "signature" -version = "2.2.0" +name = "signal-hook-registry" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ - "rand_core", + "libc", ] [[package]] @@ -573,60 +313,13 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - -[[package]] -name = "strum" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn", -] - -[[package]] -name = "subtle" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" - [[package]] name = "sutera-lib" version = "0.1.0" dependencies = [ "pretty_assertions", - "rand_core", - "ring-compat", "serde", - "serde_json", - "strum", + "serde_json_canonicalizer", "thiserror", ] @@ -691,30 +384,12 @@ dependencies = [ "syn", ] -[[package]] -name = "typenum" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -865,9 +540,3 @@ name = "yansi" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" - -[[package]] -name = "zeroize" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/sutera-lib/Cargo.toml b/sutera-lib/Cargo.toml index 6268048..bb6a1d1 100644 --- a/sutera-lib/Cargo.toml +++ b/sutera-lib/Cargo.toml @@ -5,11 +5,8 @@ edition = "2021" [dev-dependencies] pretty_assertions = "1.4.0" -serde_json = "1.0.117" [dependencies] -rand_core = { version = "0.6.4", features = ["std"] } -ring-compat = "0.8.0" serde = { version = "1.0.203", features = ["derive"] } -strum = { version = "0.26.2", features = ["derive"] } +serde_json_canonicalizer = "0.3.0" thiserror = "1.0.61" diff --git a/sutera-lib/src/lib.rs b/sutera-lib/src/lib.rs index e69de29..4917bfb 100644 --- a/sutera-lib/src/lib.rs +++ b/sutera-lib/src/lib.rs @@ -0,0 +1 @@ +pub mod signature; diff --git a/sutera-lib/src/signature/algorithms/mod.rs b/sutera-lib/src/signature/algorithms/mod.rs new file mode 100644 index 0000000..fda3ac8 --- /dev/null +++ b/sutera-lib/src/signature/algorithms/mod.rs @@ -0,0 +1,4 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct SigningAlgorithm(String); diff --git a/sutera-lib/src/signature/mod.rs b/sutera-lib/src/signature/mod.rs new file mode 100644 index 0000000..aba4699 --- /dev/null +++ b/sutera-lib/src/signature/mod.rs @@ -0,0 +1,24 @@ +pub mod algorithms; +use self::algorithms::SigningAlgorithm; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone)] +struct Signed { + #[serde(bound(deserialize = "T: DeserializeOwned", serialize = "T: Serialize"))] + payload: T, + signature: Signature, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +struct Signature { + identity: SuteraIdentity, + signature: String, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +struct SuteraIdentity { + display_name: String, + algorithm: SigningAlgorithm, + public_key: String, +} From feebb1eff6ac1a8c7197db5ea5b6b46460a950bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AsPulse=20/=20=E3=81=82=E3=81=99=E3=81=B1=E3=82=8B?= Date: Tue, 31 Dec 2024 16:09:02 +0900 Subject: [PATCH 03/13] Add error design and so on --- Cargo.lock | 321 +++++++++++++++++- rust-toolchain.toml | 2 +- sutera-lib/Cargo.toml | 4 + sutera-lib/src/error/mod.rs | 53 +++ sutera-lib/src/lib.rs | 2 + .../src/signature/algorithms/ed25519.rs | 27 ++ sutera-lib/src/signature/algorithms/mod.rs | 26 +- sutera-lib/src/signature/mod.rs | 4 +- sutera-lib/src/util/hex.rs | 101 ++++++ sutera-lib/src/util/mod.rs | 1 + 10 files changed, 536 insertions(+), 5 deletions(-) create mode 100644 sutera-lib/src/error/mod.rs create mode 100644 sutera-lib/src/signature/algorithms/ed25519.rs create mode 100644 sutera-lib/src/util/hex.rs create mode 100644 sutera-lib/src/util/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 3b01025..0c3699d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -17,6 +17,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "assert_matches" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" + [[package]] name = "autocfg" version = "1.3.0" @@ -38,12 +44,27 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bitflags" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "bytes" version = "1.6.0" @@ -62,12 +83,114 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "cpufeatures" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "zeroize", +] + [[package]] name = "diff" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "gateway" version = "0.1.0" @@ -76,6 +199,27 @@ dependencies = [ "tokio", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "gimli" version = "0.29.0" @@ -94,6 +238,12 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.155" @@ -155,6 +305,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + [[package]] name = "parking_lot" version = "0.12.3" @@ -184,6 +340,16 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pretty_assertions" version = "1.4.0" @@ -212,6 +378,15 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "redox_syscall" version = "0.5.1" @@ -227,6 +402,15 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "ryu" version = "1.0.18" @@ -245,6 +429,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "semver" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" + [[package]] name = "serde" version = "1.0.203" @@ -288,6 +478,26 @@ dependencies = [ "serde_json", ] +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -297,6 +507,15 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core", +] + [[package]] name = "smallvec" version = "1.13.2" @@ -313,14 +532,34 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "sutera-lib" version = "0.1.0" dependencies = [ + "assert_matches", + "ed25519-dalek", "pretty_assertions", "serde", "serde_json_canonicalizer", "thiserror", + "tracing", + "tracing-error", ] [[package]] @@ -354,6 +593,16 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "tokio" version = "1.38.0" @@ -384,12 +633,76 @@ dependencies = [ "syn", ] +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tracing-error" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "sharded-slab", + "thread_local", + "tracing-core", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -540,3 +853,9 @@ name = "yansi" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 6230899..0b0392e 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.78.0" +channel = "1.83.0" components = ["rust-analyzer"] profile = "default" diff --git a/sutera-lib/Cargo.toml b/sutera-lib/Cargo.toml index bb6a1d1..ef0484d 100644 --- a/sutera-lib/Cargo.toml +++ b/sutera-lib/Cargo.toml @@ -4,9 +4,13 @@ version = "0.1.0" edition = "2021" [dev-dependencies] +assert_matches = "1.5.0" pretty_assertions = "1.4.0" [dependencies] +ed25519-dalek = "2.1.1" serde = { version = "1.0.203", features = ["derive"] } serde_json_canonicalizer = "0.3.0" thiserror = "1.0.61" +tracing = "0.1.41" +tracing-error = { version = "0.2.1", features = ["traced-error"] } diff --git a/sutera-lib/src/error/mod.rs b/sutera-lib/src/error/mod.rs new file mode 100644 index 0000000..5f11b40 --- /dev/null +++ b/sutera-lib/src/error/mod.rs @@ -0,0 +1,53 @@ +use std::fmt::{Debug, Display}; + +use tracing_error::SpanTrace; + +pub trait TraceableError { + fn trace(&self) -> &SpanTrace; +} + +pub struct CapturedError { + pub error: E, + span_trace: SpanTrace, +} + +impl TraceableError for CapturedError { + fn trace(&self) -> &SpanTrace { + &self.span_trace + } +} + +impl Display for CapturedError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + ::fmt(&self.error, f) + } +} + +impl Debug for CapturedError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + ::fmt(&self.error, f) + } +} + +impl PartialEq for CapturedError { + fn eq(&self, other: &Self) -> bool { + self.error == other.error + } +} + +impl Eq for CapturedError {} + +impl std::error::Error for CapturedError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + self.error.source() + } +} + +impl From for CapturedError { + fn from(error: E) -> Self { + Self { + error, + span_trace: SpanTrace::capture(), + } + } +} diff --git a/sutera-lib/src/lib.rs b/sutera-lib/src/lib.rs index 4917bfb..087afef 100644 --- a/sutera-lib/src/lib.rs +++ b/sutera-lib/src/lib.rs @@ -1 +1,3 @@ +pub mod error; pub mod signature; +pub mod util; diff --git a/sutera-lib/src/signature/algorithms/ed25519.rs b/sutera-lib/src/signature/algorithms/ed25519.rs new file mode 100644 index 0000000..a1c10fa --- /dev/null +++ b/sutera-lib/src/signature/algorithms/ed25519.rs @@ -0,0 +1,27 @@ +use std::cell::LazyCell; + +use super::{SigningAlgorithm, SigningAlgorithmKind}; +use tracing::instrument; +use tracing_error::TracedError; + +pub struct Ed25519 {} + +impl SigningAlgorithm for Ed25519 { + fn is_capable(kind: &SigningAlgorithmKind) -> bool { + kind.0 == "ed25519" + } + + #[instrument("Ed25519/sign", skip(data, private_key))] + fn sign(data: &str, private_key: &str) -> Result> { + todo!() + } + + #[instrument("Ed25519/verify", skip(data, signature, public_key))] + fn verify( + data: &str, + signature: &str, + public_key: &str, + ) -> Result> { + todo!() + } +} diff --git a/sutera-lib/src/signature/algorithms/mod.rs b/sutera-lib/src/signature/algorithms/mod.rs index fda3ac8..7d83995 100644 --- a/sutera-lib/src/signature/algorithms/mod.rs +++ b/sutera-lib/src/signature/algorithms/mod.rs @@ -1,4 +1,28 @@ +pub mod ed25519; + use serde::{Deserialize, Serialize}; +use thiserror::Error; +use tracing_error::TracedError; + +#[derive(Error, Debug)] +enum SignatureError { + #[error("Invalid signature: {0}")] + Signature(Box), + #[error("Invalid public key: {0}")] + PublicKey(Box), + #[error("Invalid private key: {0}")] + PrivateKey(Box), +} #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] -pub struct SigningAlgorithm(String); +pub struct SigningAlgorithmKind(String); + +pub trait SigningAlgorithm { + fn is_capable(kind: &SigningAlgorithmKind) -> bool; + fn sign(data: &str, private_key: &str) -> Result>; + fn verify( + data: &str, + signature: &str, + public_key: &str, + ) -> Result>; +} diff --git a/sutera-lib/src/signature/mod.rs b/sutera-lib/src/signature/mod.rs index aba4699..d29cda6 100644 --- a/sutera-lib/src/signature/mod.rs +++ b/sutera-lib/src/signature/mod.rs @@ -1,5 +1,5 @@ pub mod algorithms; -use self::algorithms::SigningAlgorithm; +use self::algorithms::SigningAlgorithmKind; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; @@ -19,6 +19,6 @@ struct Signature { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] struct SuteraIdentity { display_name: String, - algorithm: SigningAlgorithm, + algorithm: SigningAlgorithmKind, public_key: String, } diff --git a/sutera-lib/src/util/hex.rs b/sutera-lib/src/util/hex.rs new file mode 100644 index 0000000..09a4077 --- /dev/null +++ b/sutera-lib/src/util/hex.rs @@ -0,0 +1,101 @@ +use std::fmt::Write; +use std::num::ParseIntError; +use std::str::Utf8Error; +use tracing_error::SpanTrace; + +use thiserror::Error; +use tracing::instrument; + +use crate::error::{CapturedError, TraceableError}; + +#[instrument("to_hex")] +pub(crate) fn to_hex(data: &[u8]) -> String { + data.iter().fold(String::new(), |mut acc, byte| { + write!(acc, "{byte:02x}").unwrap(); + acc + }) +} + +#[derive(Error, Debug, PartialEq, Eq)] +enum FromHexError { + #[error(transparent)] + Utf8(#[from] CapturedError), + + #[error(transparent)] + ParseInt(#[from] CapturedError), +} + +impl TraceableError for FromHexError { + fn trace(&self) -> &SpanTrace { + match self { + FromHexError::Utf8(e) => e.trace(), + FromHexError::ParseInt(e) => e.trace(), + } + } +} + +impl From for FromHexError { + fn from(error: Utf8Error) -> Self { + CapturedError::from(error).into() + } +} + +impl From for FromHexError { + fn from(error: ParseIntError) -> Self { + CapturedError::from(error).into() + } +} + +#[instrument("from_hex")] +pub(crate) fn from_hex(data: &str) -> Result, FromHexError> { + data.as_bytes() + .chunks(2) + .map(|chunk| Ok(u8::from_str_radix(std::str::from_utf8(chunk)?, 16)?)) + .collect::, FromHexError>>() +} + +#[cfg(test)] +mod tests { + use super::*; + use assert_matches::assert_matches; + use pretty_assertions::assert_eq; + + #[test] + fn from_hex_after_to_hex_should_do_nothing() { + assert_eq!( + from_hex(&to_hex(&[0x01, 0x02, 0x03, 0x04])), + Ok(vec![0x01, 0x02, 0x03, 0x04]) + ); + } + + #[test] + fn to_hex_after_from_hex_should_do_nothing() { + assert_eq!( + from_hex("faceb00c").map(|hex| to_hex(&hex)), + Ok("faceb00c".to_string()) + ); + } + + #[test] + fn from_hex_should_handle_odd_length_strings() { + assert_eq!(from_hex("f"), Ok(vec![0xf])); + assert_eq!(from_hex("fa"), Ok(vec![0xfa])); + assert_eq!(from_hex("fac"), Ok(vec![0xfa, 0xc])); + assert_eq!(from_hex("face"), Ok(vec![0xfa, 0xce])); + } + + #[test] + fn to_hex_should_handle_empty_input() { + assert_eq!(to_hex(&[]), ""); + } + + #[test] + fn from_hex_should_handle_empty_input() { + assert_eq!(from_hex(""), Ok(vec![])); + } + + #[test] + fn from_hex_should_fail_for_invalid_characters() { + assert_matches!(from_hex("g"), Err(FromHexError::ParseInt(_))); + } +} diff --git a/sutera-lib/src/util/mod.rs b/sutera-lib/src/util/mod.rs new file mode 100644 index 0000000..ce02e67 --- /dev/null +++ b/sutera-lib/src/util/mod.rs @@ -0,0 +1 @@ +pub mod hex; From f65d7c1696d96e3f0cfa945abe720afd3d098746 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AsPulse=20/=20=E3=81=82=E3=81=99=E3=81=B1=E3=82=8B?= Date: Wed, 1 Jan 2025 14:59:23 +0900 Subject: [PATCH 04/13] Implement from_hex, to_hex and fix error design to some extent --- Cargo.lock | 120 ++++++++++++++++++++++++++++-------- sutera-lib/Cargo.toml | 1 + sutera-lib/src/error/mod.rs | 36 ++++++++++- sutera-lib/src/util/hex.rs | 19 ++---- 4 files changed, 133 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0c3699d..57bcf58 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -260,6 +260,12 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + [[package]] name = "memchr" version = "2.7.2" @@ -286,6 +292,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num_cpus" version = "1.16.0" @@ -311,6 +327,12 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "parking_lot" version = "0.12.3" @@ -331,7 +353,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -560,6 +582,7 @@ dependencies = [ "thiserror", "tracing", "tracing-error", + "tracing-subscriber", ] [[package]] @@ -662,6 +685,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", + "valuable", ] [[package]] @@ -674,15 +698,29 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + [[package]] name = "tracing-subscriber" version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ + "nu-ansi-term", "sharded-slab", + "smallvec", "thread_local", "tracing-core", + "tracing-log", ] [[package]] @@ -697,6 +735,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "version_check" version = "0.9.5" @@ -709,6 +753,28 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-sys" version = "0.48.0" @@ -724,7 +790,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -744,18 +810,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -766,9 +832,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -778,9 +844,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -790,15 +856,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -808,9 +874,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -820,9 +886,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -832,9 +898,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -844,9 +910,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "yansi" diff --git a/sutera-lib/Cargo.toml b/sutera-lib/Cargo.toml index ef0484d..6a5c575 100644 --- a/sutera-lib/Cargo.toml +++ b/sutera-lib/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dev-dependencies] assert_matches = "1.5.0" pretty_assertions = "1.4.0" +tracing-subscriber = "0.3.19" [dependencies] ed25519-dalek = "2.1.1" diff --git a/sutera-lib/src/error/mod.rs b/sutera-lib/src/error/mod.rs index 5f11b40..b7c4349 100644 --- a/sutera-lib/src/error/mod.rs +++ b/sutera-lib/src/error/mod.rs @@ -2,7 +2,7 @@ use std::fmt::{Debug, Display}; use tracing_error::SpanTrace; -pub trait TraceableError { +pub trait TraceableError: std::error::Error { fn trace(&self) -> &SpanTrace; } @@ -17,6 +17,40 @@ impl TraceableError for CapturedError { } } +pub trait ResultTracingUnwrapExt { + fn tracing_unwrap(self) -> T; +} + +pub trait ResultCaptureErrExt { + fn capture_err(self) -> Result>; +} + +impl ResultTracingUnwrapExt for Result { + #[inline(always)] + fn tracing_unwrap(self) -> T { + match self { + Ok(value) => value, + Err(ref error) => { + tracing::error!(error = %error, "called `unwrap()` on an `Err` Value"); + eprintln!("== TRACING =="); + eprintln!("{}", error.trace()); + self.unwrap(); + unreachable!() + } + } + } +} + +impl ResultCaptureErrExt for Result { + #[inline(always)] + fn capture_err(self) -> Result> { + self.map_err(|error| CapturedError { + error, + span_trace: SpanTrace::capture(), + }) + } +} + impl Display for CapturedError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { ::fmt(&self.error, f) diff --git a/sutera-lib/src/util/hex.rs b/sutera-lib/src/util/hex.rs index 09a4077..1301855 100644 --- a/sutera-lib/src/util/hex.rs +++ b/sutera-lib/src/util/hex.rs @@ -1,12 +1,11 @@ use std::fmt::Write; -use std::num::ParseIntError; use std::str::Utf8Error; use tracing_error::SpanTrace; use thiserror::Error; use tracing::instrument; -use crate::error::{CapturedError, TraceableError}; +use crate::error::{CapturedError, ResultCaptureErrExt, TraceableError}; #[instrument("to_hex")] pub(crate) fn to_hex(data: &[u8]) -> String { @@ -34,23 +33,13 @@ impl TraceableError for FromHexError { } } -impl From for FromHexError { - fn from(error: Utf8Error) -> Self { - CapturedError::from(error).into() - } -} - -impl From for FromHexError { - fn from(error: ParseIntError) -> Self { - CapturedError::from(error).into() - } -} - #[instrument("from_hex")] pub(crate) fn from_hex(data: &str) -> Result, FromHexError> { data.as_bytes() .chunks(2) - .map(|chunk| Ok(u8::from_str_radix(std::str::from_utf8(chunk)?, 16)?)) + .map(|chunk| { + Ok(u8::from_str_radix(std::str::from_utf8(chunk).capture_err()?, 16).capture_err()?) + }) .collect::, FromHexError>>() } From e8853ef57396b9ed462157deabf534e599f23a81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AsPulse=20/=20=E3=81=82=E3=81=99=E3=81=B1=E3=82=8B?= Date: Wed, 1 Jan 2025 16:30:13 +0900 Subject: [PATCH 05/13] Implement Algorithm for ed25519 --- sutera-lib/Cargo.toml | 2 +- sutera-lib/src/error/mod.rs | 6 +- .../src/signature/algorithms/ed25519.rs | 70 ++++++++++++++++--- sutera-lib/src/signature/algorithms/mod.rs | 11 +-- sutera-lib/src/util/hex.rs | 2 +- 5 files changed, 71 insertions(+), 20 deletions(-) diff --git a/sutera-lib/Cargo.toml b/sutera-lib/Cargo.toml index 6a5c575..fd28785 100644 --- a/sutera-lib/Cargo.toml +++ b/sutera-lib/Cargo.toml @@ -9,7 +9,7 @@ pretty_assertions = "1.4.0" tracing-subscriber = "0.3.19" [dependencies] -ed25519-dalek = "2.1.1" +ed25519-dalek = { version = "2.1.1" } serde = { version = "1.0.203", features = ["derive"] } serde_json_canonicalizer = "0.3.0" thiserror = "1.0.61" diff --git a/sutera-lib/src/error/mod.rs b/sutera-lib/src/error/mod.rs index b7c4349..2380638 100644 --- a/sutera-lib/src/error/mod.rs +++ b/sutera-lib/src/error/mod.rs @@ -20,9 +20,9 @@ impl TraceableError for CapturedError { pub trait ResultTracingUnwrapExt { fn tracing_unwrap(self) -> T; } - pub trait ResultCaptureErrExt { fn capture_err(self) -> Result>; + fn capture_and_unwrap(self) -> T; } impl ResultTracingUnwrapExt for Result { @@ -49,6 +49,10 @@ impl ResultCaptureErrExt for Result { span_trace: SpanTrace::capture(), }) } + #[inline(always)] + fn capture_and_unwrap(self) -> T { + self.capture_err().tracing_unwrap() + } } impl Display for CapturedError { diff --git a/sutera-lib/src/signature/algorithms/ed25519.rs b/sutera-lib/src/signature/algorithms/ed25519.rs index a1c10fa..0d69d84 100644 --- a/sutera-lib/src/signature/algorithms/ed25519.rs +++ b/sutera-lib/src/signature/algorithms/ed25519.rs @@ -1,27 +1,79 @@ -use std::cell::LazyCell; +use std::str::FromStr; + +use crate::error::ResultCaptureErrExt; +use crate::util::hex::{from_hex, FromHexError}; use super::{SigningAlgorithm, SigningAlgorithmKind}; +use ed25519_dalek::ed25519::signature::SignerMut; +use ed25519_dalek::{Signature, SigningKey, VerifyingKey, PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH}; +use thiserror::Error; use tracing::instrument; -use tracing_error::TracedError; pub struct Ed25519 {} +#[derive(Debug, Error)] +enum DecodeKeyError { + #[error("unable to decode hex: {0}")] + UnableToDecodeHex(#[from] FromHexError), + #[error("invalid key length: {actual} bytes, expected {expected}")] + KeyLengthMismatch { actual: usize, expected: usize }, + #[error(transparent)] + SignatureError(#[from] ed25519_dalek::SignatureError), +} + +#[instrument("parse_verifying_key")] +fn parse_verifying_key(value: &str) -> Result { + let bytes = from_hex(value)?; + if bytes.len() != PUBLIC_KEY_LENGTH { + return Err(DecodeKeyError::KeyLengthMismatch { + actual: bytes.len(), + expected: PUBLIC_KEY_LENGTH, + }); + } + Ok(VerifyingKey::from_bytes( + &<[u8; PUBLIC_KEY_LENGTH]>::try_from(bytes).unwrap(), + )?) +} + +#[instrument("parse_signing_key")] +fn parse_signing_key(value: &str) -> Result { + let bytes = from_hex(value)?; + if bytes.len() != SECRET_KEY_LENGTH { + return Err(DecodeKeyError::KeyLengthMismatch { + actual: bytes.len(), + expected: SECRET_KEY_LENGTH, + }); + } + Ok(SigningKey::from_bytes( + &<[u8; SECRET_KEY_LENGTH]>::try_from(bytes).unwrap(), + )) +} + impl SigningAlgorithm for Ed25519 { fn is_capable(kind: &SigningAlgorithmKind) -> bool { kind.0 == "ed25519" } - #[instrument("Ed25519/sign", skip(data, private_key))] - fn sign(data: &str, private_key: &str) -> Result> { - todo!() + #[instrument("sign", skip(data, private_key))] + fn sign(data: &[u8], private_key: &str) -> Result { + let mut private_key = parse_signing_key(private_key) + .map_err(|e| super::SignatureError::PrivateKey(Box::new(e)))?; + Ok(private_key.try_sign(data).capture_and_unwrap().to_string()) } - #[instrument("Ed25519/verify", skip(data, signature, public_key))] + #[instrument("verify", skip(data, signature, public_key))] fn verify( - data: &str, + data: &[u8], signature: &str, public_key: &str, - ) -> Result> { - todo!() + ) -> Result { + let public_key = parse_verifying_key(public_key) + .map_err(|e| super::SignatureError::PublicKey(Box::new(e)))?; + let signature = Signature::from_str(signature) + .map_err(|e| super::SignatureError::Signature(Box::new(e)))?; + Ok(public_key.verify_strict(data, &signature).is_ok()) } } + +// TODO: テストを書く +// TODO: `generate_private`, `to_public` を実装する diff --git a/sutera-lib/src/signature/algorithms/mod.rs b/sutera-lib/src/signature/algorithms/mod.rs index 7d83995..e32be7d 100644 --- a/sutera-lib/src/signature/algorithms/mod.rs +++ b/sutera-lib/src/signature/algorithms/mod.rs @@ -2,10 +2,9 @@ pub mod ed25519; use serde::{Deserialize, Serialize}; use thiserror::Error; -use tracing_error::TracedError; #[derive(Error, Debug)] -enum SignatureError { +pub enum SignatureError { #[error("Invalid signature: {0}")] Signature(Box), #[error("Invalid public key: {0}")] @@ -19,10 +18,6 @@ pub struct SigningAlgorithmKind(String); pub trait SigningAlgorithm { fn is_capable(kind: &SigningAlgorithmKind) -> bool; - fn sign(data: &str, private_key: &str) -> Result>; - fn verify( - data: &str, - signature: &str, - public_key: &str, - ) -> Result>; + fn sign(data: &[u8], private_key: &str) -> Result; + fn verify(data: &[u8], signature: &str, public_key: &str) -> Result; } diff --git a/sutera-lib/src/util/hex.rs b/sutera-lib/src/util/hex.rs index 1301855..fd3446f 100644 --- a/sutera-lib/src/util/hex.rs +++ b/sutera-lib/src/util/hex.rs @@ -16,7 +16,7 @@ pub(crate) fn to_hex(data: &[u8]) -> String { } #[derive(Error, Debug, PartialEq, Eq)] -enum FromHexError { +pub enum FromHexError { #[error(transparent)] Utf8(#[from] CapturedError), From 6b274911df03b580cae727013c9d2fa5a4dcaa2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AsPulse=20/=20=E3=81=82=E3=81=99=E3=81=B1=E3=82=8B?= Date: Mon, 13 Jan 2025 18:48:41 +0900 Subject: [PATCH 06/13] Implement algorithm_action, SuteraIdentity::veriy, Signature::verify --- sutera-lib/src/signature/algorithms/mod.rs | 35 +++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/sutera-lib/src/signature/algorithms/mod.rs b/sutera-lib/src/signature/algorithms/mod.rs index e32be7d..c7dd076 100644 --- a/sutera-lib/src/signature/algorithms/mod.rs +++ b/sutera-lib/src/signature/algorithms/mod.rs @@ -3,8 +3,12 @@ pub mod ed25519; use serde::{Deserialize, Serialize}; use thiserror::Error; +use super::{Signature, SuteraIdentity}; + #[derive(Error, Debug)] pub enum SignatureError { + #[error("Algorithm not supported: {0:?}")] + AlgorithmNotSupported(SigningAlgorithmKind), #[error("Invalid signature: {0}")] Signature(Box), #[error("Invalid public key: {0}")] @@ -16,8 +20,37 @@ pub enum SignatureError { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub struct SigningAlgorithmKind(String); -pub trait SigningAlgorithm { +#[allow(dead_code)] +trait SigningAlgorithm { fn is_capable(kind: &SigningAlgorithmKind) -> bool; fn sign(data: &[u8], private_key: &str) -> Result; fn verify(data: &[u8], signature: &str, public_key: &str) -> Result; } + +macro_rules! algorithm_action { + ($kind:expr => $e:expr) => { + if $crate::signature::algorithms::ed25519::Ed25519::is_capable($kind) { + type Algorithm = $crate::signature::algorithms::ed25519::Ed25519; + Some($e) + } else { + None + } + }; +} + +impl SuteraIdentity { + pub fn verify(&self, data: &[u8], signature: &str) -> Result { + algorithm_action!(&self.algorithm => { + Algorithm::verify(data, signature, &self.public_key) + }) + .ok_or(SignatureError::AlgorithmNotSupported( + self.algorithm.clone(), + ))? + } +} + +impl Signature { + pub fn verify(&self, data: &[u8]) -> Result { + self.identity.verify(data, &self.signature) + } +} From 89aac4aae57893c8613049dc6f66d1427e99eb19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AsPulse=20/=20=E3=81=82=E3=81=99=E3=81=B1=E3=82=8B?= Date: Mon, 13 Jan 2025 20:46:35 +0900 Subject: [PATCH 07/13] Add Tests for Algorithms --- Cargo.lock | 71 +++++++++++++++++++ sutera-lib/Cargo.toml | 5 +- .../src/signature/algorithms/ed25519.rs | 20 +++++- sutera-lib/src/signature/algorithms/macros.rs | 26 +++++++ sutera-lib/src/signature/algorithms/mod.rs | 21 +++--- sutera-lib/src/signature/algorithms/tests.rs | 55 ++++++++++++++ 6 files changed, 183 insertions(+), 15 deletions(-) create mode 100644 sutera-lib/src/signature/algorithms/macros.rs create mode 100644 sutera-lib/src/signature/algorithms/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 57bcf58..7dc4267 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,6 +65,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.6.0" @@ -83,6 +89,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "concat-idents" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f76990911f2267d837d9d0ad060aa63aaad170af40904b29461734c339030d4d" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -179,6 +195,7 @@ checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ "curve25519-dalek", "ed25519", + "rand_core", "serde", "sha2", "subtle", @@ -372,6 +389,15 @@ dependencies = [ "spki", ] +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + [[package]] name = "pretty_assertions" version = "1.4.0" @@ -400,6 +426,27 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + [[package]] name = "rand_core" version = "0.6.4" @@ -575,8 +622,11 @@ name = "sutera-lib" version = "0.1.0" dependencies = [ "assert_matches", + "concat-idents", "ed25519-dalek", "pretty_assertions", + "rand", + "rand_core", "serde", "serde_json_canonicalizer", "thiserror", @@ -920,6 +970,27 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zeroize" version = "1.8.1" diff --git a/sutera-lib/Cargo.toml b/sutera-lib/Cargo.toml index fd28785..58a02d0 100644 --- a/sutera-lib/Cargo.toml +++ b/sutera-lib/Cargo.toml @@ -5,11 +5,14 @@ edition = "2021" [dev-dependencies] assert_matches = "1.5.0" +concat-idents = "1.1.5" pretty_assertions = "1.4.0" tracing-subscriber = "0.3.19" [dependencies] -ed25519-dalek = { version = "2.1.1" } +ed25519-dalek = { version = "2.1.1", features = ["rand_core"] } +rand = { version = "0.8.4", features = ["std_rng"] } +rand_core = "0.6.4" serde = { version = "1.0.203", features = ["derive"] } serde_json_canonicalizer = "0.3.0" thiserror = "1.0.61" diff --git a/sutera-lib/src/signature/algorithms/ed25519.rs b/sutera-lib/src/signature/algorithms/ed25519.rs index 0d69d84..02af35f 100644 --- a/sutera-lib/src/signature/algorithms/ed25519.rs +++ b/sutera-lib/src/signature/algorithms/ed25519.rs @@ -1,11 +1,12 @@ use std::str::FromStr; use crate::error::ResultCaptureErrExt; -use crate::util::hex::{from_hex, FromHexError}; +use crate::util::hex::{from_hex, to_hex, FromHexError}; use super::{SigningAlgorithm, SigningAlgorithmKind}; use ed25519_dalek::ed25519::signature::SignerMut; use ed25519_dalek::{Signature, SigningKey, VerifyingKey, PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH}; +use rand_core::CryptoRngCore; use thiserror::Error; use tracing::instrument; @@ -54,14 +55,27 @@ impl SigningAlgorithm for Ed25519 { kind.0 == "ed25519" } - #[instrument("sign", skip(data, private_key))] + #[instrument("sign", skip_all)] fn sign(data: &[u8], private_key: &str) -> Result { let mut private_key = parse_signing_key(private_key) .map_err(|e| super::SignatureError::PrivateKey(Box::new(e)))?; Ok(private_key.try_sign(data).capture_and_unwrap().to_string()) } - #[instrument("verify", skip(data, signature, public_key))] + #[instrument("to_public_key", skip_all)] + fn to_public_key(private_key: &str) -> Result { + let private_key = parse_signing_key(private_key) + .map_err(|e| super::SignatureError::PrivateKey(Box::new(e)))?; + Ok(to_hex(&private_key.verifying_key().to_bytes())) + } + + #[instrument("generate_private_key", skip_all)] + fn generate_private_key(rng: &mut R) -> String { + let private_key = SigningKey::generate(rng); + to_hex(&private_key.to_bytes()) + } + + #[instrument("verify", skip_all)] fn verify( data: &[u8], signature: &str, diff --git a/sutera-lib/src/signature/algorithms/macros.rs b/sutera-lib/src/signature/algorithms/macros.rs new file mode 100644 index 0000000..1654f7e --- /dev/null +++ b/sutera-lib/src/signature/algorithms/macros.rs @@ -0,0 +1,26 @@ +macro_rules! algorithm_action { + ($kind:expr => $e:expr) => { + if $crate::signature::algorithms::ed25519::Ed25519::is_capable($kind) { + type Algorithm = $crate::signature::algorithms::ed25519::Ed25519; + Some($e) + } else { + None + } + }; +} +pub(crate) use algorithm_action; + +#[cfg(test)] +macro_rules! algorithm_tests { + ($(#[$attr:meta])* fn $name:ident $args:tt $b:block) => { + ::concat_idents::concat_idents!(test_name = $name, _ed25519 { + $(#[$attr])* + fn test_name $args { + type Algorithm = $crate::signature::algorithms::ed25519::Ed25519; + $b + } + }); + }; +} +#[cfg(test)] +pub(crate) use algorithm_tests; diff --git a/sutera-lib/src/signature/algorithms/mod.rs b/sutera-lib/src/signature/algorithms/mod.rs index c7dd076..3b81cf1 100644 --- a/sutera-lib/src/signature/algorithms/mod.rs +++ b/sutera-lib/src/signature/algorithms/mod.rs @@ -1,8 +1,16 @@ pub mod ed25519; +mod macros; +#[cfg(test)] +mod tests; + +use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; use thiserror::Error; +use self::ed25519::Ed25519; +use self::macros::algorithm_action; + use super::{Signature, SuteraIdentity}; #[derive(Error, Debug)] @@ -24,18 +32,9 @@ pub struct SigningAlgorithmKind(String); trait SigningAlgorithm { fn is_capable(kind: &SigningAlgorithmKind) -> bool; fn sign(data: &[u8], private_key: &str) -> Result; + fn to_public_key(private_key: &str) -> Result; fn verify(data: &[u8], signature: &str, public_key: &str) -> Result; -} - -macro_rules! algorithm_action { - ($kind:expr => $e:expr) => { - if $crate::signature::algorithms::ed25519::Ed25519::is_capable($kind) { - type Algorithm = $crate::signature::algorithms::ed25519::Ed25519; - Some($e) - } else { - None - } - }; + fn generate_private_key(rng: &mut R) -> String; } impl SuteraIdentity { diff --git a/sutera-lib/src/signature/algorithms/tests.rs b/sutera-lib/src/signature/algorithms/tests.rs new file mode 100644 index 0000000..803a1e6 --- /dev/null +++ b/sutera-lib/src/signature/algorithms/tests.rs @@ -0,0 +1,55 @@ +use crate::signature::algorithms::SigningAlgorithm as _; + +use super::macros::algorithm_tests; +use assert_matches::assert_matches; +use rand::rngs::StdRng; +use rand::SeedableRng; + +algorithm_tests! { + #[test] + fn verify_correct_sign() { + let mut rng = StdRng::seed_from_u64(0); + let private_key = Algorithm::generate_private_key(&mut rng); + let public_key = Algorithm::to_public_key(&private_key).unwrap(); + let data = b"hello, world!"; + let signature = Algorithm::sign(data, &private_key).unwrap(); + assert_matches!(Algorithm::verify(data, &signature, &public_key), Ok(true)); + } +} + +algorithm_tests! { + #[test] + fn verify_unmatched_content() { + let mut rng = StdRng::seed_from_u64(0); + let private_key = Algorithm::generate_private_key(&mut rng); + let public_key = Algorithm::to_public_key(&private_key).unwrap(); + let data = b"hello, world!"; + let signature = Algorithm::sign(data, &private_key).unwrap(); + assert_matches!(Algorithm::verify(b"broken content", &signature, &public_key), Ok(false)); + } +} + +algorithm_tests! { + #[test] + fn verify_unmatched_signature() { + let mut rng = StdRng::seed_from_u64(0); + let private_key = Algorithm::generate_private_key(&mut rng); + let public_key = Algorithm::to_public_key(&private_key).unwrap(); + let data = b"hello, world!"; + let fake_data = b"original!"; + let signature = Algorithm::sign(fake_data, &private_key).unwrap(); + assert_matches!(Algorithm::verify(data, &signature, &public_key), Ok(false)); + } +} + +algorithm_tests! { + #[test] + fn verify_unmatched_public_key() { + let mut rng = StdRng::seed_from_u64(0); + let private_key = Algorithm::generate_private_key(&mut rng); + let data = b"hello, world!"; + let signature = Algorithm::sign(data, &private_key).unwrap(); + let fake_public_key = Algorithm::to_public_key(&Algorithm::generate_private_key(&mut rng)).unwrap(); + assert_matches!(Algorithm::verify(data, &signature, &fake_public_key), Ok(false)); + } +} From 4450973167d14546d960468356b9407b85f0889b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AsPulse=20/=20=E3=81=82=E3=81=99=E3=81=B1=E3=82=8B?= Date: Mon, 13 Jan 2025 23:52:36 +0900 Subject: [PATCH 08/13] Add PrivateKeyMasked --- sutera-lib/src/signature/mod.rs | 4 ++ .../src/signature/private_key_masked.rs | 64 +++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 sutera-lib/src/signature/private_key_masked.rs diff --git a/sutera-lib/src/signature/mod.rs b/sutera-lib/src/signature/mod.rs index d29cda6..7f4590d 100644 --- a/sutera-lib/src/signature/mod.rs +++ b/sutera-lib/src/signature/mod.rs @@ -1,4 +1,5 @@ pub mod algorithms; +pub mod private_key_masked; use self::algorithms::SigningAlgorithmKind; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; @@ -22,3 +23,6 @@ struct SuteraIdentity { algorithm: SigningAlgorithmKind, public_key: String, } + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +struct SuteraPrivateKey {} diff --git a/sutera-lib/src/signature/private_key_masked.rs b/sutera-lib/src/signature/private_key_masked.rs new file mode 100644 index 0000000..fdf6549 --- /dev/null +++ b/sutera-lib/src/signature/private_key_masked.rs @@ -0,0 +1,64 @@ +use std::any::type_name; +use std::cmp::Ordering; +use std::fmt::Debug; +use std::fmt::{self, Display}; + +struct PrivateKeyMasked(T); + +impl Debug for PrivateKeyMasked { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "(Masked, containing private key information. <{}>)", + type_name::() + ) + } +} + +impl Display for PrivateKeyMasked { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "(Masked, containing private key information. <{}>)", + type_name::() + ) + } +} + +impl From> for String { + fn from(val: PrivateKeyMasked) -> Self { + val.0 + } +} + +impl From for PrivateKeyMasked { + fn from(t: T) -> Self { + Self(t) + } +} + +impl Clone for PrivateKeyMasked { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl PartialEq for PrivateKeyMasked { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl Eq for PrivateKeyMasked {} + +impl PartialOrd for PrivateKeyMasked { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl Ord for PrivateKeyMasked { + fn cmp(&self, other: &Self) -> Ordering { + self.0.cmp(&other.0) + } +} From 7390da5dc3cb6e457bea55c7e03bca98bf45f980 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AsPulse=20/=20=E3=81=82=E3=81=99=E3=81=B1=E3=82=8B?= Date: Tue, 14 Jan 2025 17:40:30 +0900 Subject: [PATCH 09/13] Add Tests and get, get_mut for PrivateKeyMasked --- .../src/signature/private_key_masked.rs | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/sutera-lib/src/signature/private_key_masked.rs b/sutera-lib/src/signature/private_key_masked.rs index fdf6549..79a55e2 100644 --- a/sutera-lib/src/signature/private_key_masked.rs +++ b/sutera-lib/src/signature/private_key_masked.rs @@ -5,6 +5,26 @@ use std::fmt::{self, Display}; struct PrivateKeyMasked(T); +impl PrivateKeyMasked { + /// Get the inner value. + /// + /// Do not use this for error messages. + /// **Do not use this in argument or return type to any function.** + /// (tracing-subscriber might expose the inner value) + pub fn get_raw(&self) -> &T { + &self.0 + } + + /// Get the mutable reference to the inner value. + /// + /// Do not use this for error messages. + /// **Do not use this in argument or return type to any function.** + /// (tracing-subscriber might expose the inner value) + pub fn get_raw_mut(&mut self) -> &mut T { + &mut self.0 + } +} + impl Debug for PrivateKeyMasked { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( @@ -62,3 +82,45 @@ impl Ord for PrivateKeyMasked { self.0.cmp(&other.0) } } + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn debug_print_should_masked_string() { + let debug_output = format!("{:?}", PrivateKeyMasked("SUPERSECRET".to_string())); + assert!(!debug_output.contains("SUPERSECRET")); + assert!(debug_output.contains("String")); + assert!(debug_output.contains("Masked, containing private key information.")); + } + + #[test] + fn debug_print_should_masked_u8slice() { + let hex: [u8; 4] = [0x01, 0x02, 0x03, 0x04]; + let key: &PrivateKeyMasked<&[u8]> = &PrivateKeyMasked(&hex); + assert_eq!( + format!("{:?}", key), + "(Masked, containing private key information. <&[u8]>)" + ); + } + + #[test] + fn display_print_should_masked_string() { + let display_output = format!("{}", PrivateKeyMasked("SUPERSECRET".to_string())); + assert!(!display_output.contains("SUPERSECRET")); + assert!(display_output.contains("String")); + assert!(display_output.contains("Masked, containing private key information.")); + } + + #[test] + fn display_print_should_masked_u8slice() { + let hex: [u8; 4] = [0x01, 0x02, 0x03, 0x04]; + let key: &PrivateKeyMasked<&[u8]> = &PrivateKeyMasked(&hex); + assert_eq!( + format!("{}", key), + "(Masked, containing private key information. <&[u8]>)" + ); + } +} From ae776385580d829eca8a4afb3604e32bd0252e9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AsPulse=20/=20=E3=81=82=E3=81=99=E3=81=B1=E3=82=8B?= Date: Tue, 14 Jan 2025 17:43:01 +0900 Subject: [PATCH 10/13] Remove //TODO: for done tasks --- sutera-lib/src/signature/algorithms/ed25519.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/sutera-lib/src/signature/algorithms/ed25519.rs b/sutera-lib/src/signature/algorithms/ed25519.rs index 02af35f..96c8a53 100644 --- a/sutera-lib/src/signature/algorithms/ed25519.rs +++ b/sutera-lib/src/signature/algorithms/ed25519.rs @@ -88,6 +88,3 @@ impl SigningAlgorithm for Ed25519 { Ok(public_key.verify_strict(data, &signature).is_ok()) } } - -// TODO: テストを書く -// TODO: `generate_private`, `to_public` を実装する From da41a3ea5b56e822cea9272d7cdb80c321b6d220 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AsPulse=20/=20=E3=81=82=E3=81=99=E3=81=B1=E3=82=8B?= Date: Tue, 14 Jan 2025 18:32:13 +0900 Subject: [PATCH 11/13] Fix Around Hex --- sutera-lib/src/signature/algorithms/mod.rs | 1 - .../src/signature/private_key_masked.rs | 67 ++++++------------- sutera-lib/src/util/hex.rs | 55 +++++++++++++-- 3 files changed, 73 insertions(+), 50 deletions(-) diff --git a/sutera-lib/src/signature/algorithms/mod.rs b/sutera-lib/src/signature/algorithms/mod.rs index 3b81cf1..0f5bb65 100644 --- a/sutera-lib/src/signature/algorithms/mod.rs +++ b/sutera-lib/src/signature/algorithms/mod.rs @@ -8,7 +8,6 @@ use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; use thiserror::Error; -use self::ed25519::Ed25519; use self::macros::algorithm_action; use super::{Signature, SuteraIdentity}; diff --git a/sutera-lib/src/signature/private_key_masked.rs b/sutera-lib/src/signature/private_key_masked.rs index 79a55e2..9a146c6 100644 --- a/sutera-lib/src/signature/private_key_masked.rs +++ b/sutera-lib/src/signature/private_key_masked.rs @@ -1,11 +1,10 @@ -use std::any::type_name; use std::cmp::Ordering; use std::fmt::Debug; use std::fmt::{self, Display}; -struct PrivateKeyMasked(T); +pub struct PrivateKeyMasked(T); -impl PrivateKeyMasked { +impl PrivateKeyMasked { /// Get the inner value. /// /// Do not use this for error messages. @@ -25,29 +24,27 @@ impl PrivateKeyMasked { } } -impl Debug for PrivateKeyMasked { +impl PrivateKeyMasked {} + +impl Debug for PrivateKeyMasked { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, - "(Masked, containing private key information. <{}>)", - type_name::() - ) + "(!WARNING[SECRET-IN-LOG] Following angle bracket contains information from which a private key may be derived. Please be sure to mask this log when sharing it with others! <" + )?; + ::fmt(&self.0, f)?; + write!(f, ">)") } } -impl Display for PrivateKeyMasked { +impl Display for PrivateKeyMasked { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, - "(Masked, containing private key information. <{}>)", - type_name::() - ) - } -} - -impl From> for String { - fn from(val: PrivateKeyMasked) -> Self { - val.0 + "(!WARNING[SECRET-IN-LOG] Following angle bracket contains information from which a private key may be derived. Please be sure to mask this log when sharing it with others! <", + )?; + ::fmt(&self.0, f)?; + write!(f, ">)") } } @@ -63,21 +60,21 @@ impl Clone for PrivateKeyMasked { } } -impl PartialEq for PrivateKeyMasked { +impl PartialEq for PrivateKeyMasked { fn eq(&self, other: &Self) -> bool { self.0 == other.0 } } -impl Eq for PrivateKeyMasked {} +impl Eq for PrivateKeyMasked {} -impl PartialOrd for PrivateKeyMasked { +impl PartialOrd for PrivateKeyMasked { fn partial_cmp(&self, other: &Self) -> Option { self.0.partial_cmp(&other.0) } } -impl Ord for PrivateKeyMasked { +impl Ord for PrivateKeyMasked { fn cmp(&self, other: &Self) -> Ordering { self.0.cmp(&other.0) } @@ -90,37 +87,17 @@ mod tests { #[test] fn debug_print_should_masked_string() { - let debug_output = format!("{:?}", PrivateKeyMasked("SUPERSECRET".to_string())); - assert!(!debug_output.contains("SUPERSECRET")); - assert!(debug_output.contains("String")); - assert!(debug_output.contains("Masked, containing private key information.")); - } - - #[test] - fn debug_print_should_masked_u8slice() { - let hex: [u8; 4] = [0x01, 0x02, 0x03, 0x04]; - let key: &PrivateKeyMasked<&[u8]> = &PrivateKeyMasked(&hex); assert_eq!( - format!("{:?}", key), - "(Masked, containing private key information. <&[u8]>)" + format!("{:?}", PrivateKeyMasked("SUPERSECRET")), + "(!WARNING[SECRET-IN-LOG] Following angle bracket contains information from which a private key may be derived. Please be sure to mask this log when sharing it with others! <\"SUPERSECRET\">)" ); } #[test] fn display_print_should_masked_string() { - let display_output = format!("{}", PrivateKeyMasked("SUPERSECRET".to_string())); - assert!(!display_output.contains("SUPERSECRET")); - assert!(display_output.contains("String")); - assert!(display_output.contains("Masked, containing private key information.")); - } - - #[test] - fn display_print_should_masked_u8slice() { - let hex: [u8; 4] = [0x01, 0x02, 0x03, 0x04]; - let key: &PrivateKeyMasked<&[u8]> = &PrivateKeyMasked(&hex); assert_eq!( - format!("{}", key), - "(Masked, containing private key information. <&[u8]>)" + format!("{}", PrivateKeyMasked("SUPERSECRET")), + "(!WARNING[SECRET-IN-LOG] Following angle bracket contains information from which a private key may be derived. Please be sure to mask this log when sharing it with others! )" ); } } diff --git a/sutera-lib/src/util/hex.rs b/sutera-lib/src/util/hex.rs index fd3446f..444046b 100644 --- a/sutera-lib/src/util/hex.rs +++ b/sutera-lib/src/util/hex.rs @@ -1,3 +1,4 @@ +use std::fmt::Debug; use std::fmt::Write; use std::str::Utf8Error; use tracing_error::SpanTrace; @@ -6,10 +7,55 @@ use thiserror::Error; use tracing::instrument; use crate::error::{CapturedError, ResultCaptureErrExt, TraceableError}; +use crate::signature::private_key_masked::PrivateKeyMasked; + +trait HexString: Debug { + fn content(&self) -> &str; +} + +impl HexString for str { + fn content(&self) -> &str { + self + } +} + +impl HexString for String { + fn content(&self) -> &str { + self + } +} + +impl HexString for PrivateKeyMasked { + fn content(&self) -> &str { + self.get_raw().content() + } +} + +trait Binary: Debug { + fn content(&self) -> &[u8]; +} + +impl Binary for [u8; N] { + fn content(&self) -> &[u8] { + self + } +} + +impl Binary for Vec { + fn content(&self) -> &[u8] { + self + } +} + +impl Binary for PrivateKeyMasked { + fn content(&self) -> &[u8] { + self.get_raw().content() + } +} #[instrument("to_hex")] -pub(crate) fn to_hex(data: &[u8]) -> String { - data.iter().fold(String::new(), |mut acc, byte| { +pub(crate) fn to_hex(data: &impl Binary) -> String { + data.content().iter().fold(String::new(), |mut acc, byte| { write!(acc, "{byte:02x}").unwrap(); acc }) @@ -34,8 +80,9 @@ impl TraceableError for FromHexError { } #[instrument("from_hex")] -pub(crate) fn from_hex(data: &str) -> Result, FromHexError> { - data.as_bytes() +pub(crate) fn from_hex(data: &H) -> Result, FromHexError> { + data.content() + .as_bytes() .chunks(2) .map(|chunk| { Ok(u8::from_str_radix(std::str::from_utf8(chunk).capture_err()?, 16).capture_err()?) From 22949c602da1bbaf613684d27719b2d7c89358e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AsPulse=20/=20=E3=81=82=E3=81=99=E3=81=B1=E3=82=8B?= Date: Tue, 14 Jan 2025 19:50:35 +0900 Subject: [PATCH 12/13] Japanese Warning --- .../src/signature/private_key_masked.rs | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/sutera-lib/src/signature/private_key_masked.rs b/sutera-lib/src/signature/private_key_masked.rs index 9a146c6..d316d5d 100644 --- a/sutera-lib/src/signature/private_key_masked.rs +++ b/sutera-lib/src/signature/private_key_masked.rs @@ -26,25 +26,22 @@ impl PrivateKeyMasked { impl PrivateKeyMasked {} +const SECRET_IN_LOG_WARNING_LEFT: &str = "(!WARNING[SECRET-IN-LOG] Following angle bracket contains information from which a private key may be derived. Please be sure to mask this log when sharing it with others! / 警告! 続く山括弧の中には, あなたの秘密鍵を推測する材料となり得る情報が含まれます。 このログを他者と共有する際には, 該当部分を必ず隠してください。<"; +const SECRET_IN_LOG_WARNING_RIGHT: &str = ">)"; + impl Debug for PrivateKeyMasked { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "(!WARNING[SECRET-IN-LOG] Following angle bracket contains information from which a private key may be derived. Please be sure to mask this log when sharing it with others! <" - )?; + f.write_str(SECRET_IN_LOG_WARNING_LEFT)?; ::fmt(&self.0, f)?; - write!(f, ">)") + f.write_str(SECRET_IN_LOG_WARNING_RIGHT) } } impl Display for PrivateKeyMasked { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "(!WARNING[SECRET-IN-LOG] Following angle bracket contains information from which a private key may be derived. Please be sure to mask this log when sharing it with others! <", - )?; + f.write_str(SECRET_IN_LOG_WARNING_LEFT)?; ::fmt(&self.0, f)?; - write!(f, ">)") + f.write_str(SECRET_IN_LOG_WARNING_RIGHT) } } @@ -89,7 +86,10 @@ mod tests { fn debug_print_should_masked_string() { assert_eq!( format!("{:?}", PrivateKeyMasked("SUPERSECRET")), - "(!WARNING[SECRET-IN-LOG] Following angle bracket contains information from which a private key may be derived. Please be sure to mask this log when sharing it with others! <\"SUPERSECRET\">)" + format!( + "{}{}{}", + SECRET_IN_LOG_WARNING_LEFT, "\"SUPERSECRET\"", SECRET_IN_LOG_WARNING_RIGHT + ) ); } @@ -97,7 +97,10 @@ mod tests { fn display_print_should_masked_string() { assert_eq!( format!("{}", PrivateKeyMasked("SUPERSECRET")), - "(!WARNING[SECRET-IN-LOG] Following angle bracket contains information from which a private key may be derived. Please be sure to mask this log when sharing it with others! )" + format!( + "{}{}{}", + SECRET_IN_LOG_WARNING_LEFT, "SUPERSECRET", SECRET_IN_LOG_WARNING_RIGHT + ) ); } } From b31aa7225861556519c228a27e9e97e75a82cebe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AsPulse=20/=20=E3=81=82=E3=81=99=E3=81=B1=E3=82=8B?= Date: Tue, 14 Jan 2025 20:16:16 +0900 Subject: [PATCH 13/13] Implement SuteraPrivateKey --- .../src/signature/algorithms/ed25519.rs | 20 +++++++++++-------- sutera-lib/src/signature/algorithms/mod.rs | 10 +++++----- sutera-lib/src/signature/mod.rs | 14 ++++++++----- sutera-lib/src/util/hex.rs | 2 +- 4 files changed, 27 insertions(+), 19 deletions(-) diff --git a/sutera-lib/src/signature/algorithms/ed25519.rs b/sutera-lib/src/signature/algorithms/ed25519.rs index 96c8a53..8dbd475 100644 --- a/sutera-lib/src/signature/algorithms/ed25519.rs +++ b/sutera-lib/src/signature/algorithms/ed25519.rs @@ -1,7 +1,8 @@ use std::str::FromStr; use crate::error::ResultCaptureErrExt; -use crate::util::hex::{from_hex, to_hex, FromHexError}; +use crate::signature::SuteraPrivateKey; +use crate::util::hex::{from_hex, to_hex, FromHexError, HexString}; use super::{SigningAlgorithm, SigningAlgorithmKind}; use ed25519_dalek::ed25519::signature::SignerMut; @@ -37,7 +38,7 @@ fn parse_verifying_key(value: &str) -> Result { } #[instrument("parse_signing_key")] -fn parse_signing_key(value: &str) -> Result { +fn parse_signing_key(value: &T) -> Result { let bytes = from_hex(value)?; if bytes.len() != SECRET_KEY_LENGTH { return Err(DecodeKeyError::KeyLengthMismatch { @@ -56,23 +57,26 @@ impl SigningAlgorithm for Ed25519 { } #[instrument("sign", skip_all)] - fn sign(data: &[u8], private_key: &str) -> Result { - let mut private_key = parse_signing_key(private_key) + fn sign(data: &[u8], private_key: &SuteraPrivateKey) -> Result { + let mut private_key = parse_signing_key(&private_key.key) .map_err(|e| super::SignatureError::PrivateKey(Box::new(e)))?; Ok(private_key.try_sign(data).capture_and_unwrap().to_string()) } #[instrument("to_public_key", skip_all)] - fn to_public_key(private_key: &str) -> Result { - let private_key = parse_signing_key(private_key) + fn to_public_key(private_key: &SuteraPrivateKey) -> Result { + let private_key = parse_signing_key(&private_key.key) .map_err(|e| super::SignatureError::PrivateKey(Box::new(e)))?; Ok(to_hex(&private_key.verifying_key().to_bytes())) } #[instrument("generate_private_key", skip_all)] - fn generate_private_key(rng: &mut R) -> String { + fn generate_private_key(rng: &mut R) -> SuteraPrivateKey { let private_key = SigningKey::generate(rng); - to_hex(&private_key.to_bytes()) + SuteraPrivateKey { + key: to_hex(&private_key.to_bytes()).into(), + algorithm: SigningAlgorithmKind("ed25519".into()), + } } #[instrument("verify", skip_all)] diff --git a/sutera-lib/src/signature/algorithms/mod.rs b/sutera-lib/src/signature/algorithms/mod.rs index 0f5bb65..03f38cf 100644 --- a/sutera-lib/src/signature/algorithms/mod.rs +++ b/sutera-lib/src/signature/algorithms/mod.rs @@ -10,7 +10,7 @@ use thiserror::Error; use self::macros::algorithm_action; -use super::{Signature, SuteraIdentity}; +use super::{Signature, SuteraIdentity, SuteraPrivateKey}; #[derive(Error, Debug)] pub enum SignatureError { @@ -28,12 +28,12 @@ pub enum SignatureError { pub struct SigningAlgorithmKind(String); #[allow(dead_code)] -trait SigningAlgorithm { +pub trait SigningAlgorithm { fn is_capable(kind: &SigningAlgorithmKind) -> bool; - fn sign(data: &[u8], private_key: &str) -> Result; - fn to_public_key(private_key: &str) -> Result; + fn sign(data: &[u8], private_key: &SuteraPrivateKey) -> Result; + fn to_public_key(private_key: &SuteraPrivateKey) -> Result; fn verify(data: &[u8], signature: &str, public_key: &str) -> Result; - fn generate_private_key(rng: &mut R) -> String; + fn generate_private_key(rng: &mut R) -> SuteraPrivateKey; } impl SuteraIdentity { diff --git a/sutera-lib/src/signature/mod.rs b/sutera-lib/src/signature/mod.rs index 7f4590d..6801018 100644 --- a/sutera-lib/src/signature/mod.rs +++ b/sutera-lib/src/signature/mod.rs @@ -1,28 +1,32 @@ pub mod algorithms; pub mod private_key_masked; use self::algorithms::SigningAlgorithmKind; +use self::private_key_masked::PrivateKeyMasked; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Clone)] -struct Signed { +pub struct Signed { #[serde(bound(deserialize = "T: DeserializeOwned", serialize = "T: Serialize"))] payload: T, signature: Signature, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] -struct Signature { +pub struct Signature { identity: SuteraIdentity, signature: String, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] -struct SuteraIdentity { +pub struct SuteraIdentity { display_name: String, algorithm: SigningAlgorithmKind, public_key: String, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] -struct SuteraPrivateKey {} +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SuteraPrivateKey { + algorithm: SigningAlgorithmKind, + key: PrivateKeyMasked, +} diff --git a/sutera-lib/src/util/hex.rs b/sutera-lib/src/util/hex.rs index 444046b..605b88a 100644 --- a/sutera-lib/src/util/hex.rs +++ b/sutera-lib/src/util/hex.rs @@ -9,7 +9,7 @@ use tracing::instrument; use crate::error::{CapturedError, ResultCaptureErrExt, TraceableError}; use crate::signature::private_key_masked::PrivateKeyMasked; -trait HexString: Debug { +pub(crate) trait HexString: Debug { fn content(&self) -> &str; }