Skip to main content

rustical_dav_push/
vapid.rs

1use base64ct::{Base64UrlUnpadded, Encoding};
2use rustical_xml::XmlSerialize;
3use serde::Deserialize;
4use thiserror::Error;
5use web_push::VapidKey;
6
7#[derive(Debug, Error)]
8pub enum VapidError {
9    #[error(transparent)]
10    WebPushError(#[from] web_push::WebPushError),
11    #[error(transparent)]
12    FromUtf8Error(#[from] std::string::FromUtf8Error),
13}
14
15#[derive(Clone)]
16pub struct VapidKeypair(pub VapidKey);
17
18impl std::fmt::Debug for VapidKeypair {
19    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20        f.debug_struct("VapidPublicKeyB64").finish_non_exhaustive()
21    }
22}
23
24impl VapidKeypair {
25    #[must_use]
26    pub fn generate_p256() -> Self {
27        Self(VapidKey::generate())
28    }
29    #[must_use]
30    pub fn public(&self) -> VapidPublicKey {
31        VapidPublicKey(self.0.public_key())
32    }
33
34    pub fn from_pem(pem: &str) -> Result<Self, VapidError> {
35        Ok(Self(VapidKey::from_pem(pem)?))
36    }
37
38    pub fn to_pem(&self) -> Result<String, VapidError> {
39        Ok(self.0.to_pem()?)
40    }
41}
42
43#[derive(Clone, Deserialize, PartialEq, Eq)]
44pub struct VapidPublicKeyB64(pub String);
45
46impl std::fmt::Debug for VapidPublicKeyB64 {
47    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48        f.debug_struct("VapidPublicKeyB64").finish_non_exhaustive()
49    }
50}
51
52#[derive(Debug, Clone, XmlSerialize, PartialEq, Eq)]
53struct VapidPublicKeyProp<'b> {
54    #[xml(ty = "attr", rename = "type")]
55    pub ty: &'static str,
56    #[xml(ty = "text")]
57    pub key: &'b str,
58}
59
60impl XmlSerialize for VapidPublicKeyB64 {
61    fn serialize(
62        &self,
63        ns: Option<quick_xml::name::Namespace>,
64        tag: Option<&str>,
65        namespaces: &std::collections::HashMap<quick_xml::name::Namespace, &str>,
66        writer: &mut quick_xml::Writer<&mut Vec<u8>>,
67    ) -> std::io::Result<()> {
68        VapidPublicKeyProp {
69            ty: "p256ecdsa",
70            key: &self.0,
71        }
72        .serialize(ns, tag, namespaces, writer)
73    }
74
75    fn attributes<'a>(&self) -> Option<Vec<quick_xml::events::attributes::Attribute<'a>>> {
76        VapidPublicKeyProp {
77            ty: "p256ecdsa",
78            key: &self.0,
79        }
80        .attributes()
81    }
82}
83
84pub struct VapidPublicKey(Vec<u8>);
85
86impl std::fmt::Debug for VapidPublicKey {
87    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88        f.debug_struct("VapidPublicKey").finish_non_exhaustive()
89    }
90}
91
92impl VapidPublicKey {
93    #[must_use]
94    pub fn encode_b64(&self) -> VapidPublicKeyB64 {
95        VapidPublicKeyB64(Base64UrlUnpadded::encode_string(&self.0))
96    }
97}
98
99#[cfg(test)]
100pub mod tests {
101    use crate::vapid::VapidKeypair;
102
103    // pkcs8-encoded private key
104    pub const PRIVATE_KEY_PEM: &str = "-----BEGIN PRIVATE KEY-----
105MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgTB3vavSTXm+w9v6Q
1069eCwVFwRhnLfWuO3y2VwpfKhYg+hRANCAARRJ6EbENwBqqjN6v+2jxsalRvqEKUr
1073oBLcSuhKaTh5UrFE9kQUxWnmft0yL35yRmHHSpSyD3A4pqUi0satvIp
108-----END PRIVATE KEY-----
109";
110
111    pub const PRIVATE_KEY_PEM_SEC1: &str = "-----BEGIN EC PRIVATE KEY-----
112MHcCAQEEIMwug/U2ds75hkEIeou9s0kj1ziCJETswt5S9ztJ2L5SoAoGCCqGSM49
113AwEHoUQDQgAEyjUeooXqyQxljKSu17126pjAEPTyYNApO6dGQl0PexMn0T7LI3qw
114mU9ZOko2Gn7LYp5LqgA0cX6rfDftsKVvtQ==
115-----END EC PRIVATE KEY-----";
116
117    pub const PUBLIC_KEY_B64: &str =
118        "BFEnoRsQ3AGqqM3q_7aPGxqVG-oQpSvegEtxK6EppOHlSsUT2RBTFaeZ-3TIvfnJGYcdKlLIPcDimpSLSxq28ik";
119
120    #[test]
121    fn test_generate_key() {
122        let key = VapidKeypair::generate_p256();
123        let pem = key.to_pem().unwrap();
124        assert!(pem.starts_with("-----BEGIN PRIVATE KEY-----\n"));
125        assert!(pem.ends_with("-----END PRIVATE KEY-----\n"));
126    }
127
128    #[test]
129    fn test_pem_roundtrip() {
130        let key = VapidKeypair::from_pem(PRIVATE_KEY_PEM).unwrap();
131        assert_eq!(key.to_pem().unwrap(), PRIVATE_KEY_PEM);
132    }
133
134    #[test]
135    fn test_public_key() {
136        let key = VapidKeypair::from_pem(PRIVATE_KEY_PEM).unwrap();
137        assert_eq!(key.public().encode_b64().0, PUBLIC_KEY_B64);
138    }
139
140    #[test]
141    fn test_parse_pkcs8() {
142        VapidKeypair::from_pem(PRIVATE_KEY_PEM).unwrap();
143    }
144
145    #[test]
146    fn test_parse_sec1() {
147        VapidKeypair::from_pem(PRIVATE_KEY_PEM_SEC1).unwrap();
148    }
149}