1use base64::{engine::general_purpose, Engine};
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6use crate::{
7 certificate::{
8 validate_certificate_meta, Certificate, CertificateFileAttributes, CertificateFileMeta,
9 },
10 config::get_config,
11 decryptor::Decryptor,
12 errors::Error,
13 license::License,
14 machine::{Machine, MachineAttributes},
15 verifier::Verifier,
16 KeygenResponseData,
17};
18
19#[derive(Debug, Serialize, Deserialize)]
20pub struct MachineFileDataset {
21 pub license: License,
22 pub machine: Machine,
23 pub issued: DateTime<Utc>,
24 pub expiry: DateTime<Utc>,
25 pub ttl: i32,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct MachineFile {
30 pub id: String,
31 pub certificate: String,
32 pub issued: DateTime<Utc>,
33 pub expiry: DateTime<Utc>,
34 pub ttl: i32,
35}
36
37impl Into<MachineFile> for CertificateFileAttributes {
38 fn into(self) -> MachineFile {
39 MachineFile {
40 id: "".into(),
41 certificate: self.certificate,
42 issued: self.issued,
43 expiry: self.expiry,
44 ttl: self.ttl,
45 }
46 }
47}
48
49impl MachineFile {
50 pub(crate) fn from(data: KeygenResponseData<CertificateFileAttributes>) -> MachineFile {
51 MachineFile {
52 id: data.id,
53 ..data.attributes.into()
54 }
55 }
56
57 pub fn from_cert(key: &str, content: &str) -> Result<MachineFile, Error> {
58 let dataset = Self::_decrypt(key, content)?;
59 Ok(MachineFile {
60 id: dataset.machine.id.clone(),
61 certificate: content.to_string(),
62 issued: dataset.issued,
63 expiry: dataset.expiry,
64 ttl: dataset.ttl,
65 })
66 }
67
68 pub fn verify(&self) -> Result<(), Error> {
69 self.validate_ttl()?;
70
71 let config = get_config();
72
73 if let Some(public_key) = config.public_key {
74 let verifier = Verifier::new(public_key);
75 verifier.verify_machine_file(self)
76 } else {
77 Err(Error::PublicKeyMissing)
78 }
79 }
80
81 pub fn validate_ttl(&self) -> Result<(), Error> {
82 let now = Utc::now();
83 if now > self.expiry {
84 let dataset = self.decrypt("").unwrap_or_else(|_| {
85 use std::collections::HashMap;
86 MachineFileDataset {
87 license: License::from(crate::KeygenResponseData {
88 id: "".to_string(),
89 r#type: "licenses".to_string(),
90 attributes: crate::license::LicenseAttributes {
91 name: None,
92 key: "".to_string(),
93 expiry: None,
94 status: Some("".to_string()),
95 uses: Some(0),
96 max_machines: None,
97 max_cores: None,
98 max_uses: None,
99 max_processes: None,
100 max_users: None,
101 protected: None,
102 suspended: None,
103 permissions: None,
104 metadata: HashMap::new(),
105 },
106 relationships: crate::KeygenRelationships::default(),
107 }),
108 machine: Machine::from(crate::KeygenResponseData {
109 id: "".to_string(),
110 r#type: "machines".to_string(),
111 attributes: crate::machine::MachineAttributes {
112 fingerprint: "".to_string(),
113 name: None,
114 platform: None,
115 hostname: None,
116 ip: None,
117 cores: None,
118 metadata: None,
119 require_heartbeat: false,
120 heartbeat_status: "".to_string(),
121 heartbeat_duration: None,
122 created: Utc::now(),
123 updated: Utc::now(),
124 },
125 relationships: crate::KeygenRelationships::default(),
126 }),
127 issued: self.issued,
128 expiry: self.expiry,
129 ttl: self.ttl,
130 }
131 });
132 Err(Error::MachineFileExpired(dataset))
133 } else {
134 Ok(())
135 }
136 }
137
138 pub fn decrypt(&self, key: &str) -> Result<MachineFileDataset, Error> {
139 Self::_decrypt(key, &self.certificate)
140 }
141
142 pub fn certificate(&self) -> Result<Certificate, Error> {
143 Self::_certificate(self.certificate.clone())
144 }
145
146 fn _decrypt(key: &str, content: &str) -> Result<MachineFileDataset, Error> {
147 let cert = Self::_certificate(content.to_string())?;
148
149 match cert.alg.as_str() {
150 "aes-256-gcm+rsa-pss-sha256" | "aes-256-gcm+rsa-sha256" => {
151 return Err(Error::LicenseFileNotSupported(cert.alg.clone()));
152 }
153 "aes-256-gcm+ed25519" => {}
154 _ => return Err(Error::LicenseFileNotEncrypted),
155 }
156
157 let decryptor = Decryptor::new(key.to_string());
158 let data = decryptor.decrypt_certificate(&cert)?;
159 let dataset: Value =
160 serde_json::from_slice(&data).map_err(|e| Error::MachineFileInvalid(e.to_string()))?;
161
162 let meta: CertificateFileMeta = serde_json::from_value(dataset["meta"].clone())
163 .map_err(|e| Error::LicenseFileInvalid(e.to_string()))?;
164
165 let license_data = dataset["included"]
167 .as_array()
168 .ok_or(Error::MachineFileInvalid(
169 "Included data is not an array".into(),
170 ))?
171 .iter()
172 .find(|v| v["type"] == "licenses")
173 .ok_or(Error::MachineFileInvalid(
174 "No license data found in included data".into(),
175 ))?;
176 let license = License::from(serde_json::from_value(license_data.clone())?);
177
178 let machine_data: KeygenResponseData<MachineAttributes> =
179 serde_json::from_value(dataset["data"].clone())
180 .map_err(|e| Error::MachineFileInvalid(e.to_string()))?;
181 let machine = Machine::from(machine_data);
182
183 let dataset = MachineFileDataset {
184 license,
185 machine,
186 issued: meta.issued,
187 expiry: meta.expiry,
188 ttl: meta.ttl,
189 };
190
191 if let Err(err) = validate_certificate_meta(&meta) {
192 match err {
193 Error::CerificateFileExpired => Err(Error::MachineFileExpired(dataset)),
194 _ => Err(err),
195 }
196 } else {
197 Ok(dataset)
198 }
199 }
200
201 fn _certificate(certificate: String) -> Result<Certificate, Error> {
202 let payload = certificate.trim();
203 let payload = payload
204 .strip_prefix("-----BEGIN MACHINE FILE-----")
205 .and_then(|s| s.strip_suffix("-----END MACHINE FILE-----"))
206 .ok_or(Error::MachineFileInvalid(
207 "Invalid machine file format".into(),
208 ))?
209 .trim()
210 .replace("\n", "");
211
212 let decoded = general_purpose::STANDARD
213 .decode(payload)
214 .map_err(|e| Error::MachineFileInvalid(e.to_string()))?;
215
216 let cert: Certificate = serde_json::from_slice(&decoded)
217 .map_err(|e| Error::MachineFileInvalid(e.to_string()))?;
218
219 Ok(cert)
220 }
221}