Skip to content

Commit fab30b2

Browse files
authored
Implement SSHFP records (#80)
* Add SSHFP record support Use offset since we may receive a full dns packet Account for maximum hash lengths for each hash type fix: don't use the output of `Buffer#copy()`, instead use `Buffer#byteLength` fix: normalize fingerprint string fix: the offset pointer starts out at RDLENGTH fix: account for the `RDLENGTH` field in `rsshfp.decode()` * tests: add test for the SSHFP record type
1 parent a5ee789 commit fab30b2

File tree

2 files changed

+69
-0
lines changed

2 files changed

+69
-0
lines changed

index.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1294,6 +1294,65 @@ rds.encodingLength = function (digest) {
12941294
return 6 + Buffer.byteLength(digest.digest)
12951295
}
12961296

1297+
const rsshfp = exports.sshfp = {}
1298+
1299+
rsshfp.getFingerprintLengthForHashType = function getFingerprintLengthForHashType (hashType) {
1300+
switch (hashType) {
1301+
case 1: return 20
1302+
case 2: return 32
1303+
}
1304+
}
1305+
1306+
rsshfp.encode = function encode (record, buf, offset) {
1307+
if (!buf) buf = Buffer.alloc(rsshfp.encodingLength(record))
1308+
if (!offset) offset = 0
1309+
const oldOffset = offset
1310+
1311+
offset += 2 // The function call starts with the offset pointer at the RDLENGTH field, not the RDATA one
1312+
buf[offset] = record.algorithm
1313+
offset += 1
1314+
buf[offset] = record.hash
1315+
offset += 1
1316+
1317+
const fingerprintBuf = Buffer.from(record.fingerprint.toUpperCase(), 'hex')
1318+
if (fingerprintBuf.length !== rsshfp.getFingerprintLengthForHashType(record.hash)) {
1319+
throw new Error('Invalid fingerprint length')
1320+
}
1321+
fingerprintBuf.copy(buf, offset)
1322+
offset += fingerprintBuf.byteLength
1323+
1324+
rsshfp.encode.bytes = offset - oldOffset
1325+
buf.writeUInt16BE(rsshfp.encode.bytes - 2, oldOffset)
1326+
1327+
return buf
1328+
}
1329+
1330+
rsshfp.encode.bytes = 0
1331+
1332+
rsshfp.decode = function decode (buf, offset) {
1333+
if (!offset) offset = 0
1334+
const oldOffset = offset
1335+
1336+
const record = {}
1337+
offset += 2 // Account for the RDLENGTH field
1338+
record.algorithm = buf[offset]
1339+
offset += 1
1340+
record.hash = buf[offset]
1341+
offset += 1
1342+
1343+
const fingerprintLength = rsshfp.getFingerprintLengthForHashType(record.hash)
1344+
record.fingerprint = buf.slice(offset, offset + fingerprintLength).toString('hex').toUpperCase()
1345+
offset += fingerprintLength
1346+
rsshfp.decode.bytes = offset - oldOffset
1347+
return record
1348+
}
1349+
1350+
rsshfp.decode.bytes = 0
1351+
1352+
rsshfp.encodingLength = function (record) {
1353+
return 4 + Buffer.from(record.fingerprint, 'hex').byteLength
1354+
}
1355+
12971356
const renc = exports.record = function (type) {
12981357
switch (type.toUpperCase()) {
12991358
case 'A': return ra
@@ -1315,6 +1374,7 @@ const renc = exports.record = function (type) {
13151374
case 'RP': return rrp
13161375
case 'NSEC': return rnsec
13171376
case 'NSEC3': return rnsec3
1377+
case 'SSHFP': return rsshfp
13181378
case 'DS': return rds
13191379
}
13201380
return runknown

test.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,15 @@ tape('soa', function (t) {
107107
t.end()
108108
})
109109

110+
tape('sshfp', function (t) {
111+
testEncoder(t, packet.sshfp, {
112+
algorithm: 1,
113+
hash: 1,
114+
fingerprint: 'A108C9F834354D5B37AF988141C9294822F5BC00'
115+
})
116+
t.end()
117+
})
118+
110119
tape('a', function (t) {
111120
testEncoder(t, packet.a, '127.0.0.1')
112121
t.end()

0 commit comments

Comments
 (0)