Unicode Normalization in Adonisjs with Mutual Tls
Unicode Normalization in Adonisjs with Mutual Tls — how this specific combination creates or exposes the vulnerability
Unicode normalization inconsistencies can lead to authentication bypass and authorization flaws when an Adonisjs application using Mutual Transport Layer Security (mTLS) accepts client certificates that include identity values such as common names or subject alternative names. Because mTLS terminates the TLS handshake at the edge and Adonisjs typically relies on the established client identity, differences in how normalized and non-normalized Unicode strings are compared can cause two visually similar identifiers to be treated as distinct or, conversely, incorrectly equated.
Consider an endpoint that maps a certificate subject to a user or role in the application. If the certificate contains a Unicode common name like café.example.com and the application stores or compares it using a different Unicode normalization form (NFC versus NFD), the effective comparison may fail or incorrectly match. This inconsistency can be exploited in a BOLA/IDOR context where an attacker presents a certificate with a decomposed form to bypass ownership checks, or in an authentication bypass scenario where normalization discrepancies allow access to unintended resources.
Input validation checks that rely on exact string matching without normalization are particularly vulnerable. For example, an access control list (ACL) that references normalized identifiers will not recognize a request where the certificate subject is presented in a different normalization form. Attackers can chain this with mTLS by crafting certificates that exploit normalization differences, potentially escalating privileges or accessing other users’ data.
The risk is amplified when the application parses certificate fields and uses them to construct file paths, cache keys, or database identifiers without normalization. Path traversal or injection can occur if a non-normalized Unicode string introduces unexpected sequences. Therefore, consistent normalization across certificate parsing, identity mapping, and authorization logic is essential to mitigate these issues in an mTLS-enabled Adonisjs service.
Mutual Tls-Specific Remediation in Adonisjs — concrete code fixes
To remediate Unicode normalization issues in an Adonisjs application using Mutual TLS, enforce canonical normalization on all certificate-derived identity values before comparison, storage, or use in access control decisions. Use a stable normalization form such as NFC and apply it consistently across the stack.
Example mTLS setup in Adonisjs with explicit certificate parsing and normalization:
import { normalize } from 'unorm';
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
export default class AuthController {
public async handleMtlsIdentity({ request, auth }: HttpContextContract) {
const tls = request.transport();
// Extract certificate subject fields from the verified mTLS connection
const subject = tls.subject; // e.g., '/CN=café.example.com/O=Acme'
const altNames = tls.subjectaltname.split(',').map(s => s.trim());
// Normalize identity values to NFC to ensure consistent comparison
const normalizedSubjectName = this.extractCommonName(subject);
const normalizedAltNames = altNames.map(name => normalize(name));
// Use normalized values for authorization checks
const user = await User.findBy('normalized_subject', normalize(normalizedSubjectName));
if (!user) {
throw new Error('Unauthorized: identity not found');
}
// Ensure ACL checks also use normalized identifiers
const hasAccess = await this.checkAccess(user, normalizedAltNames);
if (!hasAccess) {
throw new Error('Forbidden: insufficient scope after normalization');
}
return { user, normalizedAltNames };
}
private extractCommonName(subject: string): string {
const match = subject.match(/\/CN=([^,]+)/);
return match ? match[1] : '';
}
private async checkAccess(user: User, identities: string[]): Promise {
// Implement your ACL logic using normalized identities
return true;
}
}
On the server configuration side, ensure that the TLS layer provides parsed certificate details to the application in a stable way. Middleware can centralize normalization so that every request benefits from consistent handling:
import { normalize } from 'unorm';
import { HttpContextContract, middleware } from '@ioc:Adonis/Core/HttpContext';
export const mtlsNormalizationMiddleware = middleware(async (ctx, next) => {
const tls = ctx.request.transport();
if (tls && tls.subject) {
const subject = tls.subject;
const normalized = subject.replace(/\/CN=([^,]+)/, (_, name) => `/CN=${normalize(name)}`);
// Store normalized subject for downstream use
ctx.auth.tlsSubject = normalized;
}
await next();
});
For API clients, validate and normalize identifiers before including them in requests, and verify that server-side checks also normalize incoming values. Complement this with automated tests that use certificates containing characters from multiple Unicode equivalence classes to confirm that normalization prevents bypass.