The Root That Brought Its Own Generator — Java Bug Hunt

Modelled on "Curveball" (CVE-2020-0601, patched by Microsoft in January 2020 after a report from the NSA).

  • Language: Java
  • Layer: Backend
  • Difficulty: Hard
  • Concepts: Security, Validation
  • Modelled on: Windows CryptoAPI · CVE-2020-0601
  • Visible tests: the genuine root is trusted; a root's key with a substituted generator is not trusted
  • Reward: 50 XP for a complete fix

Briefing

Modelled on "Curveball" (CVE-2020-0601, patched by Microsoft in January 2020 after a report from the NSA). Windows' CryptoAPI accepted elliptic-curve certificates that spell out their curve parameters explicitly. When checking whether a certificate was a trusted root, it matched on the public key but did not check that the curve parameters — in particular the generator point — were the standard ones. An attacker could keep a trusted root's public key, pick a different generator for which they knew the matching private key, and have their forged "root" trusted for TLS and code signing.

ChainValidator.java decides whether a certificate is one of the trusted roots. Curves can be named (resolved through Curves.named) or given explicitly.

Fix isTrustedRoot so a match requires the same public key on the same curve.

Bug report

BUG-CURVEBALL · Priority: Critical (signature spoofing) · Reported by: security

ChainValidator.isTrustedRoot(cert, store) is true only when some root in store.roots has:

  • the same public key point (qx, qy), AND
  • identical domain parameters: p, a, b, gx, gy and n all equal

A key's parameters are key.explicit when present, otherwise Curves.named(key.curveName). A curve that resolves to nothing (unknown name, no name and no parameters) never matches. Explicitly spelled-out parameters equal to a named curve's values count as that curve.

Observed: a certificate carrying a trusted root's public key with its own explicit generator is accepted as that root.

Logs

[chain] leaf "update.example" -> root "Trusted Root CA" (explicit params, gx=555 gy=777)
[chain] root matched by public key -> TRUSTED

The code as shipped

src/crypto/ChainValidator.java (editable)

class ChainValidator {
    // A certificate is anchored when it is one of the trusted roots.
    static boolean isTrustedRoot(Certificate cert, RootStore store) {
        for (Certificate root : store.roots) {
            if (root.key.qx == cert.key.qx && root.key.qy == cert.key.qy) {
                return true;
            }
        }
        return false;
    }
}

Read-only context: src/crypto/Certificate.java, src/crypto/EcParams.java.

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