spisal 24. 4. 2026
glej tudi: https://sijanec.eu/nlb.shtml
Med prijavo v spletno banko NLB Klik sem opazil, da prijava z enkratnim geslom ni več mogoča. 2026 je torej leto, ko bodo uporabniki NLB Klika prisiljeni v uporabo mobilnih telefonov Android ali iOS, s čimer se bo dodatno utrdil monopol Googla in Appla. Telefon ne sme biti odklenjen (rooted), mora pa imeti dostop do interneta in funkcionalno zadnjo kamero. V tej objavi predstavim način prijavljanja v NLB Klik na svoboden način.
Sam sicer uporabljam NLB Klik avtentikator, saj alternativne opcije že od petega maja 2021 ni, vendar sem vseeno proti uporabi le-tega. Po nekaj pokukih v NLB Klik aplikacijo sem ugotovil, da za avtorizacijo prijave s QR kodo uporablja odprtokoden avtorizacijski sistem podjetja Wultra, imenovan PowerAuth, ki ga je verjetno NLB od omenjenega podjetja naročila. Ta algoritem ima uradno odprtokodno implementacijo, a v zaledju registracija še vedno temelji na starem skritem algoritmu za enkratne kode, ki nima uradne odprtokodne implementacije. NLB Klika ne boste spravili na Android telefon, če le-ta nima Googlovih storitev.
Ker je GitHub znan po izbrisih programov, je varnostna kopija dostopna tukaj.
Za implementacijo te metode ne boste potrebovali NLB Klik aplikacije ali Googlovih storitev.
Uporaba je enostavna kot 0-1-2-3.
Na računalniku potrebujemo sledeče programe:
Telefon ni potreben.
Potrebovali bomo serijsko številko (dobimo jo na email) in aktivacijsko kodo (na sms), za kateri zaprosimo pri kontaktnem centru 24/7 na tel. št. 01 477 2000.
Če smo v preteklosti shranili OTP generator in se spomnimo serijske številke (oz. jo laho izbrskamo iz e-pošte), potem ne potrebujemo novih kod.
Aktivnih imamo lahko več instanc klika naenkrat, torej z aktivacijo na računalniku ne boste pokvarili aplikacije na telefonu. Hkrati to pomeni, da če nepridiprav dobi v roke naše kode, tega ne bomo nikoli izvedeli. V to, ali je možno posamezne kode deaktivirati, se nisem poglabljal.
S to skripto izvedemo registracijo aktivacijskih kod. Poženemo jo
samo enkrat. Ustvarila nam bo statičen config.json in
skrivni pa_token.txt ter pa_status.json, s
katerimi lahko avtoriziramo dostop komurkoli. S temi datotekami ravnajmo
pazljivo.
#!/usr/bin/env python3 # /// script # requires-python = ">=3.14" # dependencies = [ # "oath>=1.4.4", # ] # /// import argparse import base64 import hashlib import hmac import json import random import re import subprocess import sys import time from pathlib import Path from typing import Optional from urllib.parse import urlencode from urllib.request import Request, urlopen from urllib.error import HTTPError from oath import totp # Datoteke za powerauth-java-cmd STATUS_FILE = Path("./pa_status.json") CONFIG_FILE = Path("./config.json") # Vmesne datoteke (lahko so /dev/null, če jih ne želimo shranjevati) OTP_REGISTERED_FLAG = Path("./otp_registered.flag") ACTIVATION_FILE = Path("./pa_activation_code.txt") OTP_SEED_FILE = Path("./otp_seed.txt") PA_ACTIVATED_FLAG = Path("./pa_activated.flag") # Izhodna datoteka TOKEN_FILE = Path("./pa_token.txt") # https://github.com/wultra/powerauth-cmd-tool/releases/download/1.10.1/powerauth-java-cmd.jar POWERAUTH_JAR = Path("./powerauth-java-cmd.jar") def http_post( url: str, data: dict, is_json: bool, headers: Optional[dict] = None ) -> str: content_type = ( "application/json; charset=UTF-8" if is_json else "application/x-www-form-urlencoded" ) headers = (headers or {}).copy() headers["Content-Type"] = content_type body = json.dumps(data) if is_json else urlencode(data) request = Request(url, data=body.encode("utf-8"), headers=headers, method="POST") print(request.method, request.full_url, "with body:", body) try: with urlopen(request) as response: return response.read().decode("utf-8") except HTTPError as e: raise RuntimeError( f"HTTP error {e.code}: {e.reason}\n{e.headers}\n{e.read().decode('utf-8')}" ) from e def generate_config(): # Hardcoded in the apk, same for everyone. config = ( "ARCCQ47aWyZ5ryvwWQaYh2GkEF3PL/weCkvypJTVH+Tl+nkBAUEEFi4EhkGausAAnHPSVG" "qiya+PlMq4eZRHn5yQHIjJHs0X3BmXx5GnFIgS7t8kmj5+3ELb31WntQEzcoyUOmmDyQ==" ) text = json.dumps({"applicationName": "NLB Klik", "mobileSdkConfig": config}) CONFIG_FILE.write_text(text + "\n", encoding="utf-8") def require_json_field(payload: str, field: str, error_message: str) -> str: try: data = json.loads(payload) except json.JSONDecodeError as exc: raise RuntimeError(f"Neveljaven JSON odgovor: {payload}") from exc value = data.get(field) if not value: print( f"Nepričakovan JSON odgovor; ni polja '{field}': {payload}", file=sys.stderr ) raise RuntimeError(error_message) return value def run_powerauth_command(*args: str) -> str: command = [ "java", "-jar", str(POWERAUTH_JAR), "--url", "https://nlb-si-mtoken.wultra.app/enrollment-server", *args, ] try: result = subprocess.run( command, check=True, capture_output=True, text=True, ) except FileNotFoundError as exc: raise RuntimeError("Ukaz java ni na voljo") from exc except subprocess.CalledProcessError as exc: stderr = exc.stderr.strip() raise RuntimeError(stderr or "PowerAuth ukaz ni uspel") from exc return result.stdout def parse_token_fields(payload: str) -> tuple[str, str]: """Poberi tokenId in tokenSecret iz PowerAuthovega izhoda.""" token_id_match = re.search(r'"tokenId"\s*:\s*"([^"]+)"', payload) token_secret_match = re.search(r'"tokenSecret"\s*:\s*"([^"]+)"', payload) if not token_id_match or not token_secret_match: print("Nepričakovan izhod PowerAuth ukaza:", payload, file=sys.stderr) raise RuntimeError("Nisem dobil tokenId in tokenSecret :(") return token_id_match.group(1), token_secret_match.group(1) def luhn_digit(length: int, value: int) -> int: """Izračunaj Luhnovo kontrolno števko.""" total = 0 for i in range(length): digit = value % 10 value //= 10 if i % 2 == 0: digit = [0, 2, 4, 6, 8, 1, 3, 5, 7, 9][digit] total += digit mod = total % 10 return 0 if mod == 0 else 10 - mod def validate_activation(code: str) -> bool: """Preveri, ali je aktivacijska koda veljavna glede na Luhnovo formulo.""" return len(code) == 16 and luhn_digit(15, int(code[:-1])) == int(code[-1]) def derive_regcode( activation_bytes: bytes, serial_bytes: bytes, rng_bytes: Optional[bytes] ) -> str: """Izračunaj registracijsko kodo za OTP generator.""" rng_bytes = rng_bytes or random.randbytes(2) password = activation_bytes + rng_bytes derived_key = hashlib.pbkdf2_hmac("sha256", password, serial_bytes, 8, 16) digest = hmac.new(derived_key, serial_bytes, hashlib.sha256).digest() derived13 = ((digest[-2] & 0x1F) << 8) | digest[-1] value = (derived13 << 16) | int.from_bytes(rng_bytes, "big") code = f"{value}{luhn_digit(9, value)}".zfill(10) return code[:5] + "-" + code[5:] def main() -> int: parser = argparse.ArgumentParser(description="Registrira NLB Klik avtorizator") parser.add_argument("serial", help="serijska številka (iz emaila)") parser.add_argument("activation", help="aktivacijska koda (iz smsa)") parser.add_argument("rng_hex", nargs="?", help="2 naključna bajta, npr. '1337'") args = parser.parse_args() serial = args.serial.replace("-", "").strip() activation = args.activation.replace("-", "").strip() if not OTP_SEED_FILE.exists(): # lahko nastavimo po želji, npr. rng_hex = "1337" rng_hex = args.rng_hex or random.randbytes(2).hex() assert re.fullmatch(r"[0-9a-fA-F]{4}", rng_hex), "neveljavn rng_hex" serial_bytes = serial.encode("utf-8") assert validate_activation(activation), "Neveljavna aktivacijska koda" activation_bytes = int(activation[:-1]).to_bytes(7, "big") rng_bytes = bytes.fromhex(rng_hex) regcode = derive_regcode(activation_bytes, serial_bytes, rng_bytes) key = hashlib.pbkdf2_hmac( hash_name="sha256", password=activation_bytes + rng_bytes, salt=serial_bytes, iterations=8, dklen=16, ) print("OTP seme:", key.hex()) print("Registracijska koda:", regcode) OTP_SEED_FILE.write_text(key.hex() + "\n" + regcode, encoding="utf-8") else: print(f"{OTP_SEED_FILE} že obstaja.") key_hex, regcode = OTP_SEED_FILE.read_text(encoding="utf-8").strip().split("\n") key = bytes.fromhex(key_hex) otp = totp(key.hex(), hash=hashlib.sha256, t=int(time.time()), format="dec8") if not OTP_REGISTERED_FLAG.exists(): # stupid identifiers deviceid = str(random.randint(0, 2**32 - 1)) instanceid = base64.b64encode(random.randbytes(8)) http_post( "https://aam.nlb.si/igst/txnpoll", { "apiversion": "6", "platform": "ANDROID", "supportsoffline": "1", "serialnumber": serial, "deviceid": deviceid, "supportstransactions": "1", "cmd": "enroll", "instanceid": instanceid, "version": "9.1.1", "notifyenabled": "0", "supportsonline": "1", "type": "TOKEN", "appid": "co.infinum.nlb", "appscheme": "etrust", "otp": str(otp), "regcode": regcode, }, is_json=False, ) OTP_REGISTERED_FLAG.write_text( f"SERIAL={serial}\nACTIVATION={activation}\nREGCODE={regcode}\nDEVICEID={deviceid}\nINSTANCEID={instanceid.decode()}\n", encoding="utf-8", ) print("OTP generator registriran.") else: print(f"{OTP_REGISTERED_FLAG} že obstaja.") if not CONFIG_FILE.exists(): print(f"Generiram {CONFIG_FILE} ...") generate_config() else: print(f"{CONFIG_FILE} že obstaja.") if not ACTIVATION_FILE.exists(): print("Pridobivam OIDC žeton ...") resp = http_post( "https://klik.nlb.si/authentication/realms/retail/protocol/openid-connect/token", { "client_id": "klik-device-registration", "grant_type": "password", "username": str(serial), "password": str(otp), }, is_json=False, ) oidc = require_json_field(resp, "access_token", "Ni OIDC žetona :(") print("Registriram Wultra uporabnika ...") resp = http_post( "https://klik.nlb.si/authentication/realms/retail/wultra-auth/register-wultra-user", {"appId": "Klik"}, headers={"Authorization": f"Bearer {oidc}"}, is_json=True, ) pa_activ = require_json_field(resp, "activationCode", "Ni aktivacijske kode :(") ACTIVATION_FILE.write_text(f"{pa_activ}\n", encoding="utf-8") print("PA registracija uspela.") else: print(f"{ACTIVATION_FILE} že obstaja.") pa_activ = ACTIVATION_FILE.read_text(encoding="utf-8").strip() if not PA_ACTIVATED_FLAG.exists(): print("Aktiviram PowerAuth ...") resp = run_powerauth_command( "--status-file", str(STATUS_FILE), "--config-file", str(CONFIG_FILE), "--method", "create", "--version", "3.3", "--password", "1234", "--algorithm", "EC_P384_ML_L5", "--activation-code", pa_activ, ) PA_ACTIVATED_FLAG.write_text(resp, encoding="utf-8") print("PowerAuth aktiviran.") else: print(f"{PA_ACTIVATED_FLAG} že obstaja. PowerAuth je verjetno že aktiviran.") if not TOKEN_FILE.exists(): print("Generiram avtorizacijski zeton ...") token_response = run_powerauth_command( "--status-file", str(STATUS_FILE), "--config-file", str(CONFIG_FILE), "--method", "create-token", "--version", "3.3", "--password", "1234", "--auth-code-type", "possession_knowledge", ) token_id, token_secret = parse_token_fields(token_response) TOKEN_FILE.write_text( f"TOKEN_ID={token_id}\nTOKEN_SECRET={token_secret}\n", encoding="utf-8", ) print(f"Žeton shranjen v {TOKEN_FILE}") else: print(f"{TOKEN_FILE} že obstaja. Pojdi na pivo") if __name__ == "__main__": raise SystemExit(main())
./config.json in datotek s stanjem
pa_status.json in pa_token.txt, ki jih dobimo
s prejšnjo skripto.
#!/bin/bash set -euo pipefail STATUS_FILE="./pa_status.json" CONFIG_FILE="./config.json" TOKEN_FILE="./pa_token.txt" source "$TOKEN_FILE" #echo "Token ID: $TOKEN_ID" #echo "Token Secret: $TOKEN_SECRET" IP_ADDR=$(curl -sS --fail https://api64.ipify.org) TIME=$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ") echo "Time: $TIME" echo "IP Address: $IP_ADDR" read -p "Vnesi avtorizacijski URL: " URL OPERATION_ID=$(echo "$URL" | sed -n 's/.*[?&]operationId=\([^&]*\).*/\1/p') OTP=$(echo "$URL" | sed -n 's/.*[?&]proximityOtp=\([^&]*\).*/\1/p') cat <<EOT > request.json { "requestObject": { "id": "$OPERATION_ID" } } EOT java -jar powerauth-java-cmd.jar \ --url "https://nlb-si-mtoken.wultra.app/enrollment-server/api/auth/token/app/operation/detail/claim" \ --status-file "$STATUS_FILE" \ --config-file "$CONFIG_FILE" \ --method "validate-token" \ --http-method "POST" \ --version "3.3" \ --data-file "request.json" \ --token-id "$TOKEN_ID" \ --token-secret "$TOKEN_SECRET" 2>/dev/null cat <<EOT > request.json { "requestObject": { "data": "A2*Tspletna banka NLB Klik*T$IP_ADDR*T", "id": "$OPERATION_ID", "proximityCheck": { "otp": "$OTP", "timestampReceived": "$TIME", "timestampSent": "$TIME", "type": "DEEPLINK" } } } EOT java -jar powerauth-java-cmd.jar \ --url "https://nlb-si-mtoken.wultra.app/enrollment-server/api/auth/token/app/operation/authorize" \ --status-file "$STATUS_FILE" \ --config-file "$CONFIG_FILE" \ --method "authenticate" \ --http-method "POST" \ --version "3.3" \ --resource-id "/operation/authorize" \ --auth-code-type "possession_knowledge" \ --data-file "request.json" \ --password "1234" 2>/dev/null
Izhodišče za raziskavo je analiza Stephena Shkardoona.
Entrustov algoritem sestavi seme za otp generator iz serijske številke in aktivacijske kode in 2 naključnih bajtov, ki se generirata na napravi. Naključna bajta se v obliki registracijske kode pošljeta na strežnik, da bo le-ta lahko validiral naše žetone.
Ideja je, da napadalec, ki prestreže serijsko številko in aktivacijsko kodo, ne more ukrasti našega generatorja enkratnih gesel, ker ne ve registracijske kode. Če nam uspe, da smo prvi opravili registracijo, postaneta serijska številka in aktivacijska koda neuporabni.
Stephen nam predstavi algoritem, s katerim lahko iz veljavne trojice kod pridobimo seme za generator, ne vemo pa kako generirati veljavne registracijske kode.
Na pomoč nam priskoči veliki jezikovni model, ki pregleda neberljivo vzvratno prevedeno kodo iz Klik apkja in jo prevede v Python.
def derive_regcode(activation_bytes: bytes, serial_bytes: bytes): """Izračunaj registracijsko kodo za OTP generator.""" rng_bytes = random.randbytes(2) password = activation_bytes + rng_bytes derived_key = hashlib.pbkdf2_hmac("sha256", password, serial_bytes, 8, 16) digest = hmac.new(derived_key, serial_bytes, hashlib.sha256).digest() derived13 = ((digest[-2] & 0x1F) << 8) | digest[-1] value = (derived13 << 16) | int.from_bytes(rng_bytes, "big") code = f"{value}{luhn_digit(9, value)}".zfill(10) return code[:5] + "-" + code[5:]
Sedaj lahko svojo registracijsko kodo pošljemo na
aam.nlb.si in jo uspešno validiramo.
apiversion=6 platform=ANDROID supportsoffline=1 serialnumber=serijska številka deviceid=9-mestno število supportstransactions=1 cmd=enroll instanceid=base64 version=9.1.1 notifyenabled=0 supportsonline=1 type=TOKEN appid=co.infinum.nlb appscheme=etrust otp=trenutni TOTP regcode=registracijska koda; dvakrat po 5 števk, ločenih s minusom
{
"apiversion": "6",
"status": "OK"
}
Ključno vlogo igra orodje mitmproxy. Z njim lahko beremo dešifrirane HTTPS zahtevke aplikacije Klik in odkrijemo delovanje APIja, ki ga bomo nato simulirali z računalnikom.
Če ignoriramo glave, izgleda naloga precej preprosta. Na žalost pa
tak zahtevek sam po sebi ni veljaven; potrebujemo glavo
x-powerauth-authorization.
Ker je PowerAuth za namizne računalnike že implementiran, bo najlažje, da preprosto sledimo celotnemu postopku registracije. Za uporabo uradnega CLI orodja potrebujemo datoteko s konfiguracijo in datoteko stanja. Slednja se ustvari (in nadalje upravlja) avtomatsko, ko opravimo API klic za registracijo.
MobileSdkConfig je trdokodiran v Klik aplikaciji
(sources/si/nlb/klik/retail/KlikRetailApplication.java) in
je za vse uporabnike enak. (Kako dobim
apk?). ApplicationName preberemo iz
resources/res/values/strings.xml. Sestavimo konfiguracijsko
datoteko:
{
"applicationName": "NLB Klik",
"mobileSdkConfig": "ARCCQ47aWyZ5ryvwWQaYh2GkEF3PL/weCkvypJTVH+Tl+nkBAUEEFi4EhkGausAAnHPSVGqiya+PlMq4eZRHn5yQHIjJHs0X3BmXx5GnFIgS7t8kmj5+3ELb31WntQEzcoyUOmmDyQ=="
}Poglejmo si, kako od klik.nlb.si dobimo aktivacijsko
kodo za PowerAuth.
Najprej od klik.nlb.si zahtevamo openid-connect žeton s
pomočjo enkratnega gesla (star sistem).
client_id=klik-device-registration grant_type=password username=Serijska številka password=Entrust OTP koda device_token=DEVICE_TOKEN platform=ANDROID
{
"access_token": "JWT žeton",
"expires_in": 30,
"refresh_expires_in": 0,
"token_type": "Bearer",
"not-before-policy": 0,
"session_state": "UUIDv4, ni pomembno",
"scope": "microprofile-jwt"
}
Z OIDC žetonom lahko zahtevamo žeton za aktivacijo uporabnika Wultra.
{
"appId": "Klik"
}
{
"registrationId": "UUIDv4, ne bomo potrebovali.",
"activationQrCodeData": "Z znakom # ločena activationCode in activationCodeSignature.",
"activationCode": "Aktivacijska koda. Sveti gral.",
"activationCodeSignature": "70 bajtov kodiranih z base64."
}
Če smo bili uspešni, imamo zdaj PowerAuth aktivacijsko kodo. Od tu
naprej so interakcije šifrirane tudi na aplikacijskem nivoju, zato jih
ne moremo zlahka analizirati. Posnemali jih bomo s
powerauth-java-cmd.jar.
Opazimo, da smo zaključili s klik.nlb.si, v nadaljevanju
imamo opravka le še z nlb-si-mtoken.wultra.app.
V naslednjih dveh zahtevkih se naprava s strežnikom najprej dogovori za začasni komunikacijski ključ, nato pa naprava zahteva aktivacijo svojega novonastalega Wultra uporabnika.
{
"requestObject": {
"jwt": "JWT žeton"
}
}
{
"status": "OK",
"responseObject": {
"jwt": "JWT žeton"
}
}
{
"encryptedData": "base64",
"ephemeralPublicKey": "base64",
"mac": "base64",
"nonce": "base64",
"temporaryKeyId": "UUIDv4",
"timestamp": "unix timestamp"
}
{
"encryptedData": "base64",
"mac": "base64",
"nonce": "base64",
"timestamp": "unix timestamp"
}
Registracija žetona za FCM po mojih poskusih ni bila potrebna:
{
"requestObject": {
"platform": "fcm",
"token": "DEVICE_TOKEN"
}
}
{
"status": "OK"
}
V nekaterih primerih se pošlje tudi zahtevek za status aktivacije. V mojih poskusih nepotrebno.
{
"requestObject": {
"activationId": "UUIDv4",
"challenge": "base64"
}
}
{
"status": "OK",
"responseObject": {
"activationId": "UUIDv4",
"encryptedStatusBlob": "base64",
"nonce": "base64"
}
}
Ko s telefonom avtoriziramo novo sejo, se najprej pošlje t.i. operation claim:
{
"requestObject": {
"id": "operationId iz login linka"
}
}
{
"responseObject": {
"id": "operationId iz login linka",
"name": "loginweb",
"data": "A2*Tspletna banka NLB Klik*Tmoj IP naslov*T",
"status": "PENDING",
"operationCreated": "UTC ISO 8601",
"operationExpires": "UTC ISO 8601, 5 minut kasneje",
"allowedSignatureType": {
"type": "2FA",
"variants": [
"possession_knowledge",
"possession_biometry"
]
},
"formData": {
"title": "Login for web bank",
"message": "By confirming, you will grant access to your web bank",
"attributes": [
{
"type": "KEY_VALUE",
"id": "operation.userAgent",
"label": "Application",
"value": "spletna banka NLB Klik"
},
{
"type": "KEY_VALUE",
"id": "operation.ipAddress",
"label": "IP Adddress",
"value": "moj IP naslov"
},
{
"type": "KEY_VALUE",
"id": "operation.location",
"label": "Location",
"value": ""
}
]
}
},
"status": "OK",
"currentTimestamp": "UTC ISO 8601"
}
nato pa še dejanska avtorizacija
{
"requestObject": {
"data": "A2*Tspletna banka NLB Klik*Tmoj IP naslov*T",
"id": "operationId iz QR kode",
"mobileTokenData": {
"deviceToken": {
"platform": "ANDROID",
"token": "DEVICE_TOKEN"
}
},
"proximityCheck": {
"otp": "OTP iz QR kode",
"timestampReceived": "UTC ISO 8601",
"timestampSent": "UTC ISO 8601",
"type": "DEEPLINK"
}
}
}
{
"status": "OK"
}
Ob normalnem delovanju opazimo še kup drugih nepovezanih zahtevkov na domene:
fhp-de-back.group-ib.com - predvidevam, da neke vrste
root check? (TODO: verify this) Tuki se poslje 23kB b64-encoded
garbagaeum-blue-saas.instana.io - IBM instana, verjetno
splošna telemetrijafirebaseremoteconfigrealtime.googleapis.com - firebase,
verjetno povezano s FCM za potisna obvestilafirebaseinstallations.googleapis.com - firebaseota.lokalise.com - lokalizacijaota-bundles.lokalise.com - lokalizacijaTe lahko mirno blokiramo, ne da bi preprečili delovanje aplikacije.
Najprej vključimo USB razhroščevanje. Glej https://developer.android.com/tools/adb#Enabling.
Sedaj lahko z ukazom
adb shell pm list packages | grep nlb dobimo polno ime
aplikacije Klik, ki jo prenesemo z
adb shell pm path co.infinum.nlb /data/app/~~SFowXO9VvoPL6wq1j1ed7A==/co.infinum.nlb-L3zT9kaZXe_Ex8X5F9RksA==/base.apk
adb pull /data/app/~~SFowXO9VvoPL6wq1j1ed7A==/co.infinum.nlb-L3zT9kaZXe_Ex8X5F9RksA==/base.apk
Pazi, ime aplikacije bo pri tebi drugačno!
Za analizo kode uporabimo orodje apktool
Ker si mitmproxy sam podpisuje TLS certifikate za spletišča, ki jih impersonira, mora telefon zaupati njegovemu korenskemu izdajatelju (CA).
Android je dodajanje sistemskih CA certifikatov prepovedal za navadne smrtnike, zato je to možno zgolj na rooted telefonih. Še vedno lahko dodamo uporabniški CA, a večina aplikacij le-tem ne zaupa.
Če smo že potegnili apk na računalnik, lahko popravimo
res/xml/network_security_config.xml:
<network-security-config> <debug-overrides> <trust-anchors> <!-- Trust preinstalled CAs --> <certificates src="system" /> <!-- Additionally trust user added CAs --> <certificates src="user" /> </trust-anchors> </debug-overrides> </network-security-config>
Nov apk pošljemo na telefon, zbrišemo star NLB Klik in naložimo popravljenega. Ta bo zaupal uporabniškim CA-jem.