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