beemdevelopment/Aegis · error · CertificateException

Certificates cannot be null or empty!

Error message

Certificates cannot be null or empty!

What it means

checkTrustedSigner also validates each individual Signature element: any null entry or a signature whose byte array is empty triggers CertificateException('Certificates cannot be null or empty!'). This is a per-element guard distinct from the array-level null check, catching malformed signature data coming from PackageManager or a hand-built Signature[] array.

Solutions

  1. Filter out null/empty Signature entries before calling checkTrustedSigner, or fail fast with your own diagnostic
  2. Verify how the Signature was built — decode the hex/base64 certificate correctly (Signature(javaCertHex) expects the raw certificate bytes)
  3. Re-fetch signatures via PackageManager with GET_SIGNATURES / SigningInfo instead of caching stale values
  4. Compare against the expected certificate bytes and re-pin if the app's signing key changed

Example fix

// before
Signature sig = new Signature(certHex.getBytes()); // wrong: encodes hex text, may be empty
trustedIntents.checkTrustedSigner(new Signature[]{ sig });
// after
byte[] certBytes = Hex.decodeHex(certHex.toCharArray()); // actually decode the hex
if (certBytes == null || certBytes.length == 0) {
    throw new IllegalArgumentException("cert data empty");
}
trustedIntents.checkTrustedSigner(new Signature[]{ new Signature(certBytes) });
Defensive patterns

Strategy: type-guard

Validate before calling

boolean allValid = signatures != null;
if (allValid) {
    for (Signature s : signatures) {
        if (s == null || s.toByteArray().length == 0) { allValid = false; break; }
    }
}
if (!allValid) { reFetchSignaturesFromPackageManager(); }

Type guard

Signature[] validSignaturesOnly(Signature[] sigs) {
    if (sigs == null) return new Signature[0];
    return java.util.Arrays.stream(sigs)
        .filter(s -> s != null && s.toByteArray().length > 0)
        .toArray(Signature[]::new);
}

Try / catch

try {
    trustedIntents.checkTrustedSigner(signatures);
} catch (CertificateException e) {
    if (e.getMessage().contains("Certificates cannot be null")) {
        Log.e(TAG, "malformed signature element; re-fetch from PackageManager");
    }
}

Prevention

When it happens

Trigger: The Signature array contains a null element or a Signature constructed from an empty/invalid byte[] (e.g. Signature(new byte[0]), parsing an empty/corrupt hex string, or a provider returning a placeholder empty signature).

Common situations: Manual Signature construction from wrongly-decoded certificate data (hex/base64 decode failure or truncation); corrupted PackageManager results on some OEM ROMs; tests passing dummy empty signatures.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of beemdevelopment/Aegis@d6f4e5925a (2026-09-08). Data as JSON: /api/errors/137016a5755ed609. Report an issue: GitHub.

Appendix: source

Thrown at app/src/main/java/info/guardianproject/trustedintents/TrustedIntents.java:231

    public void checkTrustedSigner(String packageName)
            throws NameNotFoundException, CertificateException {
        PackageInfo packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
        checkTrustedSigner(packageInfo.signatures);
    }

    public void checkTrustedSigner(PackageInfo packageInfo)
            throws NameNotFoundException, CertificateException {
        checkTrustedSigner(packageInfo.signatures);
    }

    public void checkTrustedSigner(Signature[] signatures)
            throws NameNotFoundException, CertificateException {
        if (signatures == null || signatures.length == 0)
            throw new CertificateException("signatures cannot be null or empty!");
        for (int i = 0; i < signatures.length; i++)
            if (signatures[i] == null || signatures[i].toByteArray().length == 0)
                throw new CertificateException("Certificates cannot be null or empty!");

        // check whether the APK signer is trusted for all apps
        for (ApkSignaturePin pin : pinList)
            if (areSignaturesEqual(signatures, pin.getSignatures()))
                return; // found a matching trusted APK signer

        throw new CertificateException("APK signatures did not match!");
    }

    public boolean areSignaturesEqual(Signature[] sigs0, Signature[] sigs1) {
        // TODO where is Android's implementation of this that I can just call?
        if (sigs0 == null || sigs1 == null)
            return false;
        if (sigs0.length == 0 || sigs1.length == 0)
            return false;
        if (sigs0.length != sigs1.length)
            return false;
        for (int i = 0; i < sigs0.length; i++)

View on GitHub (pinned to d6f4e5925a)