e_imzo/
lib.rs

1#![allow(clippy::never_loop)]
2#![allow(clippy::result_large_err)]
3
4pub mod error;
5
6use error::EIMZOError;
7use native_tls::{TlsConnector, TlsStream};
8use serde::{Deserialize, Serialize};
9use serde_json::json;
10use std::{collections::HashMap, net::TcpStream};
11use tungstenite::{
12    Message, WebSocket,
13    client::client,
14    handshake::client::{Request, generate_key},
15};
16use url::Url;
17
18#[derive(Serialize, Deserialize, Debug)]
19pub struct Certificate {
20    pub disk: String,
21    pub path: String,
22    pub name: String,
23    pub alias: String,
24}
25
26impl Certificate {
27    pub fn get_alias(&self) -> HashMap<String, String> {
28        self.alias
29            .split(",")
30            .filter_map(|kv| {
31                let mut kv = kv.split("=");
32                match (kv.next(), kv.next()) {
33                    (Some(k), Some(v)) => Some((k.to_string(), v.to_string())),
34                    _ => None,
35                }
36            })
37            .collect()
38    }
39}
40
41pub struct EIMZOConnection {
42    pub socket: WebSocket<TlsStream<TcpStream>>,
43}
44
45impl EIMZOConnection {
46    fn connect() -> Result<Self, EIMZOError> {
47        let ws_url = Url::parse("wss://127.0.0.1:64443/service/cryptapi")?;
48
49        // Establish a TCP connection, then wrap the TCP stream with TLS and connect to the server
50        let tls_connector = TlsConnector::builder()
51            .danger_accept_invalid_certs(true)
52            .build()?;
53
54        let remote_addr = match (ws_url.host(), ws_url.port()) {
55            (Some(host), Some(port)) => Some(format!("{host}:{port}")),
56            _ => None,
57        }
58        .ok_or(())
59        .unwrap();
60
61        let req = Request::builder()
62            .method("GET")
63            .header("Host", "localhost")
64            .header("Connection", "Upgrade")
65            .header("Upgrade", "websocket")
66            .header("Origin", "https://localhost")
67            .header("Sec-WebSocket-Version", "13")
68            .header("Sec-WebSocket-Key", generate_key())
69            .uri(ws_url.to_string())
70            .body(())
71            .unwrap();
72        let tcp_stream = std::net::TcpStream::connect(remote_addr.clone())?;
73        let tls_stream = tls_connector.connect(remote_addr.as_str(), tcp_stream)?;
74
75        let (socket, _) = client(req, tls_stream)?;
76
77        let connection = Self { socket };
78
79        Ok(connection)
80    }
81
82    pub fn send_and_wait(&mut self, message: Message) -> tungstenite::Result<Message> {
83        self.socket.send(message)?;
84
85        while let Ok(message) = self.socket.read() {
86            return Ok(message);
87        }
88
89        unreachable!();
90    }
91
92    pub fn set_api_keys(&mut self) -> tungstenite::Result<Message> {
93        let set_api_keys = json!({
94            "plugin": "apikey",
95            "name": "apikey",
96            "arguments": [
97                "localhost",
98                "96D0C1491615C82B9A54D9989779DF825B690748224C2B04F500F370D51827CE2644D8D4A82C18184D73AB8530BB8ED537269603F61DB0D03D2104ABF789970B",
99            ]
100        });
101
102        self.send_and_wait(Message::Text(set_api_keys.to_string().into()))
103    }
104}
105
106#[derive(Serialize, Deserialize, Default, Debug)]
107pub struct ListAllCertificatesResponse {
108    pub certificates: Vec<Certificate>,
109}
110
111pub fn list_all_certificates() -> Result<Vec<Certificate>, EIMZOError> {
112    let mut conn: EIMZOConnection = EIMZOConnection::connect()?;
113
114    let _ = conn.set_api_keys();
115
116    let cmd: serde_json::Value = json!({
117        "plugin": "pfx",
118        "name": "list_all_certificates",
119    });
120
121    let value = match conn.send_and_wait(Message::Text(cmd.to_string().into())) {
122        Ok(Message::Text(str)) => serde_json::from_str::<ListAllCertificatesResponse>(&str),
123        _ => Ok(ListAllCertificatesResponse::default()),
124    };
125
126    Ok(value.map(|s| s.certificates)?)
127}