The Error Code That Meant Yes — JavaScript Bug Hunt

Modelled on GnuTLS CVE-2014-0092 (March 2014). GnuTLS's certificate-verification code used helpers that were read as booleans — non-zero meant "yes" — but…

  • Language: JavaScript
  • Layer: Backend
  • Difficulty: Easy
  • Concepts: Security, Error Handling
  • Modelled on: GnuTLS · CVE-2014-0092
  • Visible tests: a well-formed chain to a trusted root verifies; an undecodable intermediate fails the chain
  • Reward: 50 XP for a complete fix

Briefing

Modelled on GnuTLS CVE-2014-0092 (March 2014). GnuTLS's certificate-verification code used helpers that were read as booleans — non-zero meant "yes" — but on certain errors they returned a negative error code. Callers tested the result with a plain if, so an error was taken as success, and specially crafted certificates were accepted as valid. It was disclosed weeks after Apple's "goto fail" and fixed in GnuTLS 3.2.12 and 3.1.22.

verify.js walks a chain using two helpers that follow the C convention documented in x509.js: 1 for yes, 0 for no, negative for an error.

Fix verifyChain so an error is never read as "yes".

Bug report

BUG-GNUTLS-0092 · Priority: Critical (TLS bypass) · Reported by: security audit

verifyChain(chain, trustedRoots) — chain[0] is the leaf, each certificate is issued by the next, the last must be in trustedRoots (by subject):

  • every issuer must be a CA: x509.checkIfCa(issuer) === 1
  • every signature must check: x509.checkSignature(child, issuer) === 1
  • 0 (no) AND any negative value (error) both fail the chain
  • an empty chain fails

Observed: a chain whose intermediate cannot be decoded verifies as valid.

Logs

[tls] verify chain leaf=shop.example depth=3
[x509] check_if_ca(intermediate) = -69 (ASN.1 DER error)
[tls] chain OK

The code as shipped

src/tls/verify.js (editable)

var x509 = require("./x509");

// chain[0] is the leaf; each certificate is issued by the next one; the last
// must be one of trustedRoots (by subject).
exports.verifyChain = function (chain, trustedRoots) {
  if (chain.length === 0) return false;
  for (var i = 0; i + 1 < chain.length; i++) {
    var child = chain[i];
    var issuer = chain[i + 1];
    if (!x509.checkIfCa(issuer)) return false;
    if (!x509.checkSignature(child, issuer)) return false;
  }
  return trustedRoots.indexOf(chain[chain.length - 1].subject) !== -1;
};

Read-only context: src/tls/x509.js.

Open the hunt to edit the files, run the visible tests and submit against the hidden ones. More JavaScript bug hunts.