bitwarden_crypto/signing/mod.rs
1//! Signing is used to assert integrity of a message to others or to oneself.
2//!
3//! Signing and signature verification operations are divided into three layers here:
4//! - (public) High-level: Give a struct, namespace, and get a signed object or signature +
5//! serialized message. Purpose: Serialization should not be decided by the consumer of this
6//! interface, but rather by the signing implementation. Each consumer shouldn't have to make the
7//! decision on how to serialize. Further, the serialization format is written to the signature
8//! object, and verified.
9//!
10//! - Mid-level: Give a byte array, content format, namespace, and get a signed object or signature.
11//! Purpose: All signatures should be domain-separated, so that any proofs only need to consider
12//! the allowed messages under the current namespace, and cross-protocol attacks are not possible.
13//!
14//! - Low-level: Give a byte array, and get a signature. Purpose: This just implements the signing
15//! of byte arrays. Digital signature schemes generally just care about a set of input bytes to
16//! sign; and this operation implements that per-supported digital signature scheme. To add
17//! support for a new scheme, only this operation needs to be implemented for the new signing key
18//! type. This is implemented in the ['signing_key'] and ['verifying_key'] modules.
19//!
20//! Signing operations are split into two types. The mid-level and high-level operations are
21//! implemented for each type respectively.
22//! - Sign: Create a [`signed_object::SignedObject`] that contains the payload. Purpose: If only one
23//! signature is needed for an object then it is simpler to keep the signature and payload
24//! together in one blob, so they cannot be separated.
25//!
26//! - Sign detached: Create a [`signature::Signature`] that does not contain the payload; but the
27//! serialized payload is returned. Purpose: If multiple signatures are needed for one object,
28//! then sign detached can be used.
29
30mod cose;
31use cose::*;
32mod namespace;
33pub use namespace::SigningNamespace;
34mod signed_object;
35pub use signed_object::SignedObject;
36mod signature;
37pub use signature::Signature;
38mod signing_key;
39pub use signing_key::SigningKey;
40mod verifying_key;
41pub use verifying_key::VerifyingKey;
42mod message;
43pub use message::SerializedMessage;
44use schemars::JsonSchema;
45use serde::{Deserialize, Serialize};
46#[cfg(feature = "wasm")]
47use {tsify::Tsify, wasm_bindgen::prelude::*};
48
49/// The type of key / signature scheme used for signing and verifying.
50#[derive(Serialize, Deserialize, Debug, JsonSchema, PartialEq)]
51#[serde(rename_all = "camelCase", deny_unknown_fields)]
52#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
53#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
54pub enum SignatureAlgorithm {
55 /// Ed25519 is the modern, secure recommended option for digital signatures on eliptic curves,
56 /// safe under the assumption that an attacker does not have access to a large-scale quantum
57 /// computer.
58 Ed25519,
59 /// ML-DSA-44 is the NIST post-quantum digital signature standard (FIPS 204), security category
60 /// 2.
61 MlDsa44,
62}
63
64impl SignatureAlgorithm {
65 /// Returns the currently accepted safe algorithm for new keys.
66 pub fn default_algorithm() -> Self {
67 SignatureAlgorithm::MlDsa44
68 }
69}
70
71impl std::fmt::Display for SignatureAlgorithm {
72 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73 match self {
74 SignatureAlgorithm::Ed25519 => write!(f, "ed25519"),
75 SignatureAlgorithm::MlDsa44 => write!(f, "mldsa44"),
76 }
77 }
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83 use crate::CoseSerializable;
84
85 #[derive(Deserialize, Debug, PartialEq, Serialize)]
86 struct TestMessage {
87 field1: String,
88 }
89
90 /// The function used to create the test vectors below, and can be used to re-generate them.
91 /// Once rolled out to user accounts, this function can be removed, because at that point we
92 /// cannot introduce format-breaking changes anymore.
93 #[test]
94 fn make_test_vectors() {
95 let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
96 let verifying_key = signing_key.to_verifying_key();
97 let test_message = TestMessage {
98 field1: "Test message".to_string(),
99 };
100 let (signature, serialized_message) = signing_key
101 .sign_detached(&test_message, &SigningNamespace::ExampleNamespace)
102 .unwrap();
103 let signed_object = signing_key
104 .sign(&test_message, &SigningNamespace::ExampleNamespace)
105 .unwrap();
106 let raw_signed_array = signing_key.sign_raw("Test message".as_bytes());
107 println!("const SIGNING_KEY: &[u8] = &{:?};", signing_key.to_cose());
108 println!(
109 "const VERIFYING_KEY: &[u8] = &{:?};",
110 verifying_key.to_cose()
111 );
112 println!("const SIGNATURE: &[u8] = &{:?};", signature.to_cose());
113 println!(
114 "const SERIALIZED_MESSAGE: &[u8] = &{:?};",
115 serialized_message.as_bytes()
116 );
117 println!(
118 "const SIGNED_OBJECT: &[u8] = &{:?};",
119 signed_object.to_cose()
120 );
121 println!(
122 "const SIGNED_OBJECT_RAW: &[u8] = &{:?};",
123 raw_signed_array.as_slice()
124 );
125 }
126}