grafana/k6 · error
failed to decode modulus: %w
Error message
failed to decode modulus: %w
What it means
During RSA JWK import the modulus n is decoded with base64.RawURLEncoding (base64url, no padding). This error indicates n is not valid unpadded base64url: '=' padding, '+'/'/' alphabet characters, whitespace, or impossible length. It surfaces from crypto.subtle.importKey('jwk', ...) for RSA algorithms.
Source
Thrown at internal/js/modules/k6/webcrypto/jwk.go:337
// TODO: consider validating the other fields in future
return nil
}
func importRSAJWK(jsonKeyData []byte) (any, CryptoKeyType, int, error) {
var jwk rsaJWK
if err := json.Unmarshal(jsonKeyData, &jwk); err != nil {
return nil, UnknownCryptoKeyType, 0, fmt.Errorf("failed to parse input as RSA JWK key: %w", err)
}
if err := jwk.validate(); err != nil {
return nil, UnknownCryptoKeyType, 0, fmt.Errorf("invalid RSA JWK key: %w", err)
}
// decode the various key components
nBytes, err := base64URLDecode(jwk.N)
if err != nil {
return nil, UnknownCryptoKeyType, 0, fmt.Errorf("failed to decode modulus: %w", err)
}
eBytes, err := base64URLDecode(jwk.E)
if err != nil {
return nil, UnknownCryptoKeyType, 0, fmt.Errorf("failed to decode exponent: %w", err)
}
// convert exponent to an integer
eInt := new(big.Int).SetBytes(eBytes).Int64()
pubKey := rsa.PublicKey{
N: new(big.Int).SetBytes(nBytes),
E: int(eInt),
}
// if the private exponent is missing, return the public key
if jwk.D == "" {
return pubKey, PublicCryptoKeyType, pubKey.N.BitLen(), nil
}
View on GitHub (pinned to 93accf6570)
Solutions
- Convert n to unpadded base64url (replace '+'->'-', '/'->'_', strip '=')
- If the value is hex, re-encode the raw bytes as base64url without padding
- Remove embedded newlines/whitespace
- Confirm the decoded modulus byte length matches the announced key size (e.g. 256 bytes for RSA-2048)
Example fix
// before
const jwk = { kty: 'RSA', n: stdB64N, e: 'AQAB' };
// after
const toB64u = (s) => s.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '').replace(/\s+/g, '');
const jwk = { kty: 'RSA', n: toB64u(stdB64N), e: 'AQAB' }; Defensive patterns
Strategy: validation
Validate before calling
const B64URL = /^[A-Za-z0-9_-]+$/;
const toB64u = (s) => String(s).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '').replace(/\s+/g, '');
const n = toB64u(jwk.n);
if (!B64URL.test(n)) throw new Error('modulus n is not unpadded base64url');
jwk = { ...jwk, n }; Type guard
function isB64uModulus(s) {
return typeof s === 'string' && /^[A-Za-z0-9_-]+$/.test(s) && s.length >= 40;
} Try / catch
try {
key = await crypto.subtle.importKey('jwk', jwk, rsaAlg, true, usages);
} catch (e) {
if (e.message.includes('failed to decode modulus')) {
jwk = { ...jwk, n: toB64u(jwk.n) };
key = await crypto.subtle.importKey('jwk', jwk, rsaAlg, true, usages);
} else throw e;
} Prevention
- Convert standard base64 to base64url at the system boundary
- Expect ~342 chars for an RSA-2048 modulus, ~683 for RSA-4096
- Never use hex-encoded moduli directly
When it happens
Trigger: n encoded in standard or padded base64; whitespace or newlines inside the modulus; truncated modulus; hex-encoded modulus (0x... or plain hex) instead of base64url.
Common situations: Moduli copied from OpenSSL output (standard base64 with wrapping); JWKs from systems using padded base64url; values converted from hex representations by hand.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- failed to decode exponent: %w
- failed to decode private exponent: %w
- failed to decode first prime factor: %w
- failed to decode second prime factor: %w
- failed to decode first exponent: %w
AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15).
Data as JSON: /api/errors/ff338f3fab93f2cb.
Report an issue: GitHub.