From 87ab008deb2bd7701da6e91f3998e51e68ec2bcf Mon Sep 17 00:00:00 2001
From: Daniil Gentili <daniil@daniil.it>
Date: Wed, 30 Sep 2020 18:21:05 +0200
Subject: [PATCH 1/2] Automatically unlock keystore using fingerprint, if
 possible

---
 src/bridge/mod.rs | 51 ++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 44 insertions(+), 7 deletions(-)

diff --git a/src/bridge/mod.rs b/src/bridge/mod.rs
index 4b00164..9e6a8bc 100644
--- a/src/bridge/mod.rs
+++ b/src/bridge/mod.rs
@@ -7,12 +7,15 @@ use std::error::Error;
 use std::io::{Read, Write};
 use std::process::{Command, Stdio};
 
+use serde_json;
+use serde_json::{Value};
+
 use base64;
 
 /// Send a request to `termux-api` to list all the keys.
 /// Returns a string that contains a JSON object.
 pub fn list_keys() -> Result<String, Box<dyn Error>> {
-    Ok(communicate(&["list", "--ez", "detailed", "true"], &[0; 0])?)
+    Ok(communicate(&"Keystore", &["-e", "command", "list", "--ez", "detailed", "true"], &[0; 0])?)
 }
 
 /// Send some data to `termux-api` to be signed.
@@ -23,16 +26,48 @@ pub fn list_keys() -> Result<String, Box<dyn Error>> {
 ///
 /// [Signature algorithms]:
 /// https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#Signature
+fn sign_internal(alias: &str, algorithm: &str, data: &[u8]) -> Result<Vec<u8>, Box<dyn Error>> {
+    let args = [
+        "-e", "command", "sign", 
+        "-e", "alias", alias, 
+        "-e", "algorithm", algorithm
+    ];
+    let output = communicate(&"Keystore", &args, data)?;
+    let res = base64::decode(output)?;
+    if res.len() == 0 {
+        return Err("Could not sign!".into())
+    }
+    Ok(res)
+}
+
+pub fn unlock() -> Result<(), Box<dyn Error>> {
+    let args = [
+        "--es", "title", "Tergent", 
+        "--es", "description", "Use your fingerprint to unlock the keystore",
+    ];
+    let json = communicate(&"Fingerprint", &args, &[0; 0])?;
+    let decoded: Value = serde_json::from_str::<serde_json::Value>(&json)?;
+    let res = decoded["auth_result"].as_str().ok_or("Invalid result")?;
+    if res == "AUTH_RESULT_FAILURE" {
+        return Err("Fingerprint authentication failed".into())
+    }
+    Ok(())
+}
+
 pub fn sign(alias: &str, algorithm: &str, data: &[u8]) -> Result<Vec<u8>, Box<dyn Error>> {
-    let args = ["sign", "-e", "alias", alias, "-e", "algorithm", algorithm];
-    let output = communicate(&args, data)?;
-    return Ok(base64::decode(output)?);
+    match sign_internal(&alias, &algorithm, &data) {
+        Ok(res) => Ok(res),
+        Err(_) => match unlock() {
+            Ok(_) => Ok(sign_internal(&alias, &algorithm, &data)?),
+            Err(e) => Err(e)
+        }
+    }
 }
 
 /// Performs a generic call to `termux-api`, providing `args` to its receiver.
 /// Sets up proper sockets so that the `input` is provided to `termux-api` and
 /// its output is returned from this function.
-fn communicate(args: &[&str], input: &[u8]) -> Result<String, Box<dyn Error>> {
+fn communicate(method: &str, args: &[&str], input: &[u8]) -> Result<String, Box<dyn Error>> {
     let mut input_socket = socket::Socket::new()?;
     let mut output_socket = socket::Socket::new()?;
 
@@ -43,7 +78,7 @@ fn communicate(args: &[&str], input: &[u8]) -> Result<String, Box<dyn Error>> {
         .args(&["-n", "com.termux.api/.TermuxApiReceiver"])
         .args(&["--es", "socket_input", &output_socket.address()])
         .args(&["--es", "socket_output", &input_socket.address()])
-        .args(&["--es", "api_method", "Keystore", "-e", "command"])
+        .args(&["--es", "api_method", method])
         .args(args)
         .stdin(Stdio::null())
         .stdout(Stdio::null())
@@ -65,6 +100,8 @@ fn communicate(args: &[&str], input: &[u8]) -> Result<String, Box<dyn Error>> {
     input_socket.close()?;
 
     // We need to reap our children otherwise they will stay as zombies.
-    command.wait()?;
+    // Ignore result, since this may error if the process has already closed
+    command.wait();
+
     Ok(output)
 }
-- 
2.30.1