w7corp/easywechat · error · InvalidConfigException

Read the $certificate failed, please check it whether or nor

Error message

Read the $certificate failed, please check it whether or nor correct

What it means

PublicKey::getSerialNo() runs openssl_x509_parse() on the stored certificate and throws this InvalidConfigException when parsing fails, i.e. the string is not an X.509 certificate. Pay\Application uses it for the merchant certificate (src/Pay/Application.php:44) and Pay\Merchant::getPlatformCerts for platform certs; the serial feeds request signing (src/Pay/Signature.php:64). The constructor only maps an existing file path to file:// — any other string is treated as PEM content, so a bare non-existent path is parsed as PEM and fails.

Source

Thrown at src/Kernel/Support/PublicKey.php:30

class PublicKey
{
    public function __construct(public string $certificate)
    {
        if (file_exists($certificate)) {
            $this->certificate = "file://{$certificate}";
        }
    }

    /**
     * @throws InvalidConfigException
     */
    public function getSerialNo(): string
    {
        $info = openssl_x509_parse($this->certificate);

        if ($info === false) {
            throw new InvalidConfigException('Read the $certificate failed, please check it whether or nor correct');
        }

        return strtoupper($info['serialNumberHex']);
    }

    public function __toString(): string
    {
        if (str_starts_with($this->certificate, 'file://')) {
            return file_get_contents($this->certificate) ?: '';
        }

        return $this->certificate;
    }
}

View on GitHub (pinned to f0cf0a8b83)

Solutions

  1. Ensure the value is a PEM starting with -----BEGIN CERTIFICATE----- (or an existing file path containing one).
  2. Check you did not swap 'certificate' and 'private_key' (mch_key) config entries.
  3. Pre-parse to see the real error: openssl_x509_parse($pem) or openssl x509 -in cert.pem -noout -text.
  4. Use absolute paths or pass file_get_contents() output; strip BOM/whitespace.

Example fix

// before: private key PEM in the certificate slot
$merchant = new Merchant(mchId: $mchId, certificate: new PublicKey($privateKeyPem));
// after: verified X.509 certificate
$cert = trim((string) file_get_contents('/etc/wechat/apiclient_cert.pem'));
if (!str_contains($cert, 'BEGIN CERTIFICATE')) { throw new InvalidArgumentException('not an X.509 certificate'); }
$merchant = new Merchant(mchId: $mchId, certificate: new PublicKey($cert));
Defensive patterns

Strategy: validation

Validate before calling

$cert = trim((string) $config['certificate']);
if (!str_contains($cert, 'BEGIN CERTIFICATE')) { throw new InvalidArgumentException('certificate config must be an X.509 PEM'); }
if (!str_starts_with($cert, '/') || is_file($cert)) { /* ok: existing path or inline PEM */ }

Type guard

function isValidX509Pem(string $cert): bool { return str_contains($cert, '-----BEGIN CERTIFICATE-----') && @openssl_x509_parse($cert) !== false; }

Try / catch

try { $serial = $publicKey->getSerialNo(); } catch (\EasyWeChat\Kernel\Exceptions\InvalidConfigException $e) { throw new RuntimeException('merchant/platform certificate is not a parseable X.509 PEM', 0, $e); }

Prevention

When it happens

Trigger: Config certificate contains a PRIVATE KEY or PUBLIC KEY PEM instead of CERTIFICATE; passing a relative file path that does not exist in the current runtime cwd (file_exists fails, path string is parsed as PEM); a platform cert string truncated, with BOM, or in DER binary form.

Common situations: Swapping the certificate and private_key config entries (apiclient_cert.pem vs apiclient_key.pem); relative paths that differ between CLI and web workers; copying the cert with mangled line endings; platform certs downloaded via API stored incompletely.

Understand the failure class

Related errors


AI-assisted analysis of w7corp/easywechat@f0cf0a8b83 (2026-08-21). Data as JSON: /api/errors/f27a01531ddcb82b. Report an issue: GitHub.