]> git.sesse.net Git - linux-dallas-multipass/blobdiff - multipass.rs
When a smartcard error occurs, disconnect to avoid sticky errors.
[linux-dallas-multipass] / multipass.rs
index 958d18955d3aacaca3279f8481097420587db52e..8423cd9646939e39beadb590e227889380a37e9a 100644 (file)
@@ -4,13 +4,12 @@
 extern crate pcsc;
 #[macro_use]
 extern crate simple_error;
+#[macro_use]
+extern crate serde_json;
 
 use pcsc::*;
 use core::task::{Context, Poll};
-use futures_util::{
-    future::TryFutureExt,
-    stream::{Stream, StreamExt, TryStreamExt},
-};
+use futures_util::stream::{Stream, StreamExt};
 use hyper::service::{make_service_fn, service_fn};
 use hyper::{Body, Method, Request, Response, Server, StatusCode};
 use hyper::header::HeaderValue;
@@ -64,18 +63,25 @@ async fn run_server() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
 
     // Create a TCP listener via tokio.
     let mut tcp = TcpListener::bind(&addr).await?;
-    let tls_acceptor = TlsAcceptor::from(tls_cfg);
+    let tls_acceptor = &TlsAcceptor::from(tls_cfg);
     // Prepare a long-running future stream to accept and serve cients.
     let incoming_tls_stream = tcp
         .incoming()
-        .map_err(|e| error(format!("Incoming failed: {:?}", e)))
-        .and_then(move |s| {
-            tls_acceptor.accept(s).map_err(|e| {
-                println!("[!] Voluntary server halt due to client-connection error...");
-                // Errors could be handled here, instead of server aborting.
-       //         println!("TLS Error: {:?}", e);
-                error(format!("TLS Error: {:?}", e))
-            })
+        .filter_map(move |s| async move {
+            let client = match s {
+                Ok(x) => x,
+                Err(e) => {
+                    println!("Failed to accept a client, should probably back off");
+                    return Some(Err(e));
+                }
+            };
+            match tls_acceptor.accept(client).await {
+                Ok(x) => Some(Ok(x)),
+                Err(e) => {
+                    println!("[!] Client connection error: {}", e);
+                    None
+                }
+            }
         })
         .boxed();
 
@@ -157,20 +163,21 @@ async fn apdu_service(req: Request<Body>) -> Result<Response<Body>, hyper::Error
             *response.status_mut() = StatusCode::BAD_REQUEST;
         }
         (&Method::POST, "/scard/version/") => {
-            *response.body_mut() = Body::from("{\"version\":\"1.3.9.46\"}");
+            let reply = json!({
+                "version": "1.3.9.46"
+            });
+            *response.body_mut() = Body::from(reply.to_string());
         }
         (&Method::POST, "/scard/list/") => {
             return unwrap_result_into_response(get_readers(), response);
         }
         (&Method::POST, "/scard/disconnect/") => {
-            *response.body_mut() = Body::from("{\"errorcode\":0,\"errordetail\":0,\"apduresponses\":null}");
+            *response.body_mut() = Body::from("");
             println!("Disconnecting from card.");
             let mut card = GLOBAL_CARD.lock().unwrap();
             *card = None;
         }
         (&Method::POST, "/scard/getref/") => {
-            let mut reply = "{\"data\":\"".to_owned();
-
             // This is seemingly so that the PIN isn't sent “in the clear”, over TLS, to localhost.
             // We send a key (in the form of 16 bytes) with an ID (the reference),
             // the first four bytes are XORed with the next four bytes, and the PIN
@@ -179,10 +186,12 @@ async fn apdu_service(req: Request<Body>) -> Result<Response<Body>, hyper::Error
             // be a noop. (The reference is a handle to the key, in case we've given
             // out multiple ones. We set it to some dummy value.)
             let random_bytes = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
-            reply += &hex::encode(random_bytes).to_ascii_uppercase();
-            reply += "\",\"ref\":195948557}";
-            println!("Reply: {}", reply);
-            *response.body_mut() = Body::from(reply);
+            let reply = json!({
+                "data": &hex::encode(random_bytes).to_ascii_uppercase(),
+                "ref": 195948557
+            });
+            println!("Reply: {}", reply.to_string());
+            *response.body_mut() = Body::from(reply.to_string());
         }
         // Catch-all 404.
         _ => {
@@ -224,34 +233,33 @@ fn get_readers() -> Result<hyper::Body, Box<dyn std::error::Error>>
        // Establish a PC/SC context.
        let ctx = pcsc::Context::establish(Scope::User)?;
 
-       let mut reply : String = "{\"errorcode\":0,\"errordetail\":0,\"readers\":[".to_owned();
-
        // List available readers.
        let mut readers_buf = [0; 2048];
-       let mut reader_json : Vec<String> = Vec::new();
+       let mut reader_json : Vec<serde_json::Value> = Vec::new();
        for r in ctx.list_readers(&mut readers_buf)? {
                // Check the card status by connecting to it.
                let status = match ctx.connect(r, ShareMode::Shared, Protocols::ANY) {
-                       Ok(_) => "302",
-                       Err(_) => "303"
+                       Ok(_) => 302,
+                       Err(_) => 303
                };
 
-               let mut json = "{\"cardstatus\":".to_owned();
-               json += status;
-               json += ",\"name\":\"";
-               json += r.to_str()?;
-               json += "\"}";
-               reader_json.push(json);
+               reader_json.push(json!({
+                       "cardstatus": status,
+                       "name": r.to_str()?
+               }));
        }
 
-       reply += &reader_json.join(",");
-       reply += "]}";
-       println!("Reply: {}", reply);
-       return Ok(Body::from(reply));
+       let reply = json!({
+               "errorcode": 0,
+               "errordetail": 0,
+               "readers": reader_json
+       });
+       println!("Reply: {}", reply.to_string());
+       return Ok(Body::from(reply.to_string()));
 }
 
 fn transmit_apdu(card: &Card, mut apdu: &[u8]) -> Result<String, Box<dyn std::error::Error>> {
-        if apdu[0] == 0xff && apdu[1] == 0xff && apdu[2] == 0x01 && apdu[3] == 0x04 {
+        if apdu.len() >= 11 && apdu[0] == 0xff && apdu[1] == 0xff && apdu[2] == 0x01 && apdu[3] == 0x04 {
             // APDUs with PIN codes are obfuscated (see /getref/ above)
             // with a special extension header used only in scproxy. The format seems to be:
             //
@@ -305,7 +313,7 @@ fn apdureq(reader_name: &str, req: String) -> Result<hyper::Body, Box<dyn std::e
         let apdus = get_apducommands(req.clone())?;
        let mut any_sensitive = false;
        for apdu in &apdus {
-               if apdu[0] == 0xff && apdu[1] == 0xff {
+               if apdu.len() >= 2 && apdu[0] == 0xff && apdu[1] == 0xff {
                        any_sensitive = true;
                }
        }
@@ -344,18 +352,24 @@ fn run_apdu(reader_name: &str, apdus: Vec<Vec<u8>>) -> Result<hyper::Body, Box<d
                }; 
        }
 
-       let mut reply : String = "{\"errorcode\":0,\"errordetail\":0,\"apduresponses\":[".to_owned();
-
-       let mut response_json : Vec<String> = Vec::new();
+       let mut response_json : Vec<serde_json::Value> = Vec::new();
         for apdu in apdus {
-               let mut reply : String = "{\"apdu\":\"".to_owned();
-               reply += &transmit_apdu(card.as_mut().unwrap(), &apdu)?;
-               reply += "\"}";
-               response_json.push(reply);
+               let reply = match transmit_apdu(card.as_mut().unwrap(), &apdu) {
+                       Ok(r) => r,
+                       Err(err) => {
+                               *card = None;  // Clean up.
+                               return Err(err);
+                       }
+               };
+               response_json.push(json!({
+                       "apdu": reply
+               }));
        }
-
-       reply += &response_json.join(",");
-       reply += "]}";
-       println!("Reply: {}", reply);
-       return Ok(Body::from(reply));
+       let reply = json!({
+               "errorcode": 0,
+               "errordetail": 0,
+               "apduresponses": response_json
+       });
+       println!("Reply: {}", reply.to_string());
+       return Ok(Body::from(reply.to_string()));
 }