Skip to content

Certificate Verification

Working with APK signing certificates.

Basic Certificate Access

typescript
import { Apk } from "node-apk";

const apk = new Apk("app.apk");
const certs = await apk.getCertificateInfo();

for (const cert of certs) {
  console.log("Certificate:");
  console.log(`  Subject: ${cert.subject.get("CN")}`);
  console.log(`  Issuer: ${cert.issuer.get("CN")}`);
  console.log(`  Serial: ${cert.serial}`);
  console.log(`  Valid from: ${cert.validFrom}`);
  console.log(`  Valid until: ${cert.validUntil}`);
}

Certificate Chain Traversal

typescript
const apk = new Apk("app.apk");
const certs = await apk.getCertificateInfo();

for (const cert of certs) {
  console.log("\nCertificate Chain:");
  
  for (let i = 0; i < cert.chain.length; i++) {
    const c = cert.chain[i];
    const indent = "  ".repeat(i);
    console.log(`${indent}${i + 1}. ${c.subject.get("CN")}`);
  }
}

Validity Checking

typescript
function checkCertificateValidity(cert: Certificate): {
  valid: boolean;
  expired: boolean;
  notYetValid: boolean;
  daysUntilExpiry: number;
} {
  const now = new Date();
  
  return {
    valid: now >= cert.validFrom && now <= cert.validUntil,
    expired: now > cert.validUntil,
    notYetValid: now < cert.validFrom,
    daysUntilExpiry: Math.floor(
      (cert.validUntil.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)
    ),
  };
}

// Usage
const apk = new Apk("app.apk");
const certs = await apk.getCertificateInfo();

for (const cert of certs) {
  const status = checkCertificateValidity(cert);
  
  if (status.expired) {
    console.error(`❌ Certificate expired!`);
  } else if (status.notYetValid) {
    console.warn(`⚠️ Certificate not yet valid`);
  } else {
    console.log(`✅ Valid for ${status.daysUntilExpiry} more days`);
  }
}

Fingerprint Calculation

typescript
import { createHash } from "node:crypto";

function getFingerprints(cert: Certificate) {
  return {
    sha256: createHash("sha256")
      .update(cert.bytes)
      .digest("hex")
      .match(/.{2}/g)
      ?.join(":") ?? "",
    sha1: createHash("sha1")
      .update(cert.bytes)
      .digest("hex")
      .match(/.{2}/g)
      ?.join(":") ?? "",
    md5: createHash("md5")
      .update(cert.bytes)
      .digest("hex")
      .match(/.{2}/g)
      ?.join(":") ?? "",
  };
}

// Usage
const apk = new Apk("app.apk");
const certs = await apk.getCertificateInfo();

for (const cert of certs) {
  const prints = getFingerprints(cert);
  console.log(`SHA-256: ${prints.sha256}`);
  console.log(`SHA-1:   ${prints.sha1}`);
}

Certificate Comparison

Compare certificates to verify APK authenticity:

typescript
function certificatesEqual(cert1: Certificate, cert2: Certificate): boolean {
  return cert1.bytes.equals(cert2.bytes);
}

async function verifyApkSignature(
  apkPath: string,
  expectedCertPath: string
): Promise<boolean> {
  const apk = new Apk(apkPath);
  const certs = await apk.getCertificateInfo();
  
  const expectedCert = Certificate.fromDer(
    await fs.readFile(expectedCertPath)
  )[0];
  
  return certs.some(cert => certificatesEqual(cert, expectedCert));
}

// Usage
const isValid = await verifyApkSignature("app.apk", "expected-cert.der");
if (!isValid) {
  console.error("APK signature mismatch!");
}

PEM Export

Convert certificate to PEM format:

typescript
function toPem(cert: Certificate): string {
  const base64 = cert.bytes.toString("base64");
  const lines = base64.match(/.{1,64}/g) ?? [];
  
  return [
    "-----BEGIN CERTIFICATE-----",
    ...lines,
    "-----END CERTIFICATE-----",
  ].join("\n");
}

// Usage
const apk = new Apk("app.apk");
const certs = await apk.getCertificateInfo();

for (const cert of certs) {
  const pem = toPem(cert);
  await fs.writeFile(`${cert.subject.get("CN")}.pem`, pem);
}

Self-Signed vs CA-Signed

typescript
function isSelfSigned(cert: Certificate): boolean {
  // A certificate is self-signed if issuer == subject
  return !cert.parent;
}

const apk = new Apk("app.apk");
const certs = await apk.getCertificateInfo();

for (const cert of certs) {
  if (isSelfSigned(cert)) {
    console.log("Self-signed certificate");
  } else {
    console.log(`Signed by: ${cert.parent?.subject.get("CN")}`);
  }
}

Certificate Information Summary

typescript
function getCertificateSummary(cert: Certificate) {
  return {
    subject: {
      commonName: cert.subject.get("CN"),
      organization: cert.subject.get("O"),
      country: cert.subject.get("C"),
    },
    issuer: {
      commonName: cert.issuer.get("CN"),
      organization: cert.issuer.get("O"),
    },
    validity: {
      from: cert.validFrom.toISOString(),
      until: cert.validUntil.toISOString(),
      daysRemaining: Math.floor(
        (cert.validUntil.getTime() - Date.now()) / (1000 * 60 * 60 * 24)
      ),
    },
    chainLength: cert.chain.length,
    fingerprintSha256: createHash("sha256")
      .update(cert.bytes)
      .digest("hex"),
  };
}

Released under the MIT License.