use async_trait::async_trait;
use chrono::{ DateTime, Utc };
use data_encoding::BASE64URL;
use regex::Regex;
use reqwest;
use secp256k1::{Message, Signature, recover, RecoveryId, SecretKey, sign};
use serde_json::Value;
use serde::{Serialize, Deserialize};
use sha2::{Digest, Sha256};
use sha3::Keccak256;
use simple_error::SimpleError;
use std::convert::TryInto;
use std::str;
use vade::traits::VcResolver;
use vade::Vade;
pub const VC_W3C_MANDATORY_CONTEXT: &'static str = "https://www.w3.org/2018/credentials/v1";
pub const VC_DEFAULT_TYPE: &'static str = "VerifiableCredential";
const JWT_REGEX: &'static str = r#"^\s*\{"iat":[^,]+,"vc":(.*),"iss":"[^"]+?"\}\s*$"#;
#[allow(non_snake_case)]
#[derive(Serialize, Deserialize, Debug)]
struct EvanDid {
pub publicKey: Vec<EvanDidPublicKey>,
}
#[allow(non_snake_case)]
#[derive(Serialize, Deserialize, Debug)]
struct EvanDidPublicKey {
pub ethereumAddress: String,
pub id: String,
}
pub struct RustVcResolverEvan {
pub vade: Option<Box<Vade>>,
}
impl RustVcResolverEvan {
pub fn new() -> RustVcResolverEvan {
match env_logger::try_init() {
Ok(_) | Err(_) => (),
};
RustVcResolverEvan {
vade: None,
}
}
#[allow(dead_code)]
async fn set_vc_document(&mut self, _vc_id: &str, _value: &str) -> std::result::Result<(), Box<dyn std::error::Error>> {
unimplemented!();
}
async fn get_key_from_did(&self, key_from_did: &str) -> Result<String, Box<dyn std::error::Error>> {
let did = key_from_did.splitn(2, '#').next().unwrap();
debug!("getting keys for did {:?}", &did);
let did_document_string = self.vade.as_ref().unwrap().get_did_document(did).await.unwrap();
let did_document: EvanDid = serde_json::from_str(&did_document_string)?;
let key_objects: Vec<EvanDidPublicKey> = did_document.publicKey;
let matches: Vec<String> = key_objects
.into_iter()
.filter(|key| key.id == key_from_did)
.map(|key| key.ethereumAddress)
.collect();
match matches.len() {
1 => Ok(format!("{}", matches[0])),
0 => Err(Box::from(format!("key {} not found in DID {}", key_from_did, did))),
_ => Err(Box::from(format!("multiple matches found for key {} in DID {}", key_from_did, did))),
}
}
pub async fn create_vc(
&self,
vc_data: &str,
verification_method: &str,
private_key: &str
) -> Result<String, Box<dyn std::error::Error>> {
let mut parsed_vc: Value = serde_json::from_str(&vc_data).unwrap();
if parsed_vc["id"].is_null() {
return Err(Box::new(SimpleError::new("\"id\" is required for offline VCs")))
}
if parsed_vc["@context"].is_null() {
parsed_vc["@context"] = Value::from(Vec::<&str>::new());
}
if !parsed_vc["@context"].as_array().unwrap().iter().any(|v| v == VC_W3C_MANDATORY_CONTEXT) {
parsed_vc["@context"].as_array_mut().unwrap().push(Value::from(VC_W3C_MANDATORY_CONTEXT));
}
if parsed_vc["type"].is_null() {
parsed_vc["type"] = Value::from(VC_DEFAULT_TYPE);
}
if parsed_vc["issuer"].is_null() {
let split: Vec<&str> = verification_method.split('#').collect();
parsed_vc["issuer"] = Value::from(split[0]);
}
let now: DateTime<Utc> = Utc::now();
if parsed_vc["validFrom"].is_null() {
parsed_vc["validFrom"] = Value::from(format!("{}", now.format("%Y-%m-%dT%H:%M:%S.000Z")));
}
if parsed_vc["proof"].is_null() {
parsed_vc["proof"] = create_proof(&parsed_vc, &verification_method, &private_key, &now).unwrap();
}
let vc_str = String::from(format!("{}", &parsed_vc));
debug!("final VC document: {}", vc_str);
Ok(vc_str)
}
}
#[async_trait(?Send)]
impl VcResolver for RustVcResolverEvan {
async fn check_vc(&self, vc_id: &str, value: &str) -> Result<(), Box<dyn std::error::Error>> {
let mut vc: Value = serde_json::from_str(value)?;
if vc["proof"].is_null() {
debug!("vcs without a proof are considered as valid");
Ok(())
} else {
debug!("checking vc document");
let vc_without_proof = vc.as_object_mut().unwrap();
let vc_proof = vc_without_proof.remove("proof").unwrap();
let (address, decoded_payload_text) = recover_address_and_data(vc_proof["jws"].as_str().unwrap())?;
debug!("checking if document given and document from jws are equal");
let re = Regex::new(JWT_REGEX).unwrap();
let caps = re.captures(&decoded_payload_text).unwrap();
let parsed_caps1: Value = serde_json::from_str(&caps[1])?;
let parsed_caps1_map = parsed_caps1.as_object().unwrap();
if vc_without_proof != parsed_caps1_map {
return Err(Box::from("recovered VC document and given VC document do not match"));
}
debug!("checking proof of vc document");
let address = format!("0x{}", address);
let key_to_use = vc_proof["verificationMethod"].as_str().unwrap();
debug!("recovered address: {}", &address);
debug!("key to use for verification: {}", &key_to_use);
let key_from_did = self.get_key_from_did(key_to_use).await?;
debug!("key from did: {}", &key_from_did);
if address != key_from_did {
return Err(Box::from(format!("could not verify signature of \"{}\"", vc_id)));
}
debug!("checking if credential status is present, query it");
if !vc["credentialStatus"].is_null()
&& vc["credentialStatus"]["type"] == "evan:evanCredential" {
debug!("credential status is present, query it");
let vc_status = get_vc_status_valid(vc["credentialStatus"]["id"].as_str().unwrap()).await?;
if !vc_status {
return Err(Box::from(format!("vc \"{}\" is not active", &vc_id)));
}
}
debug!("vc document is valid");
Ok(())
}
}
async fn get_vc_document(&self, vc_id: &str) -> Result<String, Box<dyn std::error::Error>> {
let body = reqwest::get(&format!("https://testcore.evan.network/vc/{}", vc_id))
.await?
.text()
.await?;
let parsed: Value = serde_json::from_str(&body).unwrap();
if parsed["status"] == "error" {
Err(Box::new(SimpleError::new(format!("could not get vc document, {:?}", parsed["error"].as_str().unwrap()))))
} else {
Ok(serde_json::to_string(&parsed["vc"]).unwrap())
}
}
async fn set_vc_document(&mut self, _vc_name: &str, _value: &str) -> Result<(), Box<dyn std::error::Error>> {
unimplemented!();
}
}
async fn get_vc_status_valid(vc_status_id: &str) -> Result<bool, Box<dyn std::error::Error>> {
let body = reqwest::get(vc_status_id)
.await?
.text()
.await?;
let parsed: Value = serde_json::from_str(&body).unwrap();
if parsed["status"] == "error" {
Err(Box::new(SimpleError::new(format!("vc status error, {:?}", parsed["error"].as_str().unwrap()))))
} else {
let is_active = match parsed["vcStatus"].as_str().unwrap() {
"active" => true,
_ => false,
};
Ok(is_active)
}
}
fn create_proof(
vc: &Value,
verification_method: &str,
private_key: &str,
now: &DateTime<Utc>
) -> Result<Value, Box<dyn std::error::Error>> {
let header_str = r#"{"typ":"JWT","alg":"ES256K-R"}"#;
let padded = BASE64URL.encode(header_str.as_bytes());
let header_encoded = padded.trim_end_matches('=');
debug!("header base64 url encdoded: {:?}", &header_encoded);
let mut data_json: Value = serde_json::from_str("{}").unwrap();
let vc_clone: Value = serde_json::from_str(&format!("{}", &vc)).unwrap();
data_json["iat"] = Value::from(now.timestamp());
data_json["vc"] = vc_clone;
data_json["iss"] = Value::from(vc["issuer"].as_str().unwrap());
let padded = BASE64URL.encode(format!("{}", &data_json).as_bytes());
let data_encoded = padded.trim_end_matches('=');
debug!("data base64 url encdoded: {:?}", &data_encoded);
let header_and_data = format!("{}.{}", header_encoded, data_encoded);
let mut hasher = Sha256::new();
hasher.input(&header_and_data);
let hash = hasher.result();
debug!("header_and_data hash {:?}", hash);
let hash_arr: [u8; 32] = hash.try_into().expect("slice with incorrect length");
let message = Message::parse(&hash_arr);
let mut private_key_arr = [0u8; 32];
hex::decode_to_slice(&private_key, &mut private_key_arr).expect("private key invalid");
let secret_key = SecretKey::parse(&private_key_arr)?;
let (sig, rec): (Signature, _) = sign(&message, &secret_key);
let signature_arr = &sig.serialize();
let mut sig_and_rec: [u8; 65] = [0; 65];
for i in 0..64 {
sig_and_rec[i] = signature_arr[i];
}
sig_and_rec[64] = rec.serialize();
let padded = BASE64URL.encode(&sig_and_rec);
let sig_base64url = padded.trim_end_matches('=');
debug!("signature base64 url encdoded: {:?}", &sig_base64url);
let jws = format!("{}.{}", &header_and_data, sig_base64url);
let utc_now = format!("{}", now.format("%Y-%m-%dT%H:%M:%S.000Z"));
let proof_json_str = format!(r###"{{
"type": "EcdsaPublicKeySecp256k1",
"created": "{}",
"proofPurpose": "assertionMethod",
"verificationMethod": "{}",
"jws": "{}"
}}"###, &utc_now, &verification_method, &jws);
let proof: Value = serde_json::from_str(&proof_json_str).unwrap();
Ok(proof)
}
fn recover_address_and_data(jwt: &str) -> Result<(String, String), Box<dyn std::error::Error>> {
let split: Vec<&str> = jwt.split('.').collect();
let (header, data, signature) = (split[0], split[1], split[2]);
let header_and_data = format!("{}.{}", header, data);
let data_decoded = match BASE64URL.decode(data.as_bytes()) {
Ok(decoded) => decoded,
Err(_) => match BASE64URL.decode(format!("{}=", data).as_bytes()) {
Ok(decoded) => decoded,
Err(_) => match BASE64URL.decode(format!("{}==", data).as_bytes()) {
Ok(decoded) => decoded,
Err(_) => BASE64URL.decode(format!("{}===", data).as_bytes()).unwrap(),
},
},
};
let data_string = String::from_utf8(data_decoded)?;
let signature_decoded = match BASE64URL.decode(signature.as_bytes()) {
Ok(decoded) => decoded,
Err(_) => match BASE64URL.decode(format!("{}=", signature).as_bytes()) {
Ok(decoded) => decoded,
Err(_) => BASE64URL.decode(format!("{}==", signature).as_bytes()).unwrap(),
},
};
debug!("signature_decoded {:?}", &signature_decoded);
debug!("signature_decoded.len {:?}", signature_decoded.len());
let mut hasher = Sha256::new();
hasher.input(&header_and_data);
let hash = hasher.result();
debug!("header_and_data hash {:?}", hash);
let hash_arr: [u8; 32] = hash.try_into().expect("header_and_data hash invalid");
let ctx_msg = Message::parse(&hash_arr);
let mut signature_array = [0u8; 64];
for i in 0..64 {
signature_array[i] = signature_decoded[i];
}
debug!("recovery id: {}", signature_decoded[64]);
let ctx_sig = Signature::parse(&signature_array);
let recovery_id = RecoveryId::parse(signature_decoded[64]).unwrap();
let recovered_key = recover(&ctx_msg, &ctx_sig, &recovery_id).unwrap();
let mut hasher = Keccak256::new();
hasher.input(&recovered_key.serialize()[1..65]);
let hash = hasher.result();
debug!("recovered_key hash {:?}", hash);
let address = hex::encode(&hash[12..32]);
debug!("address 0x{}", &address);
Ok((address, data_string))
}