diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index d5d943a..a8f9e1f 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -119,3 +119,17 @@ jobs: cd opendds-smart-lock ./smart-lock android build-toolchain-x86 ./smart-lock android compile + + build_certs_downloader: + + runs-on: ubuntu-22.04 + + steps: + - name: checkout opendds-smart-lock + uses: actions/checkout@v3 + with: + path: opendds-smart-lock + - name: compile rust certs downloader + run: | + cd opendds-smart-lock/src/certs_downloader + cargo build --release diff --git a/src/certs_downloader/Cargo.toml b/src/certs_downloader/Cargo.toml new file mode 100644 index 0000000..0e85173 --- /dev/null +++ b/src/certs_downloader/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "certs_downloader" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +reqwest = { version = "0.11.18", features = ["blocking", "cookies"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" diff --git a/src/certs_downloader/src/lib.rs b/src/certs_downloader/src/lib.rs new file mode 100644 index 0000000..03c93a0 --- /dev/null +++ b/src/certs_downloader/src/lib.rs @@ -0,0 +1,151 @@ +use reqwest::blocking::Client; +use serde::{Deserialize, Serialize}; +use std::fs; +use std::io::Write; + +pub struct Config { + api_url: String, + username: String, + password: String, + nonce: String, + directory: String, + part_subdir: Option, + id_ca_subdir: Option, + perm_ca_subdir: Option, +} + +impl Config { + pub fn build(args: &Vec) -> Result { + if args.len() < 6 { + let usage_msg = format!("Usage: cargo run [part_dir] [id_ca_dir] [perm_ca_dir]\n\ + With:\n\ + {0: <15} API URL of the DDS Permission Manager (DPM)\n\ + {1: <15} Username of the application in DPM\n\ + {2: <15} Password of the application in DPM\n\ + {3: <15} A nonce string\n\ + {4: <15} Top level directory to store the docs\n\ + {5: <15} Subdirectory to store docs specific to this participant (optional)\n\ + {6: <15} Subdirectory to store identity CA doc (optional)\n\ + {7: <15} Subdirectory to store permissions CA doc (optional)", + "", "", "", "", "", "[part_dir]", "[id_ca_dir]", "[perm_ca_dir]"); + return Err(usage_msg); + } + let part_subdir_arg = if args.len() >= 7 { + Some(args[6].clone()) + } else { + None + }; + let id_ca_subdir_arg = if args.len() >= 8 { + Some(args[7].clone()) + } else { + None + }; + let perm_ca_subdir_arg = if args.len() >= 9 { + Some(args[8].clone()) + } else { + None + }; + Ok(Config { + api_url: args[1].clone(), + username: args[2].clone(), + password: args[3].clone(), + nonce: args[4].clone(), + directory: args[5].clone(), + part_subdir: part_subdir_arg, + id_ca_subdir: id_ca_subdir_arg, + perm_ca_subdir: perm_ca_subdir_arg, + }) + } +} + +#[derive(Serialize, Deserialize)] +struct KeyPair { + private: String, + public: String, +} + +pub fn download_certs(config: &Config) -> Result<(), Box> { + let client = Client::builder().cookie_store(true).build().unwrap(); + let login_url = format!("{}/login", config.api_url); + let credential = format!( + "{{\"username\":\"{}\", \"password\":\"{}\"}}", + config.username, config.password + ); + let _auth_resp = client + .post(login_url) + .header(reqwest::header::CONTENT_TYPE, "application/json") + .body(credential) + .send(); + + let base_url = format!("{}/applications", config.api_url); + let id_ca_dir = format!( + "{}/{}", + config.directory, + if config.id_ca_subdir.is_some() { + config.id_ca_subdir.clone().unwrap() + } else { + String::from("") + } + ); + let perm_ca_dir = format!( + "{}/{}", + config.directory, + if config.perm_ca_subdir.is_some() { + config.perm_ca_subdir.clone().unwrap() + } else { + String::from("") + } + ); + let part_dir = format!( + "{}/{}", + config.directory, + if config.part_subdir.is_some() { + config.part_subdir.clone().unwrap() + } else { + String::from("") + } + ); + download_file(&client, &base_url, "identity_ca.pem", None, &id_ca_dir)?; + download_file(&client, &base_url, "permissions_ca.pem", None, &perm_ca_dir)?; + download_file(&client, &base_url, "governance.xml.p7s", None, &config.directory)?; + download_file(&client, &base_url, "permissions.xml.p7s", Some(&config.nonce), &part_dir)?; + + let kp_str = get_body(&client, &base_url, "key_pair", Some(&config.nonce))?; + let kp: KeyPair = serde_json::from_str(&kp_str)?; + let public_file = format!("{}/identity.pem", part_dir); + let private_file = format!("{}/identity_key.pem", part_dir); + fs::File::create(public_file)?.write_all(kp.public.as_bytes())?; + fs::File::create(private_file)?.write_all(kp.private.as_bytes())?; + Ok(()) +} + +fn download_file( + client: &Client, + base_url: &str, + filename: &str, + nonce: Option<&str>, + directory: &str, +) -> Result<(), Box> { + let body = get_body(client, base_url, filename, nonce)?; + let path = format!("{}/{}", directory, filename); + fs::create_dir_all(directory)?; + let mut file = fs::File::create(path)?; + file.write_all(body.as_bytes())?; + Ok(()) +} + +fn get_body( + client: &Client, + base_url: &str, + filename: &str, + nonce: Option<&str>, +) -> Result> { + let url; + if nonce.is_some() { + url = format!("{}/{}?nonce={}", base_url, filename, nonce.unwrap()); + } else { + url = format!("{}/{}", base_url, filename); + } + let body = client.get(&url).send()?.text()?; + Ok(body) +} diff --git a/src/certs_downloader/src/main.rs b/src/certs_downloader/src/main.rs new file mode 100644 index 0000000..4ed550e --- /dev/null +++ b/src/certs_downloader/src/main.rs @@ -0,0 +1,20 @@ +use std::env; +use std::process; + +use certs_downloader::Config; +use certs_downloader::download_certs; + +fn main() { + let args: Vec = env::args().collect(); + let config = Config::build(&args).unwrap_or_else(|err| { + eprintln!("{err}"); + process::exit(1); + }); + + match download_certs(&config) { + Ok(_) => (), + Err(err) => { + eprintln!("download_certs failed: {:?}", err); + } + } +} diff --git a/src/smartlock.ini b/src/smartlock.ini index a51c368..a3a69a2 100644 --- a/src/smartlock.ini +++ b/src/smartlock.ini @@ -1,5 +1,5 @@ [smartlock] topic_prefix = "C.53." domain_id = 1 -username = "54" +username = "47" api_url = "https://dpm.unityfoundation.io/api" diff --git a/src/smartlock.sh b/src/smartlock.sh index e9f06bc..6a99ece 100755 --- a/src/smartlock.sh +++ b/src/smartlock.sh @@ -15,6 +15,7 @@ smartlock_ini=${BASE_PATH}/smartlock/smartlock.ini SECURITY=${SMARTLOCK_SECURE:-0} CMD=start LOCK= +CURL_CERTS_DOWNLOADER=0 while (( $# > 0 )); do case "$1" in --security) @@ -22,13 +23,17 @@ while (( $# > 0 )); do shift ;; -h|--help) - echo "Usage: smartlock.sh [--security] [--lock LOCK_ID] start | stop | restart | start-system" + echo "Usage: smartlock.sh [--security] [--lock LOCK_ID] [--curl-certs-downloader] start | stop | restart | start-system" exit ;; --lock) LOCK="$2" shift 2 ;; + --curl-certs-downloader) + CURL_CERTS_DOWNLOADER=1 + shift + ;; start) CMD=start shift @@ -83,21 +88,21 @@ fi echo "CMD: '$CMD', SECURITY: '$SECURITY', LOCK_ID: '$LOCK', SECURITY_ARGS: '$SECURITY_ARGS'" -function update_certs { - APP_PASSWORD=$(cat ${BASE_PATH}/dpm_password) - APP_NONCE=${LOCK} - API_URL=$(grep api_url ${smartlock_ini} | sed 's/api_url *= *"//; s/".*//') - USERNAME=$(grep username ${smartlock_ini} | sed 's/username *= *"//; s/".*//') +function update_certs_curl { + API_URL=$1 + USERNAME=$2 + PASSWORD=$3 + NONCE=$4 mkdir -p ${cert_dir}/id_ca ${cert_dir}/${LOCK} ${cert_dir}/perm_ca - curl -c cookies.txt -H'Content-Type: application/json' -d"{\"username\":\"${USERNAME}\",\"password\":\"$APP_PASSWORD\"}" ${API_URL}/login + curl -c cookies.txt -H'Content-Type: application/json' -d"{\"username\":\"${USERNAME}\",\"password\":\"${PASSWORD}\"}" ${API_URL}/login curl --silent -b cookies.txt "${API_URL}/applications/identity_ca.pem" > ${ID_CA} curl --silent -b cookies.txt "${API_URL}/applications/permissions_ca.pem" > ${PERM_CA} curl --silent -b cookies.txt "${API_URL}/applications/governance.xml.p7s" > ${PERM_GOV} - curl --silent -b cookies.txt "${API_URL}/applications/key_pair?nonce=${APP_NONCE}" > key-pair - curl --silent -b cookies.txt "${API_URL}/applications/permissions.xml.p7s?nonce=${APP_NONCE}" > ${PERM_PERMS} + curl --silent -b cookies.txt "${API_URL}/applications/key_pair?nonce=${NONCE}" > key-pair + curl --silent -b cookies.txt "${API_URL}/applications/permissions.xml.p7s?nonce=${NONCE}" > ${PERM_PERMS} jq -r '.public' key-pair > ${ID_CERT} jq -r '.private' key-pair > ${ID_PKEY} @@ -105,6 +110,37 @@ function update_certs { rm -f cookies.txt key-pair } +function update_certs_rust { + API_URL=$1 + USERNAME=$2 + PASSWORD=$3 + NONCE=$4 + + if ! command -v rustc &> /dev/null + then + echo "ERROR: Rust is not installed! Install it and try again or use the --curl-certs-downloader option." + exit 1 + fi + + cd certs_downloader + cargo build --release + cargo run $API_URL $USERNAME $PASSWORD $NONCE $cert_dir $LOCK id_ca perm_ca + cd .. +} + +function update_certs { + API_URL=$(grep api_url ${smartlock_ini} | sed 's/api_url *= *"//; s/".*//') + USERNAME=$(grep username ${smartlock_ini} | sed 's/username *= *"//; s/".*//') + PASSWORD=$(cat ${BASE_PATH}/dpm_password) + NONCE=${LOCK} + + if (( $CURL_CERTS_DOWNLOADER )); then + update_certs_curl $API_URL $USERNAME $PASSWORD $NONCE + else + update_certs_rust $API_URL $USERNAME $PASSWORD $NONCE + fi +} + PID_FILE=${BASE_PATH}/smartlock.pid start() { if (( $SECURITY )); then @@ -162,4 +198,3 @@ case "$CMD" in exit 1 ;; esac -