spacetimedb/auth/
mod.rs

1use jsonwebtoken::{DecodingKey, EncodingKey};
2use openssl::ec::{EcGroup, EcKey};
3use openssl::nid::Nid;
4use openssl::pkey::PKey;
5use spacetimedb_paths::cli::{PrivKeyPath, PubKeyPath};
6
7use crate::config::CertificateAuthority;
8
9pub use spacetimedb_auth::identity;
10pub mod token_validation;
11
12/// JWT verification and signing keys.
13#[derive(Clone)]
14pub struct JwtKeys {
15    pub public: DecodingKey,
16    pub public_pem: Box<[u8]>,
17    pub private: EncodingKey,
18    pub kid: Option<String>,
19}
20
21impl JwtKeys {
22    /// Create a new [`JwtKeys`] from paths to the public and private key files
23    /// respectively.
24    ///
25    /// The key files must be PEM encoded ECDSA P256 keys.
26    pub fn new(public_pem: impl Into<Box<[u8]>>, private_pem: &[u8]) -> anyhow::Result<Self> {
27        let public_pem = public_pem.into();
28        let public = DecodingKey::from_ec_pem(&public_pem)?;
29        let private = EncodingKey::from_ec_pem(private_pem)?;
30
31        Ok(Self {
32            public,
33            private,
34            public_pem,
35            kid: None,
36        })
37    }
38
39    pub fn generate() -> anyhow::Result<Self> {
40        let keypair = EcKeyPair::generate()?;
41        keypair.try_into()
42    }
43}
44
45// Get the key pair if the given files exist. If they don't, create them.
46// If only one of the files exists, return an error.
47pub fn get_or_create_keys(certs: &CertificateAuthority) -> anyhow::Result<JwtKeys> {
48    let public_key_path = &certs.jwt_pub_key_path;
49    let private_key_path = &certs.jwt_priv_key_path;
50
51    let public_key_bytes = public_key_path.read().ok();
52    let private_key_bytes = private_key_path.read().ok();
53
54    // If both keys are unspecified, create them
55    let key_pair = match (public_key_bytes, private_key_bytes) {
56        (Some(pub_), Some(priv_)) => EcKeyPair::new(pub_, priv_),
57        (None, None) => {
58            let keys = EcKeyPair::generate()?;
59            keys.write_to_files(public_key_path, private_key_path)?;
60            keys
61        }
62        (None, Some(_)) => anyhow::bail!("Unable to read public key for JWT token verification"),
63        (Some(_), None) => anyhow::bail!("Unable to read private key for JWT token signing"),
64    };
65
66    key_pair.try_into()
67}
68
69// An Ec key pair in pem format.
70pub struct EcKeyPair {
71    pub public_key_bytes: Vec<u8>,
72    pub private_key_bytes: Vec<u8>,
73}
74
75impl TryFrom<EcKeyPair> for JwtKeys {
76    type Error = anyhow::Error;
77    fn try_from(pair: EcKeyPair) -> anyhow::Result<Self> {
78        JwtKeys::new(pair.public_key_bytes, &pair.private_key_bytes)
79    }
80}
81
82impl EcKeyPair {
83    pub fn new(public_key_bytes: Vec<u8>, private_key_bytes: Vec<u8>) -> Self {
84        Self {
85            public_key_bytes,
86            private_key_bytes,
87        }
88    }
89
90    pub fn generate() -> anyhow::Result<Self> {
91        // Create a new EC group from a named curve.
92        let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1)?;
93
94        // Create a new EC key with the specified group.
95        let eckey = EcKey::generate(&group)?;
96
97        // Create a new PKey from the EC key.
98        let pkey = PKey::from_ec_key(eckey.clone())?;
99
100        // Get the private key in PKCS#8 PEM format & write it.
101        let private_key_bytes = pkey.private_key_to_pem_pkcs8()?;
102
103        // Get the public key in PEM format & write it.
104        let public_key_bytes = eckey.public_key_to_pem()?;
105
106        Ok(Self {
107            public_key_bytes,
108            private_key_bytes,
109        })
110    }
111
112    pub fn write_to_files(&self, public_key_path: &PubKeyPath, private_key_path: &PrivKeyPath) -> anyhow::Result<()> {
113        public_key_path.write(&self.public_key_bytes)?;
114        private_key_path.write(&self.private_key_bytes)?;
115        Ok(())
116    }
117}