]> git.sesse.net Git - linux-dallas-multipass/blob - multipass.rs
073e929216c9265a56952b3ecaaac19ec0d26e9e
[linux-dallas-multipass] / multipass.rs
1 // Copyright 2020 Steinar H. Gunderson <steinar+git@gunderson.no>
2 // License: GPLv2+.
3
4 extern crate pcsc;
5 #[macro_use]
6 extern crate simple_error;
7 #[macro_use]
8 extern crate serde_json;
9
10 use pcsc::*;
11 use core::task::{Context, Poll};
12 use futures_util::stream::{Stream, StreamExt};
13 use hyper::service::{make_service_fn, service_fn};
14 use hyper::{Body, Method, Request, Response, Server, StatusCode};
15 use hyper::header::HeaderValue;
16 use rustls::internal::pemfile;
17 use std::pin::Pin;
18 use std::vec::Vec;
19 use std::{fs, io, sync};
20 use tokio::net::{TcpListener, TcpStream};
21 use tokio_rustls::server::TlsStream;
22 use tokio_rustls::TlsAcceptor;
23 use serde_json::Value;
24 use serde_json::Value::Array;
25 use hex;
26 use std::sync::Mutex;
27 use lazy_static::lazy_static;
28 use std::ffi::CString;
29 use percent_encoding::percent_decode_str;
30
31 fn main() {
32     if let Err(e) = run_server() {
33         eprintln!("FAILED: {}", e);
34         std::process::exit(1);
35     }
36 }
37
38 fn error(err: String) -> io::Error {
39     io::Error::new(io::ErrorKind::Other, err)
40 }
41
42 #[tokio::main]
43 async fn run_server() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
44     // First parameter is port number (optional, defaults to 1337)
45     let port = 31505;
46     let addr = format!("127.0.0.1:{}", port);
47
48     // Build TLS configuration.
49     let tls_cfg = {
50         // Load public certificate.
51         let certs = load_certs("cert.pem")?;
52         // Load private key.
53         let key = load_private_key("cert.rsa")?;
54         // Do not use client certificate authentication.
55         let mut cfg = rustls::ServerConfig::new(rustls::NoClientAuth::new());
56         // Select a certificate to use.
57         cfg.set_single_cert(certs, key)
58             .map_err(|e| error(format!("{}", e)))?;
59         // Configure ALPN to accept HTTP/2, HTTP/1.1 in that order.
60         cfg.set_protocols(&[b"h2".to_vec(), b"http/1.1".to_vec()]);
61         sync::Arc::new(cfg)
62     };
63
64     // Create a TCP listener via tokio.
65     let mut tcp = TcpListener::bind(&addr).await?;
66     let tls_acceptor = &TlsAcceptor::from(tls_cfg);
67     // Prepare a long-running future stream to accept and serve cients.
68     let incoming_tls_stream = tcp
69         .incoming()
70         .filter_map(move |s| async move {
71             let client = match s {
72                 Ok(x) => x,
73                 Err(e) => {
74                     println!("Failed to accept a client, should probably back off");
75                     return Some(Err(e));
76                 }
77             };
78             match tls_acceptor.accept(client).await {
79                 Ok(x) => Some(Ok(x)),
80                 Err(e) => {
81                     println!("[!] Client connection error: {}", e);
82                     None
83                 }
84             }
85         })
86         .boxed();
87
88     let service = make_service_fn(|_| async { Ok::<_, io::Error>(service_fn(apdu_service)) });
89     let server = Server::builder(HyperAcceptor {
90         acceptor: incoming_tls_stream,
91     })
92     .serve(service);
93
94     // Run the future, keep going until an error occurs.
95     println!("Starting to serve on https://{}.", addr);
96     server.await?;
97     Ok(())
98 }
99
100 struct HyperAcceptor<'a> {
101     acceptor: Pin<Box<dyn Stream<Item = Result<TlsStream<TcpStream>, io::Error>> + 'a>>,
102 }
103
104 impl hyper::server::accept::Accept for HyperAcceptor<'_> {
105     type Conn = TlsStream<TcpStream>;
106     type Error = io::Error;
107
108     fn poll_accept(
109         mut self: Pin<&mut Self>,
110         cx: &mut Context,
111     ) -> Poll<Option<Result<Self::Conn, Self::Error>>> {
112         Pin::new(&mut self.acceptor).poll_next(cx)
113     }
114 }
115
116 fn unwrap_result_into_response(result: Result<hyper::Body, Box<dyn std::error::Error>>, mut response: Response<Body>) -> Result<Response<Body>, hyper::Error> {
117         match result {
118            Ok(body) => { *response.body_mut() = body; return Ok(response); }
119            Err(text) => {
120                response.headers_mut().insert("Content-Type", HeaderValue::from_static("text/plain"));
121                *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
122                *response.body_mut() = Body::from(format!("{}",text));
123                return Ok(response);
124            }
125         };
126 }
127
128 async fn apdu_service(req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
129     let mut response = Response::new(Body::empty());
130     println!("request: {} {}", req.method(), req.uri().path());
131
132     // Make sure we're talking to the right site.
133     match req.headers().get("Origin") {
134         None => {
135             println!("No origin header, exiting");
136             *response.status_mut() = StatusCode::FORBIDDEN;
137             return Ok(response);
138         }
139         Some(origin) => {
140             if origin.as_bytes() != b"https://secure.buypass.no" {
141                 println!("Wrong origin: {:?}", origin);
142                 *response.status_mut() = StatusCode::FORBIDDEN;
143                 return Ok(response);
144             }
145         }
146     }
147
148     response.headers_mut().insert("Access-Control-Allow-Origin", HeaderValue::from_static("https://secure.buypass.no"));
149     response.headers_mut().insert("Content-Type", HeaderValue::from_static("application/json; charset=UTF-8"));
150
151     if req.method() == Method::POST && req.uri().path().starts_with("/scard/apdu/") {
152         let reader_name_uri = &req.uri().path()[12..].to_owned();
153         let reader_name = percent_decode_str(reader_name_uri).decode_utf8().expect("<invalid reader name>");
154
155         let body = hyper::body::to_bytes(req.into_body()).await?;
156         let body_str = String::from_utf8(body.into_iter().collect()).expect("");
157
158         return unwrap_result_into_response(apdureq(&reader_name, body_str), response);
159     }
160
161     match (req.method(), req.uri().path()) {
162         (&Method::GET, _) => {
163             *response.status_mut() = StatusCode::BAD_REQUEST;
164         }
165         (&Method::POST, "/scard/version/") => {
166             let reply = json!({
167                 "version": "1.3.9.46"
168             });
169             *response.body_mut() = Body::from(reply.to_string());
170         }
171         (&Method::POST, "/scard/list/") => {
172             return unwrap_result_into_response(get_readers(), response);
173         }
174         (&Method::POST, "/scard/disconnect/") => {
175             *response.body_mut() = Body::from("");
176             println!("Disconnecting from card.");
177             let mut card = GLOBAL_CARD.lock().unwrap();
178             *card = None;
179         }
180         (&Method::POST, "/scard/getref/") => {
181             // This is seemingly so that the PIN isn't sent “in the clear”, over TLS, to localhost.
182             // We send a key (in the form of 16 bytes) with an ID (the reference),
183             // the first four bytes are XORed with the next four bytes, and the PIN
184             // is XOR-ed with that. Because this adds no real extra security as long
185             // as we don't log it, we just give back 16 zero bytes, and the XOR will
186             // be a noop. (The reference is a handle to the key, in case we've given
187             // out multiple ones. We set it to some dummy value.)
188             let random_bytes = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
189             let reply = json!({
190                 "data": &hex::encode(random_bytes).to_ascii_uppercase(),
191                 "ref": 195948557
192             });
193             println!("Reply: {}", reply.to_string());
194             *response.body_mut() = Body::from(reply.to_string());
195         }
196         // Catch-all 404.
197         _ => {
198             *response.status_mut() = StatusCode::NOT_FOUND;
199         }
200     };
201     Ok(response)
202 }
203
204 // Load public certificate from file.
205 fn load_certs(filename: &str) -> io::Result<Vec<rustls::Certificate>> {
206     // Open certificate file.
207     let certfile = fs::File::open(filename)
208         .map_err(|e| error(format!("failed to open {}: {}", filename, e)))?;
209     let mut reader = io::BufReader::new(certfile);
210
211     // Load and return certificate.
212     pemfile::certs(&mut reader).map_err(|_| error("failed to load certificate".into()))
213 }
214
215 // Load private key from file.
216 fn load_private_key(filename: &str) -> io::Result<rustls::PrivateKey> {
217     // Open keyfile.
218     let keyfile = fs::File::open(filename)
219         .map_err(|e| error(format!("failed to open {}: {}", filename, e)))?;
220     let mut reader = io::BufReader::new(keyfile);
221
222     // Load and return a single private key.
223     let keys = pemfile::rsa_private_keys(&mut reader)
224         .map_err(|_| error("failed to load private key".into()))?;
225     if keys.len() != 1 {
226         return Err(error(format!("expected a single private key, got {}", keys.len()).into()));
227     }
228     Ok(keys[0].clone())
229 }
230
231 fn get_readers() -> Result<hyper::Body, Box<dyn std::error::Error>>
232 {
233         // Establish a PC/SC context.
234         let ctx = pcsc::Context::establish(Scope::User)?;
235
236         // List available readers.
237         let mut readers_buf = [0; 2048];
238         let mut reader_json : Vec<serde_json::Value> = Vec::new();
239         for r in ctx.list_readers(&mut readers_buf)? {
240                 // Check the card status by connecting to it.
241                 let status = match ctx.connect(r, ShareMode::Shared, Protocols::ANY) {
242                         Ok(_) => 302,
243                         Err(_) => 303
244                 };
245
246                 reader_json.push(json!({
247                         "cardstatus": status,
248                         "name": r.to_str()?
249                 }));
250         }
251
252         let reply = json!({
253                 "errorcode": 0,
254                 "errordetail": 0,
255                 "readers": reader_json
256         });
257         println!("Reply: {}", reply.to_string());
258         return Ok(Body::from(reply.to_string()));
259 }
260
261 fn transmit_apdu(card: &Card, mut apdu: &[u8]) -> Result<String, Box<dyn std::error::Error>> {
262         if apdu.len() >= 11 && apdu[0] == 0xff && apdu[1] == 0xff && apdu[2] == 0x01 && apdu[3] == 0x04 {
263             // APDUs with PIN codes are obfuscated (see /getref/ above)
264             // with a special extension header used only in scproxy. The format seems to be:
265             //
266             // 0xff 0xff 0x01 0x04 <ref, 4 bytes> <pin offset> <pin length> | <actual APDU>
267             //
268             // where 0x04 might be the length of the ref. 0x01 probably is the number of 
269             // PIN codes, but I've never seen multiple ones. The ref is a reference to a key
270             // we gave out earlier in /getref/, and XORs the PIN. But since we gave out an
271             // all-zero key, we don't need to do anything except strip out the header,
272             // and everything will be fine.
273             apdu = &apdu[10..];
274         }
275
276         let mut rapdu_buf = [0; MAX_BUFFER_SIZE];
277         let rapdu = card.transmit(&apdu, &mut rapdu_buf)?;
278                 if rapdu.len() == 2 && rapdu[0] == 0x61 {
279                     println!("Received APDU {:?}, sending GET RESPONSE for {} bytes", rapdu, rapdu[1]);
280                     let new_apdu = [0, 0xc0, 0, 0, rapdu[1]];
281                     return transmit_apdu(&card, &new_apdu);
282                 } else {
283                     return Ok(hex::encode(rapdu).to_ascii_uppercase());
284                 }
285 }
286
287 lazy_static!{
288     static ref GLOBAL_CARD: Mutex<Option<pcsc::Card>> = Mutex::new(None);
289 }
290
291 fn get_apducommands(req: String) -> Result<Vec<Vec<u8>>, Box<dyn std::error::Error>> {
292
293         let v: Value = serde_json::from_str(&req)?;
294         let mut apdus : Vec<Vec<u8>> = [].to_vec();
295         match &v["apducommands"] {
296                 Array(ac) => {
297                         for apdu in ac {
298                                 match &apdu["apdu"] {
299                                         serde_json::Value::String(s) => {
300                                                 apdus.push(hex::decode(s)?);
301                                         }
302                                         _ => { bail!("not a string"); }
303                                 }
304                         }
305                 },
306                 _ => { bail!("no array"); }
307         };
308         return Ok(apdus);
309 }
310
311 fn apdureq(reader_name: &str, req: String) -> Result<hyper::Body, Box<dyn std::error::Error>>
312 {
313         let apdus = get_apducommands(req.clone())?;
314         let mut any_sensitive = false;
315         for apdu in &apdus {
316                 if apdu.len() >= 2 && apdu[0] == 0xff && apdu[1] == 0xff {
317                         any_sensitive = true;
318                 }
319         }
320         if any_sensitive {
321                 println!("<not printing request, may contain PIN codes>")
322         } else {
323                 println!("{}", req);
324         }
325         return run_apdu(reader_name, apdus);
326 }
327
328 fn run_apdu(reader_name: &str, apdus: Vec<Vec<u8>>) -> Result<hyper::Body, Box<dyn std::error::Error>>
329 {
330         let mut card = GLOBAL_CARD.lock()?;
331
332         // FIXME: check that we're using the same reader, and the same context.
333         // (Ideally, allow multiple readers in use at the same time.) Also, do timeout.
334         if (*card).is_none() {
335                 println!("Connecting to card");
336
337                 // Establish a PC/SC context.
338                 let ctx = pcsc::Context::establish(Scope::User)?;
339
340                 // Connect to the card.
341                 *card = match ctx.connect(&CString::new(reader_name)?, ShareMode::Shared, Protocols::ANY) {
342                         Ok(card) => Some(card),
343                         Err(Error::UnknownReader) => {
344                                 bail!(format!("Could not find reader {}", reader_name));
345                         }
346                         Err(Error::NoSmartcard) => {
347                                 bail!("A smartcard is not present in the reader.");
348                         }
349                         Err(err) => {
350                                 return Err(err.into());
351                         }
352                 }; 
353         }
354
355         let mut response_json : Vec<serde_json::Value> = Vec::new();
356         for apdu in apdus {
357                 let reply = &transmit_apdu(card.as_mut().unwrap(), &apdu)?;
358                 response_json.push(json!({
359                         "apdu": reply
360                 }));
361         }
362         let reply = json!({
363                 "errorcode": 0,
364                 "errordetail": 0,
365                 "apduresponses": response_json
366         });
367         println!("Reply: {}", reply.to_string());
368         return Ok(Body::from(reply.to_string()));
369 }