gchq/CyberChef · error · OperationError

Incorrect handshake length.

Error message

Incorrect handshake length.

What it means

Thrown by parseTLSRecord() in TLS.mjs when the declared record length field (2 bytes at offset 3-4) plus the 5-byte header does not equal the total bytes supplied to the parser. The check `s.length !== r.length.value + 5` ensures the record is neither truncated nor padded with extra bytes. A mismatch means the byte buffer is not a complete, exactly-sized handshake record.

Source

Thrown at src/core/lib/TLS.mjs:48

        throw new OperationError("Not handshake data.");

    // Version
    r.version = {
        description: "Protocol Version",
        length: 2,
        data: b.getBytes(2),
        value: s.readInt(2)
    };

    // Length
    r.length = {
        description: "Record Length",
        length: 2,
        data: b.getBytes(2),
        value: s.readInt(2)
    };
    if (s.length !== r.length.value + 5)
        throw new OperationError("Incorrect handshake length.");

    // Handshake
    r.handshake = {
        description: "Handshake",
        length: r.length.value,
        data: b.getBytes(r.length.value),
        value: parseHandshake(s.getBytes(r.length.value))
    };

    return r;
}

/**
 * Parse a TLS Handshake
 * @param {Uint8Array} bytes
 * @returns {JSON}
 */
function parseHandshake(bytes) {

View on GitHub (pinned to 4290ea7539)

Solutions

  1. Slice the input to exactly 5 + declared-length bytes: const rec = buf.slice(0, 5 + readUint16BE(buf, 3)).
  2. If the buffer is shorter than declared, continue reading from the source until the full record is available (TLS records are self-describing; reassemble by length).
  3. If the buffer is longer, iterate records using the length field and pass each one individually.

Example fix

// before
parseTLSRecord(combinedBuffer); // contains record + trailing bytes
// after
const len = (buf[3] << 8) | buf[4];
parseTLSRecord(buf.slice(0, 5 + len));
Defensive patterns

Strategy: validation

Validate before calling

function sliceOneRecord(buf) {
    if (buf.length < 5) throw new Error("Buffer too short for a TLS record header");
    const len = (buf[3] << 8) | buf[4];
    if (buf.length !== 5 + len) {
        throw new Error(
            `Record length mismatch: declared ${len} bytes but buffer has ${buf.length - 5} payload bytes.`
        );
    }
    return buf.slice(0, 5 + len);
}
const r = parseTLSRecord(sliceOneRecord(buf));

Type guard

function isExactlyOneRecord(buf) {
    return buf.length >= 5 && buf.length === 5 + ((buf[3] << 8) | buf[4]);
}

Try / catch

try {
    record = parseTLSRecord(buf);
} catch (e) {
    if (e instanceof OperationError && /Incorrect handshake length/.test(e.message)) {
        // buffer is truncated or contains multiple records; reassemble or slice
        return await refillAndRetry();
    }
    throw e;
}

Prevention

When it happens

Trigger: Passing a truncated handshake record (network MTU split, capture cut short), a buffer containing the record plus trailing bytes from the next record, or a reassembled buffer with the wrong byte range. Also when the length field itself is malformed or endianness is wrong.

Common situations: TCP segmentation delivering a partial record; merging multiple records into one buffer and feeding all of them; off-by-N slicing when extracting the record from a larger frame; reading the length field as signed or little-endian by mistake.

Understand the failure class

Related errors


AI-assisted analysis of gchq/CyberChef@4290ea7539 (2026-08-13). Data as JSON: /api/errors/ed89787687ec170d. Report an issue: GitHub.