1 // Copyright 2020 Steinar H. Gunderson <steinar+git@gunderson.no>
6 extern crate simple_error;
9 use core::task::{Context, Poll};
12 stream::{Stream, StreamExt, TryStreamExt},
14 use hyper::service::{make_service_fn, service_fn};
15 use hyper::{Body, Method, Request, Response, Server, StatusCode};
16 use hyper::header::HeaderValue;
17 use rustls::internal::pemfile;
20 use std::{fs, io, sync};
21 use tokio::net::{TcpListener, TcpStream};
22 use tokio_rustls::server::TlsStream;
23 use tokio_rustls::TlsAcceptor;
24 use serde_json::Value;
25 use serde_json::Value::Array;
28 use lazy_static::lazy_static;
29 use std::ffi::CString;
30 use percent_encoding::percent_decode_str;
33 if let Err(e) = run_server() {
34 eprintln!("FAILED: {}", e);
35 std::process::exit(1);
39 fn error(err: String) -> io::Error {
40 io::Error::new(io::ErrorKind::Other, err)
44 async fn run_server() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
45 // First parameter is port number (optional, defaults to 1337)
47 let addr = format!("127.0.0.1:{}", port);
49 // Build TLS configuration.
51 // Load public certificate.
52 let certs = load_certs("cert.pem")?;
54 let key = load_private_key("cert.rsa")?;
55 // Do not use client certificate authentication.
56 let mut cfg = rustls::ServerConfig::new(rustls::NoClientAuth::new());
57 // Select a certificate to use.
58 cfg.set_single_cert(certs, key)
59 .map_err(|e| error(format!("{}", e)))?;
60 // Configure ALPN to accept HTTP/2, HTTP/1.1 in that order.
61 cfg.set_protocols(&[b"h2".to_vec(), b"http/1.1".to_vec()]);
65 // Create a TCP listener via tokio.
66 let mut tcp = TcpListener::bind(&addr).await?;
67 let tls_acceptor = TlsAcceptor::from(tls_cfg);
68 // Prepare a long-running future stream to accept and serve cients.
69 let incoming_tls_stream = tcp
71 .map_err(|e| error(format!("Incoming failed: {:?}", e)))
73 tls_acceptor.accept(s).map_err(|e| {
74 println!("[!] Voluntary server halt due to client-connection error...");
75 // Errors could be handled here, instead of server aborting.
76 // println!("TLS Error: {:?}", e);
77 error(format!("TLS Error: {:?}", e))
82 let service = make_service_fn(|_| async { Ok::<_, io::Error>(service_fn(apdu_service)) });
83 let server = Server::builder(HyperAcceptor {
84 acceptor: incoming_tls_stream,
88 // Run the future, keep going until an error occurs.
89 println!("Starting to serve on https://{}.", addr);
94 struct HyperAcceptor<'a> {
95 acceptor: Pin<Box<dyn Stream<Item = Result<TlsStream<TcpStream>, io::Error>> + 'a>>,
98 impl hyper::server::accept::Accept for HyperAcceptor<'_> {
99 type Conn = TlsStream<TcpStream>;
100 type Error = io::Error;
103 mut self: Pin<&mut Self>,
105 ) -> Poll<Option<Result<Self::Conn, Self::Error>>> {
106 Pin::new(&mut self.acceptor).poll_next(cx)
110 fn unwrap_result_into_response(result: Result<hyper::Body, Box<dyn std::error::Error>>, mut response: Response<Body>) -> Result<Response<Body>, hyper::Error> {
112 Ok(body) => { *response.body_mut() = body; return Ok(response); }
114 response.headers_mut().insert("Content-Type", HeaderValue::from_static("text/plain"));
115 *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
116 *response.body_mut() = Body::from(format!("{}",text));
122 async fn apdu_service(req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
123 let mut response = Response::new(Body::empty());
124 println!("request: {} {}", req.method(), req.uri().path());
126 // Make sure we're talking to the right site.
127 match req.headers().get("Origin") {
129 println!("No origin header, exiting");
130 *response.status_mut() = StatusCode::FORBIDDEN;
134 if origin.as_bytes() != b"https://secure.buypass.no" {
135 println!("Wrong origin: {:?}", origin);
136 *response.status_mut() = StatusCode::FORBIDDEN;
142 response.headers_mut().insert("Access-Control-Allow-Origin", HeaderValue::from_static("https://secure.buypass.no"));
143 response.headers_mut().insert("Content-Type", HeaderValue::from_static("application/json; charset=UTF-8"));
145 if req.method() == Method::POST && req.uri().path().starts_with("/scard/apdu/") {
146 let reader_name_uri = &req.uri().path()[12..].to_owned();
147 let reader_name = percent_decode_str(reader_name_uri).decode_utf8().expect("<invalid reader name>");
149 let body = hyper::body::to_bytes(req.into_body()).await?;
150 let body_str = String::from_utf8(body.into_iter().collect()).expect("");
152 return unwrap_result_into_response(apdureq(&reader_name, body_str), response);
155 match (req.method(), req.uri().path()) {
156 (&Method::GET, _) => {
157 *response.status_mut() = StatusCode::BAD_REQUEST;
159 (&Method::POST, "/scard/version/") => {
160 *response.body_mut() = Body::from("{\"version\":\"1.3.9.46\"}");
162 (&Method::POST, "/scard/list/") => {
163 return unwrap_result_into_response(get_readers(), response);
165 (&Method::POST, "/scard/disconnect/") => {
166 *response.body_mut() = Body::from("{\"errorcode\":0,\"errordetail\":0,\"apduresponses\":null}");
167 println!("Disconnecting from card.");
168 let mut card = GLOBAL_CARD.lock().unwrap();
171 (&Method::POST, "/scard/getref/") => {
172 let mut reply = "{\"data\":\"".to_owned();
174 // This is seemingly so that the PIN isn't sent “in the clear”, over TLS, to localhost.
175 // We send a key (in the form of 16 bytes) with an ID (the reference),
176 // the first four bytes are XORed with the next four bytes, and the PIN
177 // is XOR-ed with that. Because this adds no real extra security as long
178 // as we don't log it, we just give back 16 zero bytes, and the XOR will
179 // be a noop. (The reference is a handle to the key, in case we've given
180 // out multiple ones. We set it to some dummy value.)
181 let random_bytes = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
182 reply += &hex::encode(random_bytes).to_ascii_uppercase();
183 reply += "\",\"ref\":195948557}";
184 println!("Reply: {}", reply);
185 *response.body_mut() = Body::from(reply);
189 *response.status_mut() = StatusCode::NOT_FOUND;
195 // Load public certificate from file.
196 fn load_certs(filename: &str) -> io::Result<Vec<rustls::Certificate>> {
197 // Open certificate file.
198 let certfile = fs::File::open(filename)
199 .map_err(|e| error(format!("failed to open {}: {}", filename, e)))?;
200 let mut reader = io::BufReader::new(certfile);
202 // Load and return certificate.
203 pemfile::certs(&mut reader).map_err(|_| error("failed to load certificate".into()))
206 // Load private key from file.
207 fn load_private_key(filename: &str) -> io::Result<rustls::PrivateKey> {
209 let keyfile = fs::File::open(filename)
210 .map_err(|e| error(format!("failed to open {}: {}", filename, e)))?;
211 let mut reader = io::BufReader::new(keyfile);
213 // Load and return a single private key.
214 let keys = pemfile::rsa_private_keys(&mut reader)
215 .map_err(|_| error("failed to load private key".into()))?;
217 return Err(error(format!("expected a single private key, got {}", keys.len()).into()));
222 fn get_readers() -> Result<hyper::Body, Box<dyn std::error::Error>>
224 // Establish a PC/SC context.
225 let ctx = pcsc::Context::establish(Scope::User)?;
227 let mut reply : String = "{\"errorcode\":0,\"errordetail\":0,\"readers\":[".to_owned();
229 // List available readers.
230 let mut readers_buf = [0; 2048];
231 let mut reader_json : Vec<String> = Vec::new();
232 for r in ctx.list_readers(&mut readers_buf)? {
233 // Check the card status by connecting to it.
234 let status = match ctx.connect(r, ShareMode::Shared, Protocols::ANY) {
239 let mut json = "{\"cardstatus\":".to_owned();
241 json += ",\"name\":\"";
244 reader_json.push(json);
247 reply += &reader_json.join(",");
249 println!("Reply: {}", reply);
250 return Ok(Body::from(reply));
253 fn transmit_apdu(card: &Card, mut apdu: &[u8]) -> Result<String, Box<dyn std::error::Error>> {
254 if apdu.len() >= 11 && apdu[0] == 0xff && apdu[1] == 0xff && apdu[2] == 0x01 && apdu[3] == 0x04 {
255 // APDUs with PIN codes are obfuscated (see /getref/ above)
256 // with a special extension header used only in scproxy. The format seems to be:
258 // 0xff 0xff 0x01 0x04 <ref, 4 bytes> <pin offset> <pin length> | <actual APDU>
260 // where 0x04 might be the length of the ref. 0x01 probably is the number of
261 // PIN codes, but I've never seen multiple ones. The ref is a reference to a key
262 // we gave out earlier in /getref/, and XORs the PIN. But since we gave out an
263 // all-zero key, we don't need to do anything except strip out the header,
264 // and everything will be fine.
268 let mut rapdu_buf = [0; MAX_BUFFER_SIZE];
269 let rapdu = card.transmit(&apdu, &mut rapdu_buf)?;
270 if rapdu.len() == 2 && rapdu[0] == 0x61 {
271 println!("Received APDU {:?}, sending GET RESPONSE for {} bytes", rapdu, rapdu[1]);
272 let new_apdu = [0, 0xc0, 0, 0, rapdu[1]];
273 return transmit_apdu(&card, &new_apdu);
275 return Ok(hex::encode(rapdu).to_ascii_uppercase());
280 static ref GLOBAL_CARD: Mutex<Option<pcsc::Card>> = Mutex::new(None);
283 fn get_apducommands(req: String) -> Result<Vec<Vec<u8>>, Box<dyn std::error::Error>> {
285 let v: Value = serde_json::from_str(&req)?;
286 let mut apdus : Vec<Vec<u8>> = [].to_vec();
287 match &v["apducommands"] {
290 match &apdu["apdu"] {
291 serde_json::Value::String(s) => {
292 apdus.push(hex::decode(s)?);
294 _ => { bail!("not a string"); }
298 _ => { bail!("no array"); }
303 fn apdureq(reader_name: &str, req: String) -> Result<hyper::Body, Box<dyn std::error::Error>>
305 let apdus = get_apducommands(req.clone())?;
306 let mut any_sensitive = false;
308 if apdu.len() >= 2 && apdu[0] == 0xff && apdu[1] == 0xff {
309 any_sensitive = true;
313 println!("<not printing request, may contain PIN codes>")
317 return run_apdu(reader_name, apdus);
320 fn run_apdu(reader_name: &str, apdus: Vec<Vec<u8>>) -> Result<hyper::Body, Box<dyn std::error::Error>>
322 let mut card = GLOBAL_CARD.lock()?;
324 // FIXME: check that we're using the same reader, and the same context.
325 // (Ideally, allow multiple readers in use at the same time.) Also, do timeout.
326 if (*card).is_none() {
327 println!("Connecting to card");
329 // Establish a PC/SC context.
330 let ctx = pcsc::Context::establish(Scope::User)?;
332 // Connect to the card.
333 *card = match ctx.connect(&CString::new(reader_name)?, ShareMode::Shared, Protocols::ANY) {
334 Ok(card) => Some(card),
335 Err(Error::UnknownReader) => {
336 bail!(format!("Could not find reader {}", reader_name));
338 Err(Error::NoSmartcard) => {
339 bail!("A smartcard is not present in the reader.");
342 return Err(err.into());
347 let mut reply : String = "{\"errorcode\":0,\"errordetail\":0,\"apduresponses\":[".to_owned();
349 let mut response_json : Vec<String> = Vec::new();
351 let mut reply : String = "{\"apdu\":\"".to_owned();
352 reply += &transmit_apdu(card.as_mut().unwrap(), &apdu)?;
354 response_json.push(reply);
357 reply += &response_json.join(",");
359 println!("Reply: {}", reply);
360 return Ok(Body::from(reply));