Skip to main content

matrix_sdk_crypto/types/signatures/
signature.rs

1/*
2Copyright 2022-2026 The Matrix.org Foundation C.I.C.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16use as_variant::as_variant;
17use ruma::DeviceKeyAlgorithm;
18use vodozemac::Ed25519Signature;
19
20use crate::types::InvalidSignature;
21#[cfg(feature = "experimental-x509-identity-verification")]
22use crate::types::{X509_SIGNATURE_ALGORITHM, X509Signature};
23
24/// Represents a potentially decoded signature (but *not* a validated one).
25///
26/// There are two important cases here:
27///
28/// 1. If the claimed algorithm is supported *and* the payload has an expected
29///    format, the signature will be represent by the enum variant corresponding
30///    to that algorithm. For example, decodable Ed25519 signatures are
31///    represented as `Ed25519(...)`.
32/// 2. If the claimed algorithm is unsupported, the signature is represented as
33///    `Other(...)`.
34#[derive(Clone, Debug, PartialEq, Eq)]
35pub enum Signature {
36    /// A Ed25519 digital signature.
37    Ed25519(Ed25519Signature),
38    /// An X.509 digital signature.
39    #[cfg(feature = "experimental-x509-identity-verification")]
40    X509(X509Signature),
41    /// A digital signature in an unsupported algorithm. The raw signature bytes
42    /// are represented as a base64-encoded string.
43    Other(String),
44}
45
46impl Signature {
47    /// Get the Ed25519 signature, if this is one.
48    pub fn ed25519(&self) -> Option<Ed25519Signature> {
49        as_variant!(self, Self::Ed25519).copied()
50    }
51
52    /// Decode a signature from the Base64-encoded format used in Matrix
53    /// `signatures` maps.
54    pub fn from_base64(algorithm: DeviceKeyAlgorithm, s: String) -> Result<Self, InvalidSignature> {
55        match algorithm {
56            DeviceKeyAlgorithm::Ed25519 => Ed25519Signature::from_base64(&s)
57                .map(|s| s.into())
58                .map_err(|_| InvalidSignature { source: s }),
59
60            #[cfg(feature = "experimental-x509-identity-verification")]
61            custom if custom.as_str() == X509_SIGNATURE_ALGORITHM => {
62                X509Signature::from_cms_pem(&s)
63                    .map(Into::into)
64                    .map_err(|_| InvalidSignature { source: s })
65            }
66
67            _ => Ok(Signature::Other(s)),
68        }
69    }
70
71    /// Convert the signature to a base64 encoded string.
72    pub fn to_base64(&self) -> String {
73        match self {
74            Signature::Ed25519(s) => s.to_base64(),
75            #[cfg(feature = "experimental-x509-identity-verification")]
76            Signature::X509(s) => s.to_cms_pem(),
77            Signature::Other(s) => s.to_owned(),
78        }
79    }
80}
81
82impl From<Ed25519Signature> for Signature {
83    fn from(signature: Ed25519Signature) -> Self {
84        Self::Ed25519(signature)
85    }
86}
87
88#[cfg(feature = "experimental-x509-identity-verification")]
89impl From<X509Signature> for Signature {
90    fn from(signature: X509Signature) -> Self {
91        Self::X509(signature)
92    }
93}
94
95#[cfg(test)]
96mod test {
97    use super::*;
98
99    #[test]
100    fn roundtrip_ed25519_signature() {
101        const BASE64_SIG: &str = "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQ";
102
103        let parsed = Signature::from_base64(DeviceKeyAlgorithm::Ed25519, BASE64_SIG.to_owned())
104            .expect("Failed to parse");
105
106        let ed25519 = parsed.ed25519().expect("Not parsed as Ed25519");
107        assert_eq!(ed25519.to_base64(), BASE64_SIG);
108
109        let encoded = parsed.to_base64();
110        assert_eq!(encoded, BASE64_SIG)
111    }
112
113    #[test]
114    fn parse_invalid_ed25519_signature() {
115        const BASE64_SIG: &str = "XXXX";
116
117        let parsed = Signature::from_base64(DeviceKeyAlgorithm::Ed25519, BASE64_SIG.to_owned())
118            .expect_err("Expected an invalid signature");
119        assert_eq!(parsed.source, BASE64_SIG);
120    }
121
122    #[test]
123    fn roundtrip_other_signature() {
124        const TEXT: &str = "abcd";
125
126        let parsed = Signature::from_base64(DeviceKeyAlgorithm::from("foo"), TEXT.to_owned())
127            .expect("Failed to parse");
128
129        let other = as_variant!(&parsed, Signature::Other).expect("Not parsed as Other");
130        assert_eq!(other, TEXT);
131
132        let encoded = parsed.to_base64();
133        assert_eq!(encoded, TEXT)
134    }
135}