mastra-ai/mastra · error

Expected a PEM-encoded public key or certificate string for

Error message

Expected a PEM-encoded public key or certificate string for Agent Card verification

What it means

importVerificationKey converts the JWK 'kty'-independent key material from an agent's signature verification config into a WebCrypto key. For non-HS algorithms the key string must be PEM-encoded; if isPem(key) fails the client throws this error because it cannot import the raw string for asymmetric verification.

Source

Thrown at client-sdks/client-js/src/utils/verify-agent-card-signature.ts:78

async function importVerificationKey(
  key: AgentCardVerificationKey,
  algorithm: string,
): Promise<CryptoKey | Uint8Array> {
  if (isCryptoKey(key) || key instanceof Uint8Array) {
    return key;
  }

  if (key instanceof ArrayBuffer) {
    return new Uint8Array(key);
  }

  if (typeof key === 'string') {
    if (algorithm.startsWith('HS')) {
      return new TextEncoder().encode(key);
    }

    if (!isPem(key)) {
      throw new Error('Expected a PEM-encoded public key or certificate string for Agent Card verification');
    }

    if (isCertificate(key)) {
      return importX509(key, algorithm);
    }

    return importSPKI(key, algorithm);
  }

  return importJWK(key as JWK, algorithm);
}

export async function verifyAgentCardSignatureIfPresent(
  agentCard: AgentCard,
  options: VerifyAgentCardSignatureOptions,
): Promise<AgentCard> {
  const signatures = agentCard.signatures ?? [];
  if (signatures.length === 0) {

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Ensure the key string includes full PEM armor: -----BEGIN PUBLIC KEY-----/-----END PUBLIC KEY----- (or CERTIFICATE for X.509).
  2. If using an HS* algorithm, pass the shared secret as the key; otherwise supply the PEM public key/certificate, not a raw secret.
  3. If your source is a JWKS (n/e values), convert the JWK to PEM before verification.
  4. Check env/config for truncation or whitespace corruption of the PEM (newlines replaced/lost).
  5. Match the algorithm in the Agent Card's JWS header with the key type you provide.

Example fix

// before
const key = process.env.AGENT_PUBLIC_KEY_BODY; // 'MIIBIjANBgkq...'
verifyAgentCardSignature(card, key, 'RS256');
// after
const key = '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkq...\n-----END PUBLIC KEY-----';
verifyAgentCardSignature(card, key, 'RS256');
Defensive patterns

Strategy: validation

Validate before calling

function isPemString(key: string): boolean {
  return /-----BEGIN (PUBLIC KEY|CERTIFICATE)-----[\s\S]+-----END (PUBLIC KEY|CERTIFICATE)-----/.test(key.trim());
}
if (!key.startsWith('HS') && !isPemString(publicKeyOrCert)) throw new Error('Provide PEM-encoded public key/certificate for asymmetric algorithms');

Type guard

function isPem(v: string): boolean {
  return v.includes('-----BEGIN') && v.includes('-----END') && v.includes('KEY') || v.includes('CERTIFICATE');
}

Try / catch

try {
  await verifyAgentCardSignature(card, key, algorithm);
} catch (e) {
  if (e.message.includes('PEM-encoded public key')) {
    console.error('Key is not PEM; re-export with armor or use HS* algorithm with a shared secret');
  } else throw e;
}

Prevention

When it happens

Trigger: Verifying an Agent Card JWS signature with importVerificationKey where the provided key string is not PEM (missing '-----BEGIN' headers), the algorithm is not HS* (e.g. RS256/ES256) but the config supplies a raw base64 secret or a bare public key body without PEM armor.

Common situations: Copying a raw RSA public key body without the -----BEGIN PUBLIC KEY----- armor, pasting an HS256 shared secret while the algorithm is RS256, retrieving the key from a JWKS endpoint (bare base64 JWK values) instead of a PEM certificate, or a misconfigured env var that truncated the PEM.

Understand the failure class

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/f202072b4d8c0b17. Report an issue: GitHub.