Credential manager
Directly connecting to the ADB Daemon on device requires authentication. The authentication process uses RSA algorithm, except it uses a custom public key format.
ADB protocol has two authentication methods:
- Public key: The client sends its public key to the device. The device displays a dialog asking its user to confirm the connection. If the user confirms, the connection will be established. If the user also checks "Always allow from this computer", the device will save the public key and use signature authentication next time.
- Signature: The device generates a random challenge and sends it to the client. The client signs the challenge with its private key and sends the signature back to the device. The device verifies if the signature is produced by one of its trusted public keys.
Even if the user checked "Always allow from this computer", the public key may be untrusted due to various reasons:
- On Android 11 and above, the device will automatically revoke the trust if the key is not used in last 7 days. This feature can be disabled by users in the developer settings.
- On Android 11 and above, the user can manually untrust individual keys in "Settings -> Developer options -> Wireless debugging -> Paired devices".
- On Android 10 and below, the user can manually untrust all keys in "Settings -> Developer options -> Revoke USB debugging authorizations".
Tango supports both authentication methods, and can use varies credential managers to support different runtimes.
AdbWebCryptoCredentialManager
Web Crypto API is a standard API for performing cryptographic operations including key generation, encryption, decryption, and digital signatures.
ADB doesn't use standard RSA signature scheme, thus signing the challenge is implemented by Tango, instead of using Web Crypto API's sign method directly.
Both Web browsers and Node.js support Web Crypto API, so we provide the AdbWebCryptoCredentialManager class that uses Web Crypto API to generate ADB private keys.
However, Web browsers and Node.js don't share a common API to store data. Thus, the AdbWebCryptoCredentialManager class requires a TangoKeyStorage instance to store the generated keys.
Definition
import type {
AdbCredentialManager,
AdbPrivateKey,
MaybeError,
} from "@yume-chan/adb";
import type { TangoKeyStorage } from "@yume-chan/adb-credential-web";
export declare class AdbWebCryptoCredentialManager implements AdbCredentialManager {
constructor(storage: TangoKeyStorage, name?: string);
generateKey(): Promise<AdbPrivateKey>;
iterateKeys(): AsyncGenerator<MaybeError<AdbPrivateKey>, void, void>;
}
Constructor parameters
storage
A TangoKeyStorage instance to store the generated keys.
name
An optional name for the keys. On Android 11 and above, it will appear in "Settings -> Developer options -> Wireless debugging -> Paired devices".
If not provided, Android will display it as nouser@nohostname.
TangoKeyStorage
TangoKeyStorage is a simple interface for storing data with a name. For unencrypted storages the privateKey is the raw private key in PKCS#8 format, for encrypted storages it's the encrypted data.
import type { MaybeError } from "@yume-chan/adb";
import type { MaybePromiseLike } from "@yume-chan/async";
export interface TangoKey {
privateKey: Uint8Array;
name: string | undefined;
}
export interface TangoKeyStorage {
save(
privateKey: Uint8Array,
name: string | undefined,
): MaybePromiseLike<undefined>;
load(): Iterable<MaybeError<TangoKey>> | AsyncIterable<MaybeError<TangoKey>>;
}
save
Save a private key to storage.
Parameters:
privateKey: The data to be savedname: The name of the key
Returns:
Either undefined or a Promise that resolves to undefined.
load
Load all private keys from storage.
Returns:
An sync or async iterable. Each item is either a TangoKey or an Error.
Error tolerance
If TangoKeyStorage.prototype.load() or AdbWebCryptoCredentialManager.prototype.iterateKeys() can't load a key, it yields an Error through the iterator, instead of throwing it. This allows Tango to report the error to users, then continue to try other keys.
Web
@yume-chan/adb-credential-web package provides the AdbWebCryptoCredentialManager class, along with various TangoKeyStorage implementations for Web browsers.
- npm
- Yarn
- pnpm
- Bun
npm i @yume-chan/adb-credential-web
yarn add @yume-chan/adb-credential-web
pnpm add @yume-chan/adb-credential-web
bun add @yume-chan/adb-credential-web
Two unencrypted and two encrypted storages are provided:
TangoLocalStorage
Stores the key in localStorage.
| Characteristic | Description |
|---|---|
| Number of keys | 1 |
| Encrypted | No |
| Web Worker | No |
The constructor accepts a key name to store the key (localStorage.getItem(name)). If you want, you can create multiple TangoLocalStorage instances with different names to store multiple keys.
import {
AdbWebCryptoCredentialManager,
TangoLocalStorage,
} from "@yume-chan/adb-credential-web";
const storage = new TangoLocalStorage("key");
const manager = new AdbWebCryptoCredentialManager(storage);
TangoIndexedDbStorage
IndexedDB is a low-level API for client-side storage of significant amounts of structured data, including files/blobs. An IndexedDB database contains object stores (similar to tables in traditional databases) where data is stored as key-value pairs. Each database can contain multiple object stores, allowing for organized data persistence in web applications.
| Characteristic | Description |
|---|---|
| Number of keys | Unlimited |
| Encrypted | No |
| Web Worker | Yes |
export declare class TangoIndexedDbStorage implements TangoKeyStorage {
constructor(options?: { databaseName?: string; storeName?: string });
save(privateKey: Uint8Array, name: string | undefined): Promise<undefined>;
load(): AsyncGenerator<TangoKey, void, void>;
clear(): Promise<undefined>;
}
Constructor parameters
databaseName (optional)
The name of the IndexedDB database to use. Defaults to "Tango".
storeName (optional)
The name of the object store to use. Defaults to "Authentication".
Additional methods
clear()
Removes all stored keys from the database.
Migration
TangoIndexedDbStorage will automatically migrate keys from the previous version (v1) of the database if any are found. The migration occurs during the first access to the database and moves keys from the old schema to the new schema.
import {
AdbWebCryptoCredentialManager,
TangoIndexedDbStorage,
} from "@yume-chan/adb-credential-web";
const storage = new TangoIndexedDbStorage();
const manager = new AdbWebCryptoCredentialManager(storage);
// Or with custom database and store names
const customStorage = new TangoIndexedDbStorage({
databaseName: "MyAppADBKeys",
storeName: "Keys",
});
TangoPasswordProtectedStorage
Encrypts the ADB key using a password. It requires another TangoKeyStorage instance to store the encrypted data.
TangoPasswordProtectedStorage uses PBKDF2 (Password-Based Key Derivation Function 2) to derive an AES key for encrypting the ADB private key. PBKDF2 applies a pseudorandom function (HMAC-SHA-256) to the password along with a salt value and repeats this process many times to produce a derived key. This process makes brute-force attacks computationally expensive by increasing the time required to test each possible password.
The pbkdf2SaltLength parameter specifies the length of the random salt used in the derivation process. A longer salt increases resistance to rainbow table attacks. The pbkdf2Iterations parameter controls how many times the hash function is applied. Higher iteration counts significantly slow down password guessing attempts, making brute-force attacks more difficult but also increasing the time required for legitimate password verification.
| Characteristic | Description |
|---|---|
| Number of keys | Depends on the inner TangoKeyStorage |
| Encrypted | Yes |
| Web Worker | Depends on the inner TangoKeyStorage and password callback |
export namespace TangoPasswordProtectedStorage {
export type RequestPassword = (
reason: "save" | "load",
name: string | undefined,
) => MaybePromiseLike<string>;
export type PasswordIncorrectError = typeof PasswordIncorrectError;
}
export declare class TangoPasswordProtectedStorage implements TangoKeyStorage {
static PasswordIncorrectError: typeof PasswordIncorrectError;
constructor(options: {
storage: TangoKeyStorage;
requestPassword: TangoPasswordProtectedStorage.RequestPassword;
pbkdf2SaltLength?: number | undefined;
pbkdf2Iterations?: number | undefined;
aesIvLength?: number | undefined;
});
save(privateKey: Uint8Array, name: string | undefined): Promise<undefined>;
load(): AsyncGenerator<MaybeError<TangoKey>, void, void>;
}
declare class PasswordIncorrectError extends Error {
get keyName(): string | undefined;
constructor(keyName: string | undefined);
}
Constructor parameters
storage
The inner TangoKeyStorage instance to store the encrypted data.
requestPassword
A callback function to get the password.
Parameters:
reason:"save"when saving (encrypting) the key,"load"when loading (decrypting) the key.name: The name of the key. When saving, it's theAdbWebCryptoCredentialManager'snameparameter. When loading, it's the name of the loaded key from its innerTangoKeyStorage.
pbkdf2SaltLength (optional)
The length of the PBKDF2 salt in bytes. Defaults to 16.
- Minimum: 8
- Maximum: 255
pbkdf2Iterations (optional)
The number of PBKDF2 iterations. Defaults to 1,000,000.
- Minimum: 10,000
- Maximum: 4,294,967,295
aesIvLength (optional)
The length of the AES initialization vector in bytes. Defaults to 12.
- Minimum: 1
- Maximum: 255
Example
import {
AdbWebCryptoCredentialManager,
TangoPasswordProtectedStorage,
TangoIndexedDbStorage,
} from "@yume-chan/adb-credential-web";
const innerStorage = new TangoIndexedDbStorage();
const storage = new TangoPasswordProtectedStorage({
storage: innerStorage,
requestPassword: (reason, name) =>
prompt(
`Password for ${reason === "save" ? "encrypting" : "decrypting"} ${name}:`,
),
pbkdf2Iterations: 100000, // Custom security parameters
});
const manager = new AdbWebCryptoCredentialManager(storage);
Error
If the password is incorrect when loading (decrypting) the key, a TangoPasswordProtectedStorage.PasswordIncorrectError will be yielded from the generator.
The keyName property contains the name of the key that failed to decrypt.
TangoPrfStorage
Encrypts the ADB key using a PRF (pseudo-random function) derived key. It requires another TangoKeyStorage instance to store the encrypted data.
| Characteristic | Description |
|---|---|
| Number of keys | Depends on the inner TangoKeyStorage |
| Encrypted | Yes |
| Web Worker | No |
See its dedicated page for more details.
import {
AdbWebCryptoCredentialManager,
TangoPrfStorage,
TangoIndexedDbStorage,
TangoWebAuthnPrfSource,
} from "@yume-chan/adb-credential-web";
const innerStorage = new TangoIndexedDbStorage();
const prfSource = new TangoWebAuthnPrfSource("Tango", "ADB Key");
const storage = new TangoPrfStorage(innerStorage, prfSource);
const manager = new AdbWebCryptoCredentialManager(storage);
Node.js
The @yume-chan/adb-credential-nodejs package re-exports AdbWebCryptoCredentialManager from @yume-chan/adb-credential-web, and provides a TangoKeyStorage for Node.js:
- npm
- Yarn
- pnpm
- Bun
npm i @yume-chan/adb-credential-nodejs
yarn add @yume-chan/adb-credential-nodejs
pnpm add @yume-chan/adb-credential-nodejs
bun add @yume-chan/adb-credential-nodejs
See the TangoNodeStorage page for more details.
Custom credential manager
For implementing custom credential managers, see the detailed guide.