@capgo/cli
Version:
A CLI to upload to capgo servers
96 lines (95 loc) • 3.68 kB
TypeScript
import { Buffer } from 'node:buffer';
export interface KeystoreDname {
commonName: string;
organizationName?: string;
countryCode?: string;
}
export interface KeystoreOptions {
alias: string;
storePassword: string;
keyPassword: string;
dname: KeystoreDname;
/** Default: 27 years (~10000 days, Android Play standard) */
validityYears?: number;
/** Default: 2048-bit RSA */
keySize?: number;
}
export interface KeystoreResult {
p12Base64: string;
p12Bytes: Buffer;
alias: string;
notAfter: Date;
}
/**
* Sanitize a keystore alias for use as an on-disk filename component.
*
* The alias originates from user input (e.g. `keystoreNewAlias`). It is used
* verbatim for the keystore crypto and the saved `KEYSTORE_KEY_ALIAS`, but the
* value used to build the `<alias>.p12` filename must be sanitized so a value
* like `../../evil` or `/etc/x` cannot escape the target directory.
*
* Rules:
* - strip any directory components (keep only the basename),
* - allow only `[A-Za-z0-9._-]`, replacing any other char with `_`,
* - normalize empty or dot-only results (``, `.`, `..`) to a safe default,
* - the return value never contains `/`, `\`, or `..`.
*
* IMPORTANT: this is ONLY for the filename. Do NOT use it for the crypto alias
* or the saved key alias — those must stay exactly what the user chose.
*/
export declare function sanitizeKeystoreAlias(alias: string): string;
/**
* Generate a URL-safe random password suitable for Android keystore use.
* 24 bytes → 32-char base64url string. Collision-resistant, never written in logs.
*/
export declare function generateRandomPassword(): string;
/**
* Generate a PKCS#12 (.p12) keystore with a self-signed certificate.
*
* Key decisions:
* - 3DES encryption for Gradle/keytool compatibility (same as iOS csr.ts).
* - 27-year validity — Google Play requires keys to outlive all future app updates.
* - 2048-bit RSA — standard for Android app signing.
* - Subject/issuer identical (self-signed).
*
* Throws if alias or passwords are empty.
*/
export declare function generateKeystore(options: KeystoreOptions): KeystoreResult;
export type ProbeKeyPasswordResult = {
ok: true;
} | {
ok: false;
reason: 'wrong-password' | 'unsupported-format' | 'parse-error' | 'no-private-key';
message: string;
};
/**
* Check whether the given password can both unlock a PKCS#12 keystore AND
* decrypt the private key inside it.
*
* Useful for the "skip the key-password prompt if it's the same as the store
* password" UX path: in practice most PKCS#12 keystores use a single password
* for both the integrity MAC and the encrypted private-key bag. If this
* returns `ok: true`, the CLI can use the store password as the key password
* without asking the user.
*
* Returns `unsupported-format` for JKS (node-forge can't parse it) — caller
* should fall back to prompting.
*/
export declare function tryUnlockPrivateKey(bytes: Uint8Array, password: string): ProbeKeyPasswordResult;
export type ListAliasesResult = {
ok: true;
aliases: string[];
} | {
ok: false;
reason: 'wrong-password' | 'unsupported-format' | 'parse-error';
message: string;
};
/**
* Extract key aliases (PKCS#12 `friendlyName` attributes) from a keystore file.
*
* Works for PKCS#12 (.p12, .pfx) keystores. JKS (Java KeyStore — common for
* .jks / .keystore files created by `keytool`) is NOT PKCS#12 and cannot be
* parsed by node-forge; callers should treat `unsupported-format` as "ask the
* user for the alias manually".
*/
export declare function listKeystoreAliases(bytes: Uint8Array, password: string): ListAliasesResult;