HIGH bleichenbacher attackadonisjsmutual tls

Bleichenbacher Attack in Adonisjs with Mutual Tls

Bleichenbacher Attack in Adonisjs with Mutual Tls — how this specific combination creates or exposes the vulnerability

A Bleichenbacher attack is a chosen-ciphertext attack against RSA-based encryption schemes that rely on PKCS#1 v1.5 padding or non-constant-time verification. In AdonisJS, this typically manifests when an API endpoint accepts encrypted or signed payloads (e.g., JWTs or encrypted cookies) and performs per-request RSA decryption or signature verification. If the server leaks timing or error-difference information through responses—such as distinct HTTP status codes, response times, or error messages—an attacker can iteratively decrypt or forge valid ciphertexts without the private key.

When Mutual TLS (mTLS) is enabled, the TLS layer authenticates the client using a client certificate. This often leads developers to assume the application-layer is therefore safe, but mTLS only secures the transport. If the application still performs RSA-based operations (e.g., verifying JWTs signed with RS256 or decrypting session blobs) using non-constant-time routines, the Bleichenbacher attack remains viable. An attacker with mTLS access (a valid client cert) can still target the application’s cryptographic handling of tokens or encrypted fields. Conversely, if mTLS is misconfigured to be optional or if certificate validation is inconsistent, an attacker may bypass mTLS entirely and directly probe application endpoints, making the Bleichenbacher vector independent of the client certificate’s presence.

In AdonisJS, common triggers include custom authentication guards that decrypt or verify JWTs using Node’s crypto module with privateDecrypt or verify in a non-constant-time fashion. Because the framework does not enforce constant-time semantics at the application layer, subtle timing differences in padding validation or error paths become exploitable. The attack is particularly effective when the endpoint returns different responses for malformed versus valid padding, and when the server processes many requests sequentially, allowing statistical analysis of response times or status codes.

For example, an endpoint that accepts an encrypted payload in a request body and uses crypto.privateDecrypt to recover a user ID is vulnerable if the RSA implementation leaks via timing or error messages. Even with mTLS ensuring a trusted client, the application must treat every decrypted or verified token as untrusted input and avoid branching logic or error details that depend on padding correctness.

Key prerequisites for a successful Bleichenbacher attack against an AdonisJS service with mTLS include:

  • The server performs RSA decryption or signature verification using PKCS#1 v1.5 padding in application code.
  • The server provides distinguishable responses (status, timing, message) between padding errors and other failures.
  • The attacker can make many requests with modified ciphertexts or tokens.
  • mTLS is either absent, optional, or does not protect the specific endpoint being attacked.

Mitigations require replacing error-differentiating verification with constant-time operations, avoiding PKCS#1 v1.5 where possible, and ensuring mTLS is enforced consistently so that transport-layer authentication complements rather than replaces application-layer cryptography hygiene.

Mutual Tls-Specific Remediation in Adonisjs — concrete code fixes

Remediation focuses on ensuring mTLS is mandatory and correctly validated, and on hardening any application-layer cryptographic operations to avoid Bleichenbacher-vulnerable patterns. Below are concrete, realistic examples for AdonisJS projects using the built-in HTTP server and the @adonisjs/ssl helper ecosystem.

1) Enforce mTLS at the server level by configuring the HTTPS server to require client certificates and to reject unauthenticated connections. In your start/server.ts or custom server bootstrap, set the requestCert and rejectUnauthorized options:

import { defineConfig } from '@adonisjs/core/app'
import { join } from 'path'

export default defineConfig({
  https: {
    key: join(__dirname, '../cert/server.key'),
    cert: join(__dirname, '../cert/server.crt'),
    ca: join(__dirname, '../cert/ca.crt'),
    requestCert: true,
    rejectUnauthorized: true,
  },
})

This ensures that only clients presenting a certificate signed by the trusted CA can establish a TLS connection. Note that this configuration is independent of application logic; if the server is behind a proxy or load balancer, ensure the proxy terminates TLS correctly and forwards verified client certificate information (e.g., via x-client-ssl-cert headers) and that AdonisJS validates those headers appropriately.

2) Avoid leaking cryptographic errors in application responses. When verifying tokens or decrypting data, use constant-time comparison utilities and generic error messages. For example, instead of returning 401 Invalid padding versus 401 Invalid signature, return a uniform 401 Unauthorized and log detailed errors server-side only:

import crypto from 'crypto'
import { createHmac } from 'crypto'

export function safeVerifySignature(buffer: Buffer, signature: Buffer, publicKey: string): boolean {
  const expected = createHmac('sha256', publicKey).update(buffer).digest()
  // timingSafeEqual ensures constant-time comparison
  if (signature.length !== expected.length) {
    return false
  }
  return crypto.timingSafeEqual(signature, expected)
}

3) Prefer modern algorithms and formats that are not susceptible to Bleichenbacher attacks. For JWT verification, use libraries that default to constant-time verification and avoid jsonwebtoken with RS256 in environments where you cannot guarantee constant-time behavior; consider using jose which is designed with secure defaults:

import { jwtVerify } from 'jose'

const publicKey = '--BEGIN PUBLIC KEY-----...'

export async function verifyTokenCompact(token: string): Promise {
  const { payload } = await jwtVerify(token, publicKey, {
    algorithms: ['RS256'],
  })
  return payload
}

4) If you must continue using crypto.privateDecrypt, ensure you handle errors without revealing padding validity. Catch all decryption errors and return a generic response, and avoid branching on error types:

import crypto from 'crypto'

export function decryptPayload(encrypted: Buffer, privateKey: string): Buffer | null {
  try {
    const decrypted = crypto.privateDecrypt(
      {
        key: privateKey,
        padding: crypto.constants.RSA_PKCS1_PADDING,
      },
      encrypted
    )
    return decrypted
  } catch (err) {
    // Always return the same generic failure to avoid leaking padding errors
    return null
  }
}

5) Combine mTLS with application-level token validation. Even with mTLS, validate all inbound data and do not rely on the client certificate to convey business identity. Map the certificate’s subject or SAN to your user/role store in a separate authorization step, ensuring defense in depth:

import { HttpContextContract } from '@adonisjs/core/http'

export default class AuthMiddleware {
  public async handle({ request }: HttpContextContract, next: () => Promise) {
    const cert = request.$getClientCertificate?.() // hypothetical helper
    if (!cert || !this.isValidCertificate(cert)) {
      return response.unauthorized({ message: 'Unauthorized' })
    }
    // Map cert to user, then proceed
    await next()
  }
}

These steps ensure that mTLS is correctly enforced and that application-layer cryptography avoids Bleichenbacher-vulnerable patterns. They complement the platform’s security posture and reduce the risk of ciphertext recovery via timing or error-differentiation attacks.

Frequently Asked Questions

Does mTLS alone prevent Bleichenbacher attacks in AdonisJS?
No. mTLS secures the transport and client authentication but does not protect application-layer cryptography. If your AdonisJS app performs RSA decryption or JWT verification with non-constant-time checks, Bleichenbacher attacks remain viable even with mTLS enforced.
What should I do if my AdonisJS service must accept legacy PKCS#1 v1.5 encrypted payloads?
Avoid PKCS#1 v1.5 where possible; migrate to RSA-OAEP or modern authenticated encryption. If you must support PKCS#1 v1.5, ensure decryption errors are caught and handled with constant-time practices and generic responses, and validate all inputs rigorously to limit oracle behavior.