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
- Ensure the key string includes full PEM armor: -----BEGIN PUBLIC KEY-----/-----END PUBLIC KEY----- (or CERTIFICATE for X.509).
- If using an HS* algorithm, pass the shared secret as the key; otherwise supply the PEM public key/certificate, not a raw secret.
- If your source is a JWKS (n/e values), convert the JWK to PEM before verification.
- Check env/config for truncation or whitespace corruption of the PEM (newlines replaced/lost).
- 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
- Store PEM keys with intact newlines (use multiline env/config, avoid single-line mangling).
- Match key type to algorithm: HS* with shared secret, RS/ES/PS* with PEM public key or X.509 cert.
- Convert JWKS/JWK keys to PEM before verification.
- Load keys at startup and validate format once, not per-request.
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
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- Google RBAC roleMapping is required.
- Google service account private key signing failed (${(err as
- Cookie password must be at least 32 characters. Set OKTA_COO
- heartbeatMs must be a finite number no greater than ${MAX_TI
- Agent Card signature is missing a protected "alg" header
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/f202072b4d8c0b17.
Report an issue: GitHub.