All pages
Powered by GitBook
1 of 2

Loading...

Loading...

Providing User Data Retrieval API

Providing a REST API to retrieve the user data

The Data Issuer is required to expose a REST API that facilitates secure user data token retrieval. The API should be designed to authenticate the user robustly, ensuring that only the legitimate owner can access the data. The zkPass SDK does not dictate the precise authentication mechanisms, API semantics, or response formats, providing developers the flexibility to implement an approach best suited to their application's architecture.

The Data Issuer needs to make sure the user data is in JSON encoding. The DVR app architecture does not dictate the schema or structure of the user data, however, JSON encoding is required.

By conforming to these guidelines, Data Issuers contribute to the robustness and security of the DVR app infrastructure, ensuring an optimized and secure experience for all users involved.

Dvr Module Client Integration

Signing the User Data

As the authority provisioning sensitive user data, the Data Issuer plays a critical role in the DVR ecosystem. To ensure the authenticity of the user data, the Data Issuer must sign this sensitive information into a JWS (JSON Web Signature) token. The signing of the user data by the Data Issuer is illustrated by part of the DVR/zkPass call sequence, which is highlighted in red below:

The Dvr module client SDK library provides a specialized utility method for this purpose: callDvrGenerateUserDataToken. How to digitally sign the user data using the Dvr module client SDK library is illustrated by the code snippet below.

Parameters passed to callDvrGenerateUserDataToken:

  • signingKey This is the signing private key owned by the Data Issuer.

  • JSON.stringify(data) This is a stringified JSON user data.

  • verifyingKey This is a key to validate user data token.

Data Issuer

The Data Issuer only needs to follow this step for integration with the zkPass Service:

Sample Implementation

Sample codes for the Data Issuer implementation follow. Each step is explained in detail in the subsections. The code is also available on the zkpass-sdk repo, as shown here:

// Step 1: Instantiate DvrModuleClient
const dvrModuleClient = new DvrModuleClient({
      baseUrl: SERVICE_URL,
      apiKey: API_KEY,
      secretApiKey: API_SECRET,
    });
//
// Step 2: Call the DVR module client's callDvrGenerateUserDataToken
//         This is to digitally-sign the user data.
const userDataToken = dvrModuleClient.callDvrGenerateUserDataToken(
      signingKey,
      JSON.stringify(data),
      verifyingKey
    );
Providing REST API for user data retrieval
https://github.com/gl-zkPass/zkpass-sdk/blob/main/rust/zkpass-demo/src/data_issuer.rs
/*
 * data_issuer.rs
 * Simulating the Data Issuer process for the zkPass Demo
 *
 * ---
 * References:
 *   https://docs.ssi.id/zkpass/zkpass-developers-guide/privacy-apps/dvr/dvr-client-roles/data-issuer
 * ---
 * Copyright (c) 2024 PT Darta Media Indonesia. All rights reserved.
 */
use crate::{ sample_keys::ISSUER_PRIVKEY, lib_loader::generate_user_data_token };
use dvr_types::PublicKeyOptionFfi;
use serde_json::Value;
use std::{ collections::HashMap, io::prelude::*, ffi::CString };
use tracing::info;

// This struct ensures that the data reference is valid
#[allow(dead_code)]
pub struct IssuerPublicKeyOptionHolder {
    jku: CString,
    kid: CString,
    empty_str: CString,
    pub public_key_option: PublicKeyOptionFfi,
}

//
//  Simulating the REST call to the Data Issuer
//
pub struct DataIssuer;

//
//  Simulating the Data Issuer
//
impl DataIssuer {
    //
    // This function simulates the Data Issuer's get_user_data_token REST API
    //
    pub fn get_user_data_tokens(
        &self,
        data_files: HashMap<String, String>
    ) -> HashMap<String, String> {
        //
        // Call the dvr_client.dvr_generate_user_data_token.
        // This is to digitally-sign the user data.
        //
        data_files
            .iter()
            .map(|(data_tag, data_file)| {
                let data = self.read_user_data_token(data_file);
                let data_token = self.sign_user_data_token(data);
                (data_tag.clone(), data_token)
            })
            .collect()
    }

    ///
    /// Signs the user data token.
    ///
    fn sign_user_data_token(&self, data: Value) -> String {
        let user_data_token = unsafe {
            generate_user_data_token(ISSUER_PRIVKEY, &data.to_string())
        };

        user_data_token
    }

    ///
    /// Reads the user data token from the given file path.
    ///
    fn read_user_data_token(&self, data_file: &String) -> Value {
        let mut data_content = std::fs::File
            ::open(data_file)
            .expect("Cannot find the user data file");
        let mut data = String::new();
        data_content.read_to_string(&mut data).expect("Should not have I/O errors");
        info!("data={}", data);

        let data: Value = serde_json::from_str(&data).unwrap();
        data
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn add_dummy_file() {
        let data = r#"{"name": "Alice", "age": 25}"#;
        std::fs::write("./user_data_1.json", data).expect("Unable to write file");
        std::fs::write("./user_data_2.json", data).expect("Unable to write file");
    }

    fn remove_dummy_file() {
        std::fs::remove_file("./user_data_1.json").expect("Unable to remove file");
        std::fs::remove_file("./user_data_2.json").expect("Unable to remove file");
    }

    // on cargo llvm-cov nextest, this test is failing because of SIGSEGV (due to unsafe behavior), but not on cargo test
    #[ignore]
    #[test]
    fn test_get_user_data_tokens() {
        add_dummy_file();
        std::env::set_var(
            "DVR_MODULE_PATH",
            "/home/builder/zkPass/target/release/libdvr_client.so"
        );

        let data_issuer = DataIssuer;
        let data_files = vec![
            ("tag1".to_string(), "./user_data_1.json".to_string()),
            ("tag2".to_string(), "./user_data_2.json".to_string())
        ]
            .into_iter()
            .collect();

        let user_data_tokens = data_issuer.get_user_data_tokens(data_files);
        assert_eq!(user_data_tokens.len(), 2);
        remove_dummy_file();
    }

    #[test]
    #[should_panic]
    fn test_read_user_data_token_error() {
        let data_issuer = DataIssuer;
        let data_file = "./user_data_3.json".to_string();

        let _ = data_issuer.read_user_data_token(&data_file);
    }
}