dtls-fingerprint-chain-validator
The DTLS-SRTP Certificate Fingerprint Validator parses raw SDP byte streams and raw DTLS handshake bytes to verify that the certificate presented during the DTLS handshake matches the fingerprint advertised in the SDP. It works entirely without browser WebRTC APIs.
The validator (~/dtls-fingerprint-validator.js) is composed of four layers:
| Layer | Files | Purpose |
|---|---|---|
| SDP Parser | parseSdpFingerprints() |
Extracts a=fingerprint:<alg> <hex> attributes from SDP text/buffer |
| DTLS Record Parser | parseDtlsRecords() |
Parses DTLS record headers (ContentType, epoch, sequence_number) |
| Handshake Parser | parseHandshakeMessages() |
Extracts handshake messages from DTLS record fragments |
| Certificate Extractor | parseCertificateMessage() |
Parses the Certificate handshake message (3-byte length-prefixed DER certs) |
| Fingerprint Matcher | validateDtlsFingerprint() |
Computes SHA-256 of each cert DER and compares against SDP fingerprints |
| Attack | Detection | Error |
|---|---|---|
| Fingerprint stripped from SDP | No a=fingerprint lines found |
FINGERPRINT_DOWNGRADE |
| Attacker cert injected | Computed cert fingerprint doesn't match any SDP fingerprint | FINGERPRINT_MISMATCH |
| Empty cert chain | Certificate message has 0 certificates | CERTIFICATE_INJECTION |
| No Certificate message | Handshake lacks Certificate (type 11) message | CERTIFICATE_INJECTION |
function validateDtlsFingerprint(sdpBytes, dtlsBytes) {
// 1. Parse SDP fingerprints
const sdpFingerprints = parseSdpFingerprints(sdpBytes);
if (sdpFingerprints.length === 0) /* downgrade attack */
// 2. Parse DTLS records
const records = parseDtlsRecords(dtlsBytes);
// 3. Extract handshake messages
const handshakeMessages = parseHandshakeMessages(records);
// 4. Find Certificate messages (HandshakeType 11)
const certMessages = handshakeMessages.filter(m => m.msgType === 11);
if (certMessages.length === 0) /* injection attack */
// 5. Parse DER certificates
const certs = parseCertificateMessage(certMessages[0].body);
if (certs.length === 0) /* injection attack */
// 6. Compute SHA-256 fingerprints
// 7. Match against SDP fingerprints (case-insensitive)
const normalizedSdpFps = sdpFingerprints.map(f => f.fingerprint);
for (const certFp of certFingerprints) {
if (normalizedSdpFps.includes(certFp)) return valid;
}
/* fingerprint mismatch - injection attack */
}
const { verify } = require('./dtls-fingerprint-validator');
const sdp = Buffer.from(`v=0\r\na=fingerprint:sha-256 AA:BB:CC:...:FF\r\n`);
const dtlsHandshake = /* raw DTLS bytes from network */;
const result = verify(sdp, dtlsHandshake);
console.log(result.valid); // true | false
console.log(result.errors); // []
console.log(result.details); // debug info
All **31 tests pass** with 0 failures. The test suite (`~/test-validator.js`) covers:
### SDP Parsing (9 tests)
- Single fingerprint, media-level fingerprint, session+media fingerprints
- Mixed case hex (`AA:bb:Cc:Dd:ee:Ff`)
- Buffer input, missing fingerprints, ignoring non-fingerprint lines
- Realistic multi-media SDP with full SHA-256 (64 hex chars each)
### DTLS Record Parsing (3 tests)
- Valid DTLS record header (version FE/FD, epoch, 6-byte sequence number)
- Truncated data returns null
- Multiple records concatenated
### Handshake Parsing (1 test)
- Handshake header with type, uint24 length, message_seq, fragment_offset, fragment_length
### Certificate Extraction (3 tests)
- Single certificate, chain of 3, empty list
### Fingerprint Computation (1 test)
- SHA-256 of known input matches `crypto.createHash('sha256')`
### Integration - Happy Path (3 tests)
- Valid SFP fingerprint matches DTLS certificate
- Case-insensitive hex matching (uppercase SDP fingerprint)
- Case-insensitive algorithm name (`SHA-256` vs `sha-256`)
### Attack Detection (6 tests)
- **Fingerprint mismatch**: SDP advertises fingerprint A, DTLS presents certificate B → `FINGERPRINT_MISMATCH`
- **Downgrade attack**: SDP with no `a=fingerprint` → `FINGERPRINT_DOWNGRADE`
- **Empty DTLS data** → invalid
- **No Certificate message** (only ServerHello) → `CERTIFICATE_INJECTION`
- **Empty certificate chain** → `CERTIFICATE_INJECTION`
### Chain & Multi-Fingerprint (4 tests)
- 3-cert chain where first cert matches SDP fingerprint
- 3-cert chain where middle cert matches
- Multiple SDP fingerprints (any match succeeds)
- Realistic WebRTC SDP with ICE, media sections, BUNDLE group
### Edge Cases
- Multiple DTLS records across incremental sequence numbers
- Parsing continues past non-handshake records
- `verify()` convenience wrapper function
---{"model": "claude-sonnet-4-20250514", "problem_class": "dtls-fingerprint-chain-validator", "result": "passed", "tests": 31}The DTLS-SRTP Certificate Fingerprint Validator parses raw SDP byte streams and raw DTLS handshake bytes to verify that the certificate presented during the DTLS handshake matches the fingerprint advertised in the SDP. It works entirely without browser WebRTC APIs.
The validator (~/dtls-fingerprint-validator.js) is composed of four layers:
| Layer | Files | Purpose |
|---|---|---|
| SDP Parser | parseSdpFingerprints() |
Extracts a=fingerprint:<alg> <hex> attributes from SDP text/buffer |
| DTLS Record Parser | parseDtlsRecords() |
Parses DTLS record headers (ContentType, epoch, sequence_number) |
| Handshake Parser | parseHandshakeMessages() |
Extracts handshake messages from DTLS record fragments |
| Certificate Extractor | parseCertificateMessage() |
Parses the Certificate handshake message (3-byte length-prefixed DER certs) |
| Fingerprint Matcher | validateDtlsFingerprint() |
Computes SHA-256 of each cert DER and compares against SDP fingerprints |
| Attack | Detection | Error |
|---|---|---|
| Fingerprint stripped from SDP | No a=fingerprint lines found |
FINGERPRINT_DOWNGRADE |
| Attacker cert injected | Computed cert fingerprint doesn't match any SDP fingerprint | FINGERPRINT_MISMATCH |
| Empty cert chain | Certificate message has 0 certificates | CERTIFICATE_INJECTION |
| No Certificate message | Handshake lacks Certificate (type 11) message | CERTIFICATE_INJECTION |
function validateDtlsFingerprint(sdpBytes, dtlsBytes) {
// 1. Parse SDP fingerprints
const sdpFingerprints = parseSdpFingerprints(sdpBytes);
if (sdpFingerprints.length === 0) /* downgrade attack */
// 2. Parse DTLS records
const records = parseDtlsRecords(dtlsBytes);
// 3. Extract handshake messages
const handshakeMessages = parseHandshakeMessages(records);
// 4. Find Certificate messages (HandshakeType 11)
const certMessages = handshakeMessages.filter(m => m.msgType === 11);
if (certMessages.length === 0) /* injection attack */
// 5. Parse DER certificates
const certs = parseCertificateMessage(certMessages[0].body);
if (certs.length === 0) /* injection attack */
// 6. Compute SHA-256 fingerprints
// 7. Match against SDP fingerprints (case-insensitive)
const normalizedSdpFps = sdpFingerprints.map(f => f.fingerprint);
for (const certFp of certFingerprints) {
if (normalizedSdpFps.includes(certFp)) return valid;
}
/* fingerprint mismatch - injection attack */
}
const { verify } = require('./dtls-fingerprint-validator');
const sdp = Buffer.from(`v=0\r\na=fingerprint:sha-256 AA:BB:CC:...:FF\r\n`);
const dtlsHandshake = /* raw DTLS bytes from network */;
const result = verify(sdp, dtlsHandshake);
console.log(result.valid); // true | false
console.log(result.errors); // []
console.log(result.details); // debug info
All **31 tests pass** with 0 failures. The test suite (`~/test-validator.js`) covers:
### SDP Parsing (9 tests)
- Single fingerprint, media-level fingerprint, session+media fingerprints
- Mixed case hex (`AA:bb:Cc:Dd:ee:Ff`)
- Buffer input, missing fingerprints, ignoring non-fingerprint lines
- Realistic multi-media SDP with full SHA-256 (64 hex chars each)
### DTLS Record Parsing (3 tests)
- Valid DTLS record header (version FE/FD, epoch, 6-byte sequence number)
- Truncated data returns null
- Multiple records concatenated
### Handshake Parsing (1 test)
- Handshake header with type, uint24 length, message_seq, fragment_offset, fragment_length
### Certificate Extraction (3 tests)
- Single certificate, chain of 3, empty list
### Fingerprint Computation (1 test)
- SHA-256 of known input matches `crypto.createHash('sha256')`
### Integration - Happy Path (3 tests)
- Valid SFP fingerprint matches DTLS certificate
- Case-insensitive hex matching (uppercase SDP fingerprint)
- Case-insensitive algorithm name (`SHA-256` vs `sha-256`)
### Attack Detection (6 tests)
- **Fingerprint mismatch**: SDP advertises fingerprint A, DTLS presents certificate B → `FINGERPRINT_MISMATCH`
- **Downgrade attack**: SDP with no `a=fingerprint` → `FINGERPRINT_DOWNGRADE`
- **Empty DTLS data** → invalid
- **No Certificate message** (only ServerHello) → `CERTIFICATE_INJECTION`
- **Empty certificate chain** → `CERTIFICATE_INJECTION`
### Chain & Multi-Fingerprint (4 tests)
- 3-cert chain where first cert matches SDP fingerprint
- 3-cert chain where middle cert matches
- Multiple SDP fingerprints (any match succeeds)
- Realistic WebRTC SDP with ICE, media sections, BUNDLE group
### Edge Cases
- Multiple DTLS records across incremental sequence numbers
- Parsing continues past non-handshake records
- `verify()` convenience wrapper function
---{"model": "claude-sonnet-4-20250514", "problem_class": "dtls-fingerprint-chain-validator", "result": "passed", "tests": 31}