diff options
Diffstat (limited to 'rustore-apk-get')
| -rwxr-xr-x | rustore-apk-get | 112 |
1 files changed, 102 insertions, 10 deletions
diff --git a/rustore-apk-get b/rustore-apk-get index a5f7b20..088c5b2 100755 --- a/rustore-apk-get +++ b/rustore-apk-get @@ -5,9 +5,10 @@ set -euo pipefail # See https://github.com/ImranR98/Obtainium/blob/main/lib/app_sources/rustore.dart # @describe Download apk from https://rustore.ru -# @meta require-tools curl,jq,fzf,xxh64sum +# @meta require-tools curl,jq,fzf,xxh64sum,base64,openssl # @flag -u --update download file if it exists but hash not match # @flag -v --verbose verbose output +# @flag --persistent-device-id Use persistent device ID (stored in ~/.cache/rustore-apk-get-device-id.txt) # @flag --whats-new show whats new before download # @flag --save-json save package info JSON # @flag --extract extract ZIP on download @@ -15,6 +16,17 @@ set -euo pipefail # @option -o --output=`_default_output` Output directory/file # @arg package_name Application package name # @env RU_GOV_CERTIFICATE_PATH! Path to Russian Ministry of Digital Development (MinCifry) certificates +# @env RU_STORE_DEVICE_ID_FILE Path to device id file location +# @env RU_STORE_HMAC_KEY=K+eeiCbnVFnZ71KEVal0g5siHaX6v6drh8upeLgEPoU= HMAC key for request signing +# @env RU_STORE_APK_CERT=Zh8ggo73gN4LebxZ8mowhkMWNV8w5Pkc+hSiB5GDmRQ= APK certificate for signing +# @env RU_STORE_DEVICE_MANUFACTURER_NAME=Google Device manufacturer name +# @env RU_STORE_DEVICE_MODEL_NAME=Pixel 8 Pro Device model name +# @env RU_STORE_DEVICE_TYPE=mobile Device type +# @env RU_STORE_ANDROID_SDK_VER=36 Android SDK version +# @env RU_STORE_FIRMWARE_VER=16 Firmware version +# @env RU_STORE_FIRMWARE_LANG=ru Firmware language +# @env RU_STORE_VER_CODE=1105002 RuStore version code +# @env RU_STORE_AGENT=RuStore/1.105.0.2 User agent string BASE_URL=https://backapi.rustore.ru PAGE_SIZE=10 @@ -23,19 +35,41 @@ _default_output() { pwd } +_generate_device_id() { + local part1 part2 + part1=$(LC_ALL=C tr -dc "a-z0-9" </dev/urandom | head -c 16) + part2=$(LC_ALL=C tr -dc "0-9" </dev/urandom | head -c 10) + + echo "${part1}-${part2}" +} + _curl() { - curl \ - --cacert "$RU_GOV_CERTIFICATE_PATH" \ - --retry 3 \ - --header 'ruStoreVerCode: 1105002' \ - "$@" + local curl_opts=( + --cacert "$RU_GOV_CERTIFICATE_PATH" + --fail-with-body + --retry 10 + --retry-delay 2 + --retry-all-errors + ) + + for header in "${headers[@]}"; do + curl_opts+=(--header "$header") + done + + curl "${curl_opts[@]}" "$@" } _aria2() { - aria2c \ - --ca-certificate="$RU_GOV_CERTIFICATE_PATH" \ - --header='ruStoreVerCode: 1105002' \ - "$@" + local aria2c_opts=( + --ca-certificate="$RU_GOV_CERTIFICATE_PATH" + --quiet + ) + + for header in "${headers[@]}"; do + aria2c_opts+=(--header="$header") + done + + aria2c "${aria2c_opts[@]}" "$@" } _download_file() { @@ -129,7 +163,65 @@ download_apk() { fi } +_configure_client() { + local content_type="application/json; charset=utf-8" + local device_id + if [ -n "${argc_persistent_device_id:-}" ]; then + local device_filename="${RU_STORE_DEVICE_ID_FILE:-$HOME/.cache/rustore-apk-get-device-id.txt}" + if [ -s "$device_filename" ]; then + device_id=$(<"$device_filename") + else + device_id=$(_generate_device_id) + echo "$device_id" >"$device_filename" + fi + else + device_id=$(_generate_device_id) + fi + + local device_model="$RU_STORE_DEVICE_MANUFACTURER_NAME $RU_STORE_DEVICE_MODEL_NAME" + local user_agent="$RU_STORE_AGENT (Android $RU_STORE_FIRMWARE_VER; SDK $RU_STORE_ANDROID_SDK_VER; arm64-v8a; $device_model; $RU_STORE_FIRMWARE_LANG)" + + headers=() + headers+=("Content-Type:$content_type") + headers+=("User-Agent:$user_agent") + headers+=("androidSdkVer:$RU_STORE_ANDROID_SDK_VER") + headers+=("deviceId:$device_id") + headers+=("deviceManufacturerName:$RU_STORE_DEVICE_MANUFACTURER_NAME") + headers+=("deviceModelName:$RU_STORE_DEVICE_MODEL_NAME") + headers+=("deviceModel:$device_model") + headers+=("deviceType:$RU_STORE_DEVICE_TYPE") + headers+=("firmwareVer:$RU_STORE_FIRMWARE_VER") + headers+=("firmwareLang:$RU_STORE_FIRMWARE_LANG") + headers+=("ruStoreVerCode:$RU_STORE_VER_CODE") +} + +_get_signature() { + local nonce_b64 + nonce_b64=$( + _curl_silent \ + --request POST \ + --url https://api.rustore.ru/v1/secure/nonce | jq -r .nonce + ) + + local rustore_hmac_key_bin rustore_apk_cert_bin nonce_bin + rustore_hmac_key_bin=$(echo -n "$RU_STORE_HMAC_KEY" | base64 -d) + rustore_apk_cert_bin=$(echo -n "$RU_STORE_APK_CERT" | base64 -d) + nonce_bin=$(echo -n "$nonce_b64" | base64 -d) + local signature + signature=$( + { + echo -n "$nonce_bin" + echo -n "$rustore_apk_cert_bin" + } | openssl dgst -sha256 -hmac "$rustore_hmac_key_bin" -binary | base64 + ) + + headers+=("X-Client-Signature:$signature") +} + main() { + _configure_client + _get_signature + local package_name="${argc_package_name:-}" if [[ "$package_name" == http* ]]; then package_name=$(echo "$package_name" | grep -oP '(?<=/app/)[^/?]+') |
