Skip to content

Commit

Permalink
TLSA, SMIMEA: add BIND support (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
msimerson committed Mar 15, 2022
1 parent 7a0151f commit 2a8fd69
Show file tree
Hide file tree
Showing 9 changed files with 283 additions and 11 deletions.
5 changes: 5 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
#### 1.N.N - YYYY-MM-DD


#### 0.9.1 - 2022-03-14

- TLSA, SMIMEA: add BIND support #13


#### 0.9.0 - 2022-03-10

- added null object instantiation
Expand Down
18 changes: 10 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

# dns-resource-record

DNS resource record parser, validator, exporter, and importer.
DNS resource record parser, validator, importer, and exporter.


## SYNOPSIS
Expand All @@ -12,18 +12,19 @@ This module is used to:

- validate well formedness and RFC compliance of DNS resource records
- import RRs from:
- [x] JSON
- [x] [BIND](https://www.isc.org/bind/) zone [file format](https://bind9.readthedocs.io/en/latest/reference.html#zone-file)
- [x] tinydns [data format](https://cr.yp.to/djbdns/tinydns-data.html)
- export RRs to:
- [x] BIND zone file format
- [x] tinydns data format

This module intends to import and export RFC compliant DNS resource records. If you can pass invalid resource records through this library, or cannot pass valid records through, please [raise an issue](https://github.com/msimerson/dns-resource-record/issues).
This module intends to import and export RFC compliant DNS resource records. Please [raise an issue](https://github.com/msimerson/dns-resource-record/issues) if you cannot pass a valid resource record or you can pass an invalid resource record.


## USAGE

Load the index for access to all RR types
Load the index for access to all RR types:

```js
const RR = require('dns-resource-record')
Expand Down Expand Up @@ -88,7 +89,7 @@ const A = require('dns-resource-record').A
const validatedA = new A(exampleRRs.A)
```

We can also manipulate the validated record using specially named setter functions:
Manipulate the validated record using pattern named setter functions:

```js
console.log(validatedA.toBind())
Expand All @@ -99,7 +100,7 @@ console.log(validatedA.toBind())
test.example.com 3600 IN A 192.0.2.128
```

The setter functions are named: `setFieldname`, where fieldname are the resource records fields. For any RR, you can get the field names with `getFields()`:
The setter functions are named: `set` + `Field`, where field is the resource record field name to modify. Multi-word names are camel cased, so a field named `Certificate Usage` would have a setter named `setCertificateUsage`. You can get the field names with `getFields()`:

```js
> validatedA.getFields()
Expand Down Expand Up @@ -142,7 +143,7 @@ ns1.example.com 3600 IN CAA 0 issue "http://letsencrypt.org"

### set

Don't tell anyone but the DNS validation checks can be bypassed entirely by using 'set':
The DNS validation checks can be bypassed entirely by using 'set':

```js
> validatedA.set('address', 'oops')
Expand Down Expand Up @@ -180,11 +181,12 @@ PRs are welcome, especially PRs with tests.
| **NAPTR** |:white_check_mark:|:white_check_mark:|:white_check_mark:| |:white_check_mark:|
| **NS** |:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|
| **PTR** |:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|
| **SMIMEA** | | | | | |
| **SMIMEA** |:white_check_mark:| |:white_check_mark:| | |
| **SOA** |:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|
| **SPF** |:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|
| **SRV** |:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|
| **SSHFP** |:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|
| **TLSA** |:white_check_mark:| |:white_check_mark:| | |
| **TXT** |:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|
| **URI** |:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|

Expand All @@ -193,6 +195,6 @@ PRs are welcome, especially PRs with tests.
- [x] Change all IPs to use [RFC example/doc](https://en.wikipedia.org/wiki/Reserved_IP_addresses) address space
- [x] change all domains to use reserved doc names
- [x] import tests from nictool/server/t/12_records.t
- [x] add defaults for empty values like TTL?
- [x] add defaults for empty values like TTL
- [x] DNSSEC RRs, except: RRSIG, NSEC, NSEC3, NSEC3PARAM
- [ ] Additional RRs?: KX, CERT, DHCID, TLSA, ...
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"name": "dns-resource-record",
"version": "0.9.0",
"version": "0.9.1",
"description": "DNS Resource Records",
"main": "rr/index.js",
"scripts": {
"cover": "NODE_ENV=cov npx nyc --reporter=lcovonly npm run test",
"lint": "npx eslint lib/*.js rr/*.js test/*.js",
"lintfix": "npx eslint --fix lib/*/js rr/*.js test/*.js",
"lintfix": "npx eslint --fix lib/*.js rr/*.js test/*.js",
"test": "npx mocha"
},
"repository": {
Expand Down
7 changes: 6 additions & 1 deletion rr/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,16 @@ class RR extends Map {
this.setType (opts?.type)

for (const f of this.getFields('rdata')) {
const fnName = `set${f.charAt(0).toUpperCase() + f.slice(1)}`
const fnName = `set${this.ucfirst(f)}`
if (this[fnName] === undefined) throw new Error(`Missing ${fnName} in class ${this.get('type')}`)
this[fnName](opts[f])
}
}

ucfirst (str) {
return str.split(/\s/).map(w => w.charAt(0).toUpperCase() + w.slice(1)).join('')
}

setClass (c) {
switch (c) {
case 'IN':
Expand Down
80 changes: 80 additions & 0 deletions rr/smimea.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@

const RR = require('./index').RR

class SMIMEA extends RR {
constructor (opts) {
super(opts)
}

/****** Resource record specific setters *******/
setCertificateUsage (val) {
if (![ 0,1,2,3 ].includes(val))
throw new Error(`SMIMEA: certificate usage invalid, see ${this.getRFCs()}`)

this.set('certificate usage', val)
}

setSelector (val) {
if (![ 0,1 ].includes(val))
throw new Error(`SMIMEA: selector invalid, see ${this.getRFCs()}`)

this.set('selector', val)
}

setMatchingType (val) {
if (![ 0,1,2 ].includes(val))
throw new Error(`SMIMEA: matching type, see ${this.getRFCs()}`)

this.set('matching type', val)
}

setCertificateAssociationData (val) {
this.set('certificate association data', val)
}


getDescription () {
return 'S/MIME cert association'
}

getRdataFields (arg) {
return [ 'certificate usage', 'selector', 'matching type', 'certificate association data' ]
}

getRFCs () {
return [ 8162 ]
}

getTypeId () {
return 53
}

getQuotedFields () {
return [ ]
}

/****** IMPORTERS *******/
// fromTinydns (str) {
// }

fromBind (str) {
// test.example.com 3600 IN SMIMEA, usage, selector, match, data
const [ fqdn, ttl, c, type, usage, selector, match ] = str.split(/\s+/)
return new this.constructor({
name : fqdn,
ttl : parseInt(ttl, 10),
class : c,
type : type,
'certificate usage' : parseInt(usage, 10),
selector : parseInt(selector, 10),
'matching type' : parseInt(match , 10),
'certificate association data': str.split(/\s+/).slice(7).join(' ').trim(),
})
}

/****** EXPORTERS *******/
// toTinydns () {
// }
}

module.exports = SMIMEA
79 changes: 79 additions & 0 deletions rr/tlsa.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@

const RR = require('./index').RR

class TLSA extends RR {
constructor (opts) {
super(opts)
}

/****** Resource record specific setters *******/
setCertificateUsage (val) {
if (![ 0,1,2,3 ].includes(val))
throw new Error(`TLSA: certificate usage invalid, see ${this.getRFCs()}`)

this.set('certificate usage', val)
}

setSelector (val) {
if (![ 0,1 ].includes(val))
throw new Error(`TLSA: selector invalid, see ${this.getRFCs()}`)

this.set('selector', val)
}

setMatchingType (val) {
if (![ 0,1,2 ].includes(val))
throw new Error(`TLSA: matching type, see ${this.getRFCs()}`)

this.set('matching type', val)
}

setCertificateAssociationData (val) {
this.set('certificate association data', val)
}

getDescription () {
return 'TLSA certificate association'
}

getRdataFields (arg) {
return [ 'certificate usage', 'selector', 'matching type', 'certificate association data' ]
}

getRFCs () {
return [ 6698 ]
}

getTypeId () {
return 52
}

getQuotedFields () {
return [ ]
}

/****** IMPORTERS *******/
// fromTinydns (str) {
// }

fromBind (str) {
// test.example.com 3600 IN TLSA, usage, selector, match, data
const [ fqdn, ttl, c, type, usage, selector, match ] = str.split(/\s+/)
return new this.constructor({
name : fqdn,
ttl : parseInt(ttl, 10),
class : c,
type : type,
'certificate usage' : parseInt(usage, 10),
selector : parseInt(selector, 10),
'matching type' : parseInt(match , 10),
'certificate association data': str.split(/\s+/).slice(7).join(' ').trim(),
})
}

/****** EXPORTERS *******/
// toTinydns () {
// }
}

module.exports = TLSA
1 change: 1 addition & 0 deletions test/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ exports.valid = (type, validRecords, defaults) => {
it(`parses record: ${val.name}`, async function () {
if (defaults) val.default = defaults
const r = new type(val)
if (defaults) delete val.default
if (process.env.DEBUG) console.dir(r)

for (const k of Object.keys(val)) {
Expand Down
44 changes: 44 additions & 0 deletions test/smimea.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@

// const assert = require('assert')

const base = require('./base')

const SMIMEA = require('../rr/tlsa')

const validRecords = [
{
class : 'IN',
name : '_443._tcp.www.example.com',
type : 'SMIMEA',
ttl : 3600,
'certificate usage' : 0,
selector : 0,
'matching type' : 1,
'certificate association data': '( d2abde240d7cd3ee6b4b28c54df034b9 7983a1d16e8a410e4561cb106618e971 )',
testB : '_443._tcp.www.example.com\t3600\tIN\tSMIMEA\t0\t0\t1\t( d2abde240d7cd3ee6b4b28c54df034b9 7983a1d16e8a410e4561cb106618e971 )\n',
// testT : '',
},
]

const invalidRecords = [
{
// name : 'test.example.com',
selector: 6, // invalid
},
]

describe('SMIMEA record', function () {
base.valid(SMIMEA, validRecords, { ttl: 3600 })
base.invalid(SMIMEA, invalidRecords, { ttl: 3600 })

base.getDescription(SMIMEA)
base.getRFCs(SMIMEA, validRecords[0])
base.getFields(SMIMEA, [ 'certificate usage', 'selector', 'matching type', 'certificate association data' ])
base.getTypeId(SMIMEA, 52)

base.toBind(SMIMEA, validRecords)
// base.toTinydns(SMIMEA, validRecords)

base.fromBind(SMIMEA, validRecords)
// base.fromTinydns(SMIMEA, validRecords)
})
56 changes: 56 additions & 0 deletions test/tlsa.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@

// const assert = require('assert')

const base = require('./base')

const TLSA = require('../rr/tlsa')

const validRecords = [
{
class : 'IN',
name : '_443._tcp.www.example.com',
type : 'TLSA',
ttl : 3600,
'certificate usage' : 0,
selector : 0,
'matching type' : 1,
'certificate association data': '( d2abde240d7cd3ee6b4b28c54df034b9 7983a1d16e8a410e4561cb106618e971 )',
testB : '_443._tcp.www.example.com\t3600\tIN\tTLSA\t0\t0\t1\t( d2abde240d7cd3ee6b4b28c54df034b9 7983a1d16e8a410e4561cb106618e971 )\n',
// testT : '',
},
{
class : 'IN',
name : '_443._tcp.www.example.com',
type : 'TLSA',
ttl : 3600,
'certificate usage' : 1,
selector : 1,
'matching type' : 2,
'certificate association data': '( 92003ba34942dc74152e2f2c408d29ec a5a520e7f2e06bb944f4dca346baf63c 1b177615d466f6c4b71c216a50292bd5 8c9ebdd2f74e38fe51ffd48c43326cbc )',
testB : `_443._tcp.www.example.com\t3600\tIN\tTLSA\t1\t1\t2\t( 92003ba34942dc74152e2f2c408d29ec a5a520e7f2e06bb944f4dca346baf63c 1b177615d466f6c4b71c216a50292bd5 8c9ebdd2f74e38fe51ffd48c43326cbc )\n`,
testT : '',
},
]

const invalidRecords = [
{
// name : 'test.example.com',
selector: 6, // invalid
},
]

describe('TLSA record', function () {
base.valid(TLSA, validRecords)
base.invalid(TLSA, invalidRecords, { ttl: 3600 })

base.getDescription(TLSA)
base.getRFCs(TLSA, validRecords[0])
base.getFields(TLSA, [ 'certificate usage', 'selector', 'matching type', 'certificate association data' ])
base.getTypeId(TLSA, 52)

base.toBind(TLSA, validRecords)
// base.toTinydns(TLSA, validRecords)

base.fromBind(TLSA, validRecords)
// base.fromTinydns(TLSA, validRecords)
})

0 comments on commit 2a8fd69

Please sign in to comment.