summaryrefslogtreecommitdiff
path: root/rustore-apk-get
diff options
context:
space:
mode:
authorAnton Bobov <anton@bobov.name>2026-08-20 15:06:33 +0500
committerAnton Bobov <anton@bobov.name>2026-08-20 15:06:33 +0500
commit6cc90e320ff6660677708efaeac121fea699c082 (patch)
tree4d254ed75fd4a4e6a4d913048cb4022852d7f3b6 /rustore-apk-get
parentfa16152d6b7f8cfb7d601ec9e4bda27c3693e697 (diff)
rustore-apk-get: add secure sessionHEADmaster
Diffstat (limited to 'rustore-apk-get')
-rwxr-xr-xrustore-apk-get112
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/)[^/?]+')