-
Notifications
You must be signed in to change notification settings - Fork 413
/
DNSSECImpl.sol
425 lines (388 loc) · 15.1 KB
/
DNSSECImpl.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
pragma experimental ABIEncoderV2;
import "./Owned.sol";
import "./BytesUtils.sol";
import "./RRUtils.sol";
import "./DNSSEC.sol";
import "./algorithms/Algorithm.sol";
import "./digests/Digest.sol";
import "@ensdomains/buffer/contracts/Buffer.sol";
/*
* @dev An oracle contract that verifies and stores DNSSEC-validated DNS records.
* @note This differs from the DNSSEC spec defined in RFC4034 and RFC4035 in some key regards:
* - NSEC & NSEC3 are not supported; only positive proofs are allowed.
* - Proofs involving wildcard names will not validate.
* - TTLs on records are ignored, as data is not stored persistently.
* - Canonical form of names is not checked; in ENS this is done on the frontend, so submitting
* proofs with non-canonical names will only result in registering unresolvable ENS names.
*/
contract DNSSECImpl is DNSSEC, Owned {
using Buffer for Buffer.buffer;
using BytesUtils for bytes;
using RRUtils for *;
uint16 constant DNSCLASS_IN = 1;
uint16 constant DNSTYPE_DS = 43;
uint16 constant DNSTYPE_DNSKEY = 48;
uint256 constant DNSKEY_FLAG_ZONEKEY = 0x100;
error InvalidLabelCount(bytes name, uint256 labelsExpected);
error SignatureNotValidYet(uint32 inception, uint32 now);
error SignatureExpired(uint32 expiration, uint32 now);
error InvalidClass(uint16 class);
error InvalidRRSet();
error SignatureTypeMismatch(uint16 rrsetType, uint16 sigType);
error InvalidSignerName(bytes rrsetName, bytes signerName);
error InvalidProofType(uint16 proofType);
error ProofNameMismatch(bytes signerName, bytes proofName);
error NoMatchingProof(bytes signerName);
mapping(uint8 => Algorithm) public algorithms;
mapping(uint8 => Digest) public digests;
/**
* @dev Constructor.
* @param _anchors The binary format RR entries for the root DS records.
*/
constructor(bytes memory _anchors) {
// Insert the 'trust anchors' - the key hashes that start the chain
// of trust for all other records.
anchors = _anchors;
}
/**
* @dev Sets the contract address for a signature verification algorithm.
* Callable only by the owner.
* @param id The algorithm ID
* @param algo The address of the algorithm contract.
*/
function setAlgorithm(uint8 id, Algorithm algo) public owner_only {
algorithms[id] = algo;
emit AlgorithmUpdated(id, address(algo));
}
/**
* @dev Sets the contract address for a digest verification algorithm.
* Callable only by the owner.
* @param id The digest ID
* @param digest The address of the digest contract.
*/
function setDigest(uint8 id, Digest digest) public owner_only {
digests[id] = digest;
emit DigestUpdated(id, address(digest));
}
/**
* @dev Takes a chain of signed DNS records, verifies them, and returns the data from the last record set in the chain.
* Reverts if the records do not form an unbroken chain of trust to the DNSSEC anchor records.
* @param input A list of signed RRSets.
* @return rrs The RRData from the last RRSet in the chain.
* @return inception The inception time of the signed record set.
*/
function verifyRRSet(
RRSetWithSignature[] memory input
)
external
view
virtual
override
returns (bytes memory rrs, uint32 inception)
{
return verifyRRSet(input, block.timestamp);
}
/**
* @dev Takes a chain of signed DNS records, verifies them, and returns the data from the last record set in the chain.
* Reverts if the records do not form an unbroken chain of trust to the DNSSEC anchor records.
* @param input A list of signed RRSets.
* @param now The Unix timestamp to validate the records at.
* @return rrs The RRData from the last RRSet in the chain.
* @return inception The inception time of the signed record set.
*/
function verifyRRSet(
RRSetWithSignature[] memory input,
uint256 now
)
public
view
virtual
override
returns (bytes memory rrs, uint32 inception)
{
bytes memory proof = anchors;
for (uint256 i = 0; i < input.length; i++) {
RRUtils.SignedSet memory rrset = validateSignedSet(
input[i],
proof,
now
);
proof = rrset.data;
inception = rrset.inception;
}
return (proof, inception);
}
/**
* @dev Validates an RRSet against the already trusted RR provided in `proof`.
*
* @param input The signed RR set. This is in the format described in section
* 5.3.2 of RFC4035: The RRDATA section from the RRSIG without the signature
* data, followed by a series of canonicalised RR records that the signature
* applies to.
* @param proof The DNSKEY or DS to validate the signature against.
* @param now The current timestamp.
*/
function validateSignedSet(
RRSetWithSignature memory input,
bytes memory proof,
uint256 now
) internal view returns (RRUtils.SignedSet memory rrset) {
rrset = input.rrset.readSignedSet();
// Do some basic checks on the RRs and extract the name
bytes memory name = validateRRs(rrset, rrset.typeCovered);
if (name.labelCount(0) != rrset.labels) {
revert InvalidLabelCount(name, rrset.labels);
}
rrset.name = name;
// All comparisons involving the Signature Expiration and
// Inception fields MUST use "serial number arithmetic", as
// defined in RFC 1982
// o The validator's notion of the current time MUST be less than or
// equal to the time listed in the RRSIG RR's Expiration field.
if (!RRUtils.serialNumberGte(rrset.expiration, uint32(now))) {
revert SignatureExpired(rrset.expiration, uint32(now));
}
// o The validator's notion of the current time MUST be greater than or
// equal to the time listed in the RRSIG RR's Inception field.
if (!RRUtils.serialNumberGte(uint32(now), rrset.inception)) {
revert SignatureNotValidYet(rrset.inception, uint32(now));
}
// Validate the signature
verifySignature(name, rrset, input, proof);
return rrset;
}
/**
* @dev Validates a set of RRs.
* @param rrset The RR set.
* @param typecovered The type covered by the RRSIG record.
*/
function validateRRs(
RRUtils.SignedSet memory rrset,
uint16 typecovered
) internal pure returns (bytes memory name) {
// Iterate over all the RRs
for (
RRUtils.RRIterator memory iter = rrset.rrs();
!iter.done();
iter.next()
) {
// We only support class IN (Internet)
if (iter.class != DNSCLASS_IN) {
revert InvalidClass(iter.class);
}
if (name.length == 0) {
name = iter.name();
} else {
// Name must be the same on all RRs. We do things this way to avoid copying the name
// repeatedly.
if (
name.length != iter.data.nameLength(iter.offset) ||
!name.equals(0, iter.data, iter.offset, name.length)
) {
revert InvalidRRSet();
}
}
// o The RRSIG RR's Type Covered field MUST equal the RRset's type.
if (iter.dnstype != typecovered) {
revert SignatureTypeMismatch(iter.dnstype, typecovered);
}
}
}
/**
* @dev Performs signature verification.
*
* Throws or reverts if unable to verify the record.
*
* @param name The name of the RRSIG record, in DNS label-sequence format.
* @param data The original data to verify.
* @param proof A DS or DNSKEY record that's already verified by the oracle.
*/
function verifySignature(
bytes memory name,
RRUtils.SignedSet memory rrset,
RRSetWithSignature memory data,
bytes memory proof
) internal view {
// o The RRSIG RR's Signer's Name field MUST be the name of the zone
// that contains the RRset.
if (!name.isSubdomainOf(rrset.signerName)) {
revert InvalidSignerName(name, rrset.signerName);
}
RRUtils.RRIterator memory proofRR = proof.iterateRRs(0);
// Check the proof
if (proofRR.dnstype == DNSTYPE_DS) {
verifyWithDS(rrset, data, proofRR);
} else if (proofRR.dnstype == DNSTYPE_DNSKEY) {
verifyWithKnownKey(rrset, data, proofRR);
} else {
revert InvalidProofType(proofRR.dnstype);
}
}
/**
* @dev Attempts to verify a signed RRSET against an already known public key.
* @param rrset The signed set to verify.
* @param data The original data the signed set was read from.
* @param proof The serialized DS or DNSKEY record to use as proof.
*/
function verifyWithKnownKey(
RRUtils.SignedSet memory rrset,
RRSetWithSignature memory data,
RRUtils.RRIterator memory proof
) internal view {
// Check the DNSKEY's owner name matches the signer name on the RRSIG
for (; !proof.done(); proof.next()) {
bytes memory proofName = proof.name();
if (!proofName.equals(rrset.signerName)) {
revert ProofNameMismatch(rrset.signerName, proofName);
}
bytes memory keyrdata = proof.rdata();
RRUtils.DNSKEY memory dnskey = keyrdata.readDNSKEY(
0,
keyrdata.length
);
if (verifySignatureWithKey(dnskey, keyrdata, rrset, data)) {
return;
}
}
revert NoMatchingProof(rrset.signerName);
}
/**
* @dev Attempts to verify some data using a provided key and a signature.
* @param dnskey The dns key record to verify the signature with.
* @param rrset The signed RRSET being verified.
* @param data The original data `rrset` was decoded from.
* @return True iff the key verifies the signature.
*/
function verifySignatureWithKey(
RRUtils.DNSKEY memory dnskey,
bytes memory keyrdata,
RRUtils.SignedSet memory rrset,
RRSetWithSignature memory data
) internal view returns (bool) {
// TODO: Check key isn't expired, unless updating key itself
// The Protocol Field MUST have value 3 (RFC4034 2.1.2)
if (dnskey.protocol != 3) {
return false;
}
// o The RRSIG RR's Signer's Name, Algorithm, and Key Tag fields MUST
// match the owner name, algorithm, and key tag for some DNSKEY RR in
// the zone's apex DNSKEY RRset.
if (dnskey.algorithm != rrset.algorithm) {
return false;
}
uint16 computedkeytag = keyrdata.computeKeytag();
if (computedkeytag != rrset.keytag) {
return false;
}
// o The matching DNSKEY RR MUST be present in the zone's apex DNSKEY
// RRset, and MUST have the Zone Flag bit (DNSKEY RDATA Flag bit 7)
// set.
if (dnskey.flags & DNSKEY_FLAG_ZONEKEY == 0) {
return false;
}
Algorithm algorithm = algorithms[dnskey.algorithm];
if (address(algorithm) == address(0)) {
return false;
}
return algorithm.verify(keyrdata, data.rrset, data.sig);
}
/**
* @dev Attempts to verify a signed RRSET against an already known hash. This function assumes
* that the record
* @param rrset The signed set to verify.
* @param data The original data the signed set was read from.
* @param proof The serialized DS or DNSKEY record to use as proof.
*/
function verifyWithDS(
RRUtils.SignedSet memory rrset,
RRSetWithSignature memory data,
RRUtils.RRIterator memory proof
) internal view {
uint256 proofOffset = proof.offset;
for (
RRUtils.RRIterator memory iter = rrset.rrs();
!iter.done();
iter.next()
) {
if (iter.dnstype != DNSTYPE_DNSKEY) {
revert InvalidProofType(iter.dnstype);
}
bytes memory keyrdata = iter.rdata();
RRUtils.DNSKEY memory dnskey = keyrdata.readDNSKEY(
0,
keyrdata.length
);
if (verifySignatureWithKey(dnskey, keyrdata, rrset, data)) {
// It's self-signed - look for a DS record to verify it.
if (
verifyKeyWithDS(rrset.signerName, proof, dnskey, keyrdata)
) {
return;
}
// Rewind proof iterator to the start for the next loop iteration.
proof.nextOffset = proofOffset;
proof.next();
}
}
revert NoMatchingProof(rrset.signerName);
}
/**
* @dev Attempts to verify a key using DS records.
* @param keyname The DNS name of the key, in DNS label-sequence format.
* @param dsrrs The DS records to use in verification.
* @param dnskey The dnskey to verify.
* @param keyrdata The RDATA section of the key.
* @return True if a DS record verifies this key.
*/
function verifyKeyWithDS(
bytes memory keyname,
RRUtils.RRIterator memory dsrrs,
RRUtils.DNSKEY memory dnskey,
bytes memory keyrdata
) internal view returns (bool) {
uint16 keytag = keyrdata.computeKeytag();
for (; !dsrrs.done(); dsrrs.next()) {
bytes memory proofName = dsrrs.name();
if (!proofName.equals(keyname)) {
revert ProofNameMismatch(keyname, proofName);
}
RRUtils.DS memory ds = dsrrs.data.readDS(
dsrrs.rdataOffset,
dsrrs.nextOffset - dsrrs.rdataOffset
);
if (ds.keytag != keytag) {
continue;
}
if (ds.algorithm != dnskey.algorithm) {
continue;
}
Buffer.buffer memory buf;
buf.init(keyname.length + keyrdata.length);
buf.append(keyname);
buf.append(keyrdata);
if (verifyDSHash(ds.digestType, buf.buf, ds.digest)) {
return true;
}
}
return false;
}
/**
* @dev Attempts to verify a DS record's hash value against some data.
* @param digesttype The digest ID from the DS record.
* @param data The data to digest.
* @param digest The digest data to check against.
* @return True iff the digest matches.
*/
function verifyDSHash(
uint8 digesttype,
bytes memory data,
bytes memory digest
) internal view returns (bool) {
if (address(digests[digesttype]) == address(0)) {
return false;
}
return digests[digesttype].verify(data, digest);
}
}