├── .eslintrc.json ├── .github └── workflows │ └── unit-test.yml ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── bin ├── bns-keygen ├── bns-prove ├── dig.js ├── dig2json ├── json2dig ├── json2rr ├── json2zone ├── named.js ├── read.js ├── rr2json ├── whois.js └── zone2json ├── etc └── whois.json ├── hints ├── anchors.json ├── anchors.zone ├── hints.json └── hints.zone ├── lib ├── api.js ├── authority.js ├── bns.js ├── cache.js ├── constants.js ├── dane.js ├── dns.js ├── dnssec.js ├── encoding.js ├── error.js ├── hints.js ├── hosts.js ├── hsig.js ├── internal │ ├── crypto.js │ ├── iana.js │ ├── keys.js │ ├── lazy-browser.js │ ├── lazy.js │ ├── net.js │ ├── scan.js │ └── schema.js ├── nsec3.js ├── openpgpkey.js ├── ownership.js ├── punycode.js ├── rdns.js ├── resolvconf.js ├── resolver │ ├── dns.js │ ├── recursive.js │ ├── root.js │ ├── stub.js │ ├── ub.js │ ├── unbound-browser.js │ └── unbound.js ├── roothints.js ├── server │ ├── auth.js │ ├── dns.js │ ├── recursive.js │ ├── stub.js │ └── unbound.js ├── sig0.js ├── smimea.js ├── srv.js ├── sshfp.js ├── tlsa.js ├── tsig.js ├── udns.js ├── util.js ├── wire.js └── zone.js ├── package.json └── test ├── authority-test.js ├── bns-test.js ├── cache-test.js ├── constants-test.js ├── dane-test.js ├── data ├── Knlnetlabs.nl.+003+03793.key ├── Knlnetlabs.nl.+003+03793.private ├── com-glue.zone ├── com-response.zone ├── dnssec-verify-1.json ├── dnssec-verify-2.json ├── dnssec-verify-3.json ├── dnssec-verify-4.json ├── dnssec-verify-5.json ├── dsa-test.zone ├── ed25519-test.zone ├── ed448-test.zone ├── msg-json.json ├── msg-raw.json ├── nsec3-vectors.json ├── nx-response.zone ├── openpgpkey.zone ├── ownership.json ├── root.json ├── root.zone └── server-records.json ├── dnssec-test.js ├── encoding-test.js ├── hints-test.js ├── hosts-test.js ├── hsig-test.js ├── iana-test.js ├── nsec3-test.js ├── openpgpkey-test.js ├── ownership-test.js ├── proof-test.js ├── punycode-test.js ├── record-test.js ├── recursive-test.js ├── resolvconf-test.js ├── server-test.js ├── sig0-test.js ├── smimea-test.js ├── srv-test.js ├── sshfp-test.js ├── stub-test.js ├── tlsa-test.js ├── tsig-test.js ├── util-test.js ├── wire-test.js └── zone-test.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "node": true 5 | }, 6 | "extends": "eslint:recommended", 7 | "globals": { 8 | "Atomics": "readable", 9 | "BigInt": "readable", 10 | "BigInt64Array": "readable", 11 | "BigUint64Array": "readable", 12 | "queueMicrotask": "readable", 13 | "SharedArrayBuffer": "readable", 14 | "TextEncoder": "readable", 15 | "TextDecoder": "readable" 16 | }, 17 | "overrides": [ 18 | { 19 | "files": ["*.mjs"], 20 | "parserOptions": { 21 | "sourceType": "module" 22 | } 23 | }, 24 | { 25 | "files": ["*.cjs"], 26 | "parserOptions": { 27 | "sourceType": "script" 28 | } 29 | }, 30 | { 31 | "files": [ 32 | "test/{,**/}*.{mjs,cjs,js}" 33 | ], 34 | "env": { 35 | "mocha": true 36 | }, 37 | "globals": { 38 | "register": "readable" 39 | }, 40 | "rules": { 41 | "max-len": "off", 42 | "prefer-arrow-callback": "off" 43 | } 44 | } 45 | ], 46 | "parser": "babel-eslint", 47 | "parserOptions": { 48 | "ecmaVersion": 10, 49 | "ecmaFeatures": { 50 | "globalReturn": true 51 | }, 52 | "requireConfigFile": false, 53 | "sourceType": "script" 54 | }, 55 | "root": true, 56 | "rules": { 57 | "array-bracket-spacing": ["error", "never"], 58 | "arrow-parens": ["error", "as-needed", { 59 | "requireForBlockBody": true 60 | }], 61 | "arrow-spacing": "error", 62 | "block-spacing": ["error", "always"], 63 | "brace-style": ["error", "1tbs"], 64 | "camelcase": ["error", { 65 | "properties": "never" 66 | }], 67 | "comma-dangle": ["error", "never"], 68 | "consistent-return": "error", 69 | "eol-last": ["error", "always"], 70 | "eqeqeq": ["error", "always", { 71 | "null": "ignore" 72 | }], 73 | "func-name-matching": "error", 74 | "indent": ["off", 2, { 75 | "ArrayExpression": "off", 76 | "SwitchCase": 1, 77 | "CallExpression": { 78 | "arguments": "off" 79 | }, 80 | "FunctionDeclaration": { 81 | "parameters": "off" 82 | }, 83 | "FunctionExpression": { 84 | "parameters": "off" 85 | }, 86 | "MemberExpression": "off", 87 | "ObjectExpression": "off", 88 | "ImportDeclaration": "off" 89 | }], 90 | "handle-callback-err": "off", 91 | "linebreak-style": ["error", "unix"], 92 | "max-len": ["error", { 93 | "code": 80, 94 | "ignorePattern": "function \\w+\\(", 95 | "ignoreUrls": true 96 | }], 97 | "max-statements-per-line": ["error", { 98 | "max": 1 99 | }], 100 | "new-cap": ["error", { 101 | "newIsCap": true, 102 | "capIsNew": false 103 | }], 104 | "new-parens": "error", 105 | "no-buffer-constructor": "error", 106 | "no-console": "off", 107 | "no-extra-semi": "off", 108 | "no-fallthrough": "off", 109 | "no-func-assign": "off", 110 | "no-implicit-coercion": "error", 111 | "no-multi-assign": "error", 112 | "no-multiple-empty-lines": ["error", { 113 | "max": 1 114 | }], 115 | "no-nested-ternary": "error", 116 | "no-param-reassign": "off", 117 | "no-return-assign": "error", 118 | "no-return-await": "off", 119 | "no-shadow-restricted-names": "error", 120 | "no-tabs": "error", 121 | "no-trailing-spaces": "error", 122 | "no-unused-vars": ["error", { 123 | "vars": "all", 124 | "args": "none", 125 | "ignoreRestSiblings": false 126 | }], 127 | "no-use-before-define": ["error", { 128 | "functions": false, 129 | "classes": false 130 | }], 131 | "no-useless-escape": "off", 132 | "no-var": "error", 133 | "nonblock-statement-body-position": ["error", "below"], 134 | "padded-blocks": ["error", "never"], 135 | "prefer-arrow-callback": "error", 136 | "prefer-const": ["error", { 137 | "destructuring": "all", 138 | "ignoreReadBeforeAssign": true 139 | }], 140 | "prefer-template": "off", 141 | "quotes": ["error", "single"], 142 | "semi": ["error", "always"], 143 | "spaced-comment": ["error", "always", { 144 | "exceptions": ["!"] 145 | }], 146 | "space-before-blocks": "error", 147 | "strict": "error", 148 | "unicode-bom": ["error", "never"], 149 | "valid-jsdoc": "error", 150 | "wrap-iife": ["error", "inside"] 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /.github/workflows/unit-test.yml: -------------------------------------------------------------------------------- 1 | name: Unit Tests 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | tests: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node_version: [10.x, 12.x, 14.x] 16 | 17 | steps: 18 | - name: Checkout Unbound 19 | uses: actions/checkout@v2 20 | with: 21 | repository: nlnetlabs/unbound 22 | path: ub 23 | 24 | - name: Install Unbound 25 | working-directory: ub 26 | run: ./configure && 27 | make && 28 | sudo make install && 29 | sudo ldconfig 30 | 31 | - name: Checkout 32 | uses: actions/checkout@v2 33 | 34 | - name: Setup 35 | uses: actions/setup-node@v1 36 | 37 | - name: Install 38 | run: npm install 39 | 40 | - name: Test 41 | run: npm run test 42 | 43 | - name: Browser Test 44 | run: npm install browserify && npm run test-browser 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | node_modules/ 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .babel* 2 | .bmocharc* 3 | .bpkgignore 4 | .editorconfig 5 | .eslint* 6 | .git* 7 | .mocharc* 8 | .yarnignore 9 | bench/ 10 | build/ 11 | docs/ 12 | node_modules/ 13 | npm-debug.log 14 | package-lock.json 15 | test/ 16 | webpack.*.js 17 | yarn.lock 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This software is licensed under the MIT License. 2 | 3 | Copyright (c) 2018, Christopher Jeffrey (https://github.com/chjj) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | Parts of this software are based on miekg/dns and golang/go: 24 | https://github.com/miekg/dns 25 | https://github.com/golang/go 26 | 27 | Extensions of the original work are copyright (c) 2011 Miek Gieben 28 | 29 | As this is fork of the official Go code the same license applies: 30 | 31 | Copyright (c) 2009 The Go Authors. All rights reserved. 32 | 33 | Redistribution and use in source and binary forms, with or without 34 | modification, are permitted provided that the following conditions are 35 | met: 36 | 37 | * Redistributions of source code must retain the above copyright 38 | notice, this list of conditions and the following disclaimer. 39 | * Redistributions in binary form must reproduce the above 40 | copyright notice, this list of conditions and the following disclaimer 41 | in the documentation and/or other materials provided with the 42 | distribution. 43 | * Neither the name of Google Inc. nor the names of its 44 | contributors may be used to endorse or promote products derived from 45 | this software without specific prior written permission. 46 | 47 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 48 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 49 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 50 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 51 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 52 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 53 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 54 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 55 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 56 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 57 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 58 | 59 | Parts of this software are based on solvere: 60 | https://github.com/rolandshoemaker/solvere 61 | 62 | MIT License 63 | 64 | Copyright (c) 2016 Roland Bracewell Shoemaker 65 | 66 | Permission is hereby granted, free of charge, to any person obtaining a copy 67 | of this software and associated documentation files (the "Software"), to deal 68 | in the Software without restriction, including without limitation the rights 69 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 70 | copies of the Software, and to permit persons to whom the Software is 71 | furnished to do so, subject to the following conditions: 72 | 73 | The above copyright notice and this permission notice shall be included in all 74 | copies or substantial portions of the Software. 75 | 76 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 77 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 78 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 79 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 80 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 81 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 82 | SOFTWARE. 83 | -------------------------------------------------------------------------------- /bin/bns-keygen: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | process.title = 'bns-keygen'; 6 | 7 | process.on('uncaughtException', (err) => { 8 | console.error(err.message); 9 | process.exit(1); 10 | }); 11 | 12 | const pkg = require('../package.json'); 13 | const constants = require('../lib/constants'); 14 | const dnssec = require('../lib/dnssec'); 15 | const util = require('../lib/util'); 16 | 17 | const { 18 | algs, 19 | stringToAlg, 20 | keyFlags 21 | } = constants; 22 | 23 | let alg = algs.RSASHA256; 24 | let bits = 2048; 25 | let flags = keyFlags.ZONE; 26 | let name = '.'; 27 | let dir = null; 28 | let ttl = -1; 29 | 30 | for (let i = 2; i < process.argv.length; i++) { 31 | const arg = process.argv[i]; 32 | const next = i !== process.argv.length - 1 33 | ? process.argv[i + 1] 34 | : ''; 35 | 36 | switch (arg) { 37 | case '-a': { 38 | alg = stringToAlg(next); 39 | 40 | if (alg === algs.DSA || alg === algs.DSANSEC3SHA1) { 41 | if (bits > 1024) 42 | bits = 1024; 43 | } 44 | 45 | i += 1; 46 | 47 | break; 48 | } 49 | 50 | case '-b': { 51 | bits = util.parseU16(next); 52 | 53 | if (bits < 768 || bits > 4096) 54 | throw new Error('Invalid bits.'); 55 | 56 | i += 1; 57 | 58 | break; 59 | } 60 | 61 | case '-n': { 62 | if (next !== 'ZONE') 63 | throw new Error('Invalid name type.'); 64 | 65 | i += 1; 66 | 67 | break; 68 | } 69 | 70 | case '-f': { 71 | if (!keyFlags.hasOwnProperty(next)) 72 | throw new Error('Unknown flag.'); 73 | 74 | flags |= keyFlags[next]; 75 | i += 1; 76 | 77 | break; 78 | } 79 | 80 | case '-K': { 81 | if (!next) 82 | throw new Error('Invalid directory.'); 83 | 84 | dir = next; 85 | i += 1; 86 | 87 | break; 88 | } 89 | 90 | case '-L': { 91 | ttl = util.parseU32(next); 92 | i += 1; 93 | break; 94 | } 95 | 96 | case '-h': 97 | case '--help': 98 | case '-?': 99 | case '-v': { 100 | console.log(`bns-keygen ${pkg.version}`); 101 | process.exit(0); 102 | break; 103 | } 104 | 105 | default: { 106 | if (!util.isName(arg)) 107 | throw new Error('Invalid name.'); 108 | name = util.fqdn(arg.toLowerCase()); 109 | break; 110 | } 111 | } 112 | } 113 | 114 | (async () => { 115 | const priv = await dnssec.createPrivateAsync(alg, bits); 116 | const pub = dnssec.createPublic(alg, priv); 117 | const key = dnssec.createKey(name, alg, pub, flags); 118 | 119 | if (ttl !== -1) 120 | key.ttl = ttl; 121 | 122 | if (dir) { 123 | await dnssec.writeKeysAsync(dir, pub, priv); 124 | return; 125 | } 126 | 127 | const txt = dnssec.encodePrivate(alg, priv); 128 | 129 | process.stdout.write(txt + '\n'); 130 | process.stdout.write('\n'); 131 | process.stdout.write(key.toString() + '\n'); 132 | })().catch((err) => { 133 | process.stderr.write(err.stack + '\n'); 134 | process.exit(1); 135 | }); 136 | -------------------------------------------------------------------------------- /bin/bns-prove: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | process.title = 'bns-prove'; 6 | 7 | process.on('uncaughtException', (err) => { 8 | console.error(err.message); 9 | process.exit(1); 10 | }); 11 | 12 | const pkg = require('../package.json'); 13 | const constants = require('../lib/constants'); 14 | const dnssec = require('../lib/dnssec'); 15 | const Resolver = require('../lib/resolver/stub'); 16 | const Ownership = require('../lib/ownership'); 17 | const util = require('../lib/util'); 18 | const wire = require('../lib/wire'); 19 | 20 | const { 21 | keyFlags, 22 | classes, 23 | types 24 | } = constants; 25 | 26 | const { 27 | Record, 28 | TXTRecord 29 | } = wire; 30 | 31 | let hex = false; 32 | let base64 = false; 33 | let secure = false; 34 | let hardened = false; 35 | let lifespan = 365 * 24 * 60 * 60; 36 | let dir = '.'; 37 | let name = null; 38 | let txt = null; 39 | 40 | for (let i = 2; i < process.argv.length; i++) { 41 | const arg = process.argv[i]; 42 | const next = i !== process.argv.length - 1 43 | ? process.argv[i + 1] 44 | : ''; 45 | 46 | switch (arg) { 47 | case '-x': { 48 | hex = true; 49 | break; 50 | } 51 | 52 | case '-b': { 53 | base64 = true; 54 | break; 55 | } 56 | 57 | case '-s': { 58 | secure = true; 59 | break; 60 | } 61 | 62 | case '-r': { 63 | hardened = true; 64 | break; 65 | } 66 | 67 | case '-t': { 68 | lifespan = util.parseU32(lifespan); 69 | i += 1; 70 | break; 71 | } 72 | 73 | case '-K': { 74 | if (!next) 75 | throw new Error('Invalid directory.'); 76 | 77 | dir = next; 78 | i += 1; 79 | 80 | break; 81 | } 82 | 83 | case '-h': 84 | case '--help': 85 | case '-?': 86 | case '-v': { 87 | console.log(`bns-prove ${pkg.version}`); 88 | process.exit(0); 89 | break; 90 | } 91 | 92 | default: { 93 | if (!name) { 94 | if (!util.isName(arg)) 95 | throw new Error('Invalid name.'); 96 | name = util.fqdn(arg.toLowerCase()); 97 | } else if (!txt) { 98 | txt = arg; 99 | } 100 | break; 101 | } 102 | } 103 | } 104 | 105 | if (!name || name === '.') 106 | throw new Error('No name provided.'); 107 | 108 | if (!txt) 109 | throw new Error('No text provided.'); 110 | 111 | (async () => { 112 | const ctx = new Ownership(); 113 | 114 | ctx.Resolver = Resolver; 115 | ctx.secure = secure; 116 | 117 | const proof = await ctx.prove(name, true); 118 | 119 | if (hardened && ctx.isWeak(proof)) 120 | throw new Error('Cannot use RSA-1024 with hardening enabled.'); 121 | 122 | const zone = proof.zones[proof.zones.length - 1]; 123 | 124 | zone.claim.length = 0; 125 | 126 | for (const key of zone.keys) { 127 | if (key.type !== types.DNSKEY) 128 | continue; 129 | 130 | const kd = key.data; 131 | 132 | if (!(kd.flags & keyFlags.ZONE)) 133 | continue; 134 | 135 | if (kd.flags & keyFlags.SEP) 136 | continue; 137 | 138 | if (kd.flags & keyFlags.REVOKE) 139 | continue; 140 | 141 | if (!ctx.verifyKey(key, hardened)) 142 | continue; 143 | 144 | const priv = await dnssec.readPrivateAsync(dir, key); 145 | 146 | if (!priv) 147 | continue; 148 | 149 | const rr = new Record(); 150 | const rd = new TXTRecord(); 151 | 152 | rr.name = name; 153 | rr.type = types.TXT; 154 | rr.class = classes.IN; 155 | rr.ttl = 3600; 156 | rr.data = rd; 157 | 158 | rd.txt.push(txt); 159 | 160 | const sig = dnssec.sign(key, priv, [rr], lifespan); 161 | 162 | zone.claim.push(rr); 163 | zone.claim.push(sig); 164 | 165 | break; 166 | } 167 | 168 | if (zone.claim.length === 0) 169 | throw new Error('Could not find suitable key to sign with.'); 170 | 171 | if (hex) 172 | process.stdout.write(proof.toHex() + '\n'); 173 | else if (base64) 174 | process.stdout.write(proof.toBase64() + '\n'); 175 | else 176 | process.stdout.write(proof.toString() + '\n'); 177 | })().catch((err) => { 178 | console.error(err.message); 179 | process.exit(1); 180 | }); 181 | -------------------------------------------------------------------------------- /bin/dig2json: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | process.title = 'dig2json'; 6 | 7 | const {read} = require('./read'); 8 | const {Message} = require('../lib/wire'); 9 | 10 | read(async (file, input) => { 11 | const msg = Message.fromString(input); 12 | const json = msg.toJSON(); 13 | const str = JSON.stringify(json, null, 2); 14 | 15 | process.stdout.write(str + '\n'); 16 | }); 17 | -------------------------------------------------------------------------------- /bin/json2dig: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | process.title = 'json2dig'; 6 | 7 | const {read} = require('./read'); 8 | const {Message} = require('../lib/wire'); 9 | 10 | const now = Date.now(); 11 | 12 | read(async (file, input) => { 13 | const json = JSON.parse(input); 14 | const msg = Message.fromJSON(json); 15 | const str = msg.toString(now); 16 | 17 | process.stdout.write(str + '\n'); 18 | }); 19 | -------------------------------------------------------------------------------- /bin/json2rr: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | process.title = 'json2rr'; 6 | 7 | const {readArg} = require('./read'); 8 | const {Record} = require('../lib/wire'); 9 | 10 | readArg(async (file, input) => { 11 | const json = JSON.parse(input); 12 | const rr = Record.fromJSON(json); 13 | const str = rr.toString(); 14 | 15 | process.stdout.write(str + '\n'); 16 | }); 17 | -------------------------------------------------------------------------------- /bin/json2zone: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | process.title = 'json2zone'; 6 | 7 | const {read} = require('./read'); 8 | const wire = require('../lib/wire'); 9 | const {Record} = wire; 10 | 11 | read(async (file, input) => { 12 | const items = JSON.parse(input); 13 | 14 | if (!Array.isArray(items)) 15 | throw new Error('Not a zone.'); 16 | 17 | const zone = []; 18 | 19 | for (const json of items) { 20 | const rr = Record.fromJSON(json); 21 | zone.push(rr); 22 | } 23 | 24 | const str = wire.toZone(zone); 25 | 26 | process.stdout.write(str); 27 | }); 28 | -------------------------------------------------------------------------------- /bin/named.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | process.title = 'named.js'; 6 | 7 | const pkg = require('../package.json'); 8 | const AuthServer = require('../lib/server/auth'); 9 | const RecursiveServer = require('../lib/server/recursive'); 10 | const UnboundServer = require('../lib/server/unbound'); 11 | const StubServer = require('../lib/server/stub'); 12 | const util = require('../lib/util'); 13 | 14 | let host = '127.0.0.1'; 15 | let port = 53; 16 | let confFile = null; 17 | let hostsFile = null; 18 | let recursive = false; 19 | let unbound = false; 20 | let minimize = false; 21 | let hintsFile = null; 22 | let origin = '.'; 23 | let zoneFile = null; 24 | let inet6 = null; 25 | let tcp = true; 26 | let edns = true; 27 | let dnssec = false; 28 | let debug = false; 29 | 30 | for (let i = 2; i < process.argv.length; i++) { 31 | const arg = process.argv[i]; 32 | 33 | if (arg.length === 0) 34 | throw new Error('Unexpected argument.'); 35 | 36 | switch (arg) { 37 | case '-4': 38 | inet6 = false; 39 | break; 40 | case '-6': 41 | inet6 = true; 42 | break; 43 | case '-p': 44 | port = util.parseU16(process.argv[i + 1]); 45 | i += 1; 46 | break; 47 | case '--conf': 48 | confFile = process.argv[i + 1]; 49 | i += 1; 50 | break; 51 | case '--hosts': 52 | hostsFile = process.argv[i + 1]; 53 | i += 1; 54 | break; 55 | case '-r': 56 | case '--recursive': 57 | recursive = true; 58 | break; 59 | case '-u': 60 | case '--unbound': 61 | unbound = true; 62 | recursive = true; 63 | break; 64 | case '-m': 65 | case '--minimize': 66 | minimize = true; 67 | break; 68 | case '--hints': 69 | hintsFile = process.argv[i + 1]; 70 | i += 1; 71 | break; 72 | case '-o': 73 | case '--origin': 74 | origin = process.argv[i + 1]; 75 | i += 1; 76 | break; 77 | case '-z': 78 | case '--zone': 79 | zoneFile = process.argv[i + 1]; 80 | i += 1; 81 | break; 82 | case '-h': 83 | case '--help': 84 | case '-?': 85 | case '-v': 86 | console.log(`named.js ${pkg.version}`); 87 | process.exit(0); 88 | break; 89 | case '+vc': 90 | case '+tcp': 91 | tcp = true; 92 | break; 93 | case '+novc': 94 | case '+notcp': 95 | tcp = false; 96 | break; 97 | case '+edns': 98 | edns = true; 99 | break; 100 | case '+noedns': 101 | edns = false; 102 | break; 103 | case '+dnssec': 104 | edns = true; 105 | dnssec = true; 106 | break; 107 | case '+nodnssec': 108 | dnssec = false; 109 | break; 110 | case '+debug': 111 | debug = true; 112 | break; 113 | case '+nodebug': 114 | debug = false; 115 | break; 116 | default: 117 | if (arg[0] === '@') { 118 | host = arg.substring(1); 119 | break; 120 | } 121 | 122 | if (zoneFile) { 123 | origin = arg; 124 | break; 125 | } 126 | 127 | throw new Error(`Unexpected argument: ${arg}.`); 128 | } 129 | } 130 | 131 | let Server; 132 | 133 | if (zoneFile) 134 | Server = AuthServer; 135 | else if (unbound) 136 | Server = UnboundServer; 137 | else if (recursive) 138 | Server = RecursiveServer; 139 | else 140 | Server = StubServer; 141 | 142 | const server = new Server({ 143 | inet6, 144 | tcp, 145 | edns, 146 | dnssec, 147 | minimize 148 | }); 149 | 150 | if (zoneFile) { 151 | server.setOrigin(origin); 152 | server.setFile(zoneFile); 153 | } else if (recursive) { 154 | if (hintsFile) 155 | server.hints.fromFile(hintsFile); 156 | else 157 | server.hints.fromRoot(); 158 | } else { 159 | if (confFile) 160 | server.conf.fromFile(confFile); 161 | else 162 | server.conf.fromSystem(); 163 | 164 | if (hostsFile) 165 | server.hosts.fromFile(hostsFile); 166 | else 167 | server.hosts.fromSystem(); 168 | } 169 | 170 | server.on('error', (err) => { 171 | console.error(err.stack); 172 | }); 173 | 174 | if (debug) { 175 | server.on('log', (...args) => { 176 | console.error(...args); 177 | }); 178 | 179 | server.on('query', (req, res, rinfo) => { 180 | console.error(''); 181 | console.error('Rinfo:'); 182 | console.error('Address: %s, Port: %d, TCP: %s', 183 | rinfo.address, rinfo.port, rinfo.tcp); 184 | 185 | console.error(''); 186 | console.error('Request:'); 187 | console.error(req.toString()); 188 | 189 | console.error('Response:'); 190 | console.error(res.toString()); 191 | }); 192 | } 193 | 194 | server.on('listening', () => { 195 | const {address, port} = server.address(); 196 | console.log(`Server listening on ${address}:${port}.`); 197 | }); 198 | 199 | server.open(port, host); 200 | -------------------------------------------------------------------------------- /bin/read.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | 5 | const { 6 | argv, 7 | exit, 8 | stdin, 9 | stderr 10 | } = process; 11 | 12 | async function readInput(arg) { 13 | return new Promise((resolve, reject) => { 14 | if (argv.length > 2) { 15 | if (arg) { 16 | resolve(['/dev/stdin', ...argv.slice(2)]); 17 | return; 18 | } 19 | 20 | if (argv[2] !== '-') { 21 | fs.readFile(argv[2], 'utf8', (err, text) => { 22 | if (err) { 23 | reject(err); 24 | return; 25 | } 26 | resolve([argv[2], text, ...argv.slice(3)]); 27 | }); 28 | return; 29 | } 30 | } 31 | 32 | let input = ''; 33 | 34 | stdin.setEncoding('utf8'); 35 | stdin.resume(); 36 | 37 | stdin.on('error', reject); 38 | 39 | stdin.on('data', (str) => { 40 | input += str; 41 | }); 42 | 43 | stdin.on('end', () => { 44 | resolve(['/dev/stdin', input, ...argv.slice(2)]); 45 | }); 46 | }); 47 | } 48 | 49 | async function _read(callback, arg) { 50 | try { 51 | const args = await readInput(arg); 52 | await callback(...args); 53 | } catch (e) { 54 | stderr.write(e.stack + '\n'); 55 | exit(1); 56 | } 57 | } 58 | 59 | function read(callback) { 60 | _read(callback, false); 61 | } 62 | 63 | function readArg(callback) { 64 | _read(callback, true); 65 | } 66 | 67 | exports.read = read; 68 | exports.readArg = readArg; 69 | -------------------------------------------------------------------------------- /bin/rr2json: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | process.title = 'rr2json'; 6 | 7 | const {readArg} = require('./read'); 8 | const {Record} = require('../lib/wire'); 9 | 10 | readArg(async (file, input) => { 11 | const rr = Record.fromString(input); 12 | const json = rr.toJSON(); 13 | const str = JSON.stringify(json, null, 2); 14 | 15 | process.stdout.write(str + '\n'); 16 | }); 17 | -------------------------------------------------------------------------------- /bin/whois.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | process.title = 'whois.js'; 6 | 7 | const tcp = require('btcp'); 8 | const dns = require('../lib/dns'); 9 | const encoding = require('../lib/encoding'); 10 | const util = require('../lib/util'); 11 | const whois = require('../etc/whois.json'); 12 | 13 | function getServer(name) { 14 | const labels = util.split(name); 15 | 16 | if (labels.length === 0) 17 | return null; 18 | 19 | if (labels.length === 1) 20 | return whois['.']; 21 | 22 | const sld = util.from(name, labels, -2); 23 | const tld = util.from(name, labels, -1); 24 | const server = whois[sld] || whois[tld]; 25 | 26 | if (typeof server !== 'string') 27 | return null; 28 | 29 | return server; 30 | } 31 | 32 | let name = null; 33 | let server = null; 34 | 35 | for (let i = 2; i < process.argv.length; i++) { 36 | const arg = process.argv[i]; 37 | 38 | if (arg.length === 0) 39 | throw new Error('Unexpected argument.'); 40 | 41 | switch (arg) { 42 | default: 43 | if (arg[0] === '@') { 44 | server = arg.substring(1); 45 | break; 46 | } 47 | 48 | if (!name) { 49 | name = util.fqdn(arg.toLowerCase()); 50 | break; 51 | } 52 | 53 | throw new Error(`Unexpected argument: ${arg}.`); 54 | } 55 | } 56 | 57 | if (!name) { 58 | console.error('No name provided.'); 59 | process.exit(1); 60 | return; 61 | } 62 | 63 | if (!encoding.isName(name)) { 64 | console.error(`Invalid domain name: ${name}.`); 65 | process.exit(1); 66 | return; 67 | } 68 | 69 | if (!server) 70 | server = getServer(name); 71 | 72 | if (!server) { 73 | console.error(`WHOIS server not found for: ${name}.`); 74 | process.exit(1); 75 | return; 76 | } 77 | 78 | (async () => { 79 | console.error(`WHOIS ${name} (${server})`); 80 | 81 | if (!util.isIP(server)) { 82 | const options = { all: true, hints: dns.ADDRCONFIG }; 83 | const addrs = await dns.lookup(server, options); 84 | const inet4 = addrs.filter(addr => addr.family === 4); 85 | const {address} = util.randomItem(inet4); 86 | 87 | server = address; 88 | } 89 | 90 | console.error(`Connecting to ${server}.`); 91 | console.error(''); 92 | 93 | const socket = new tcp.Socket(); 94 | 95 | socket.on('error', (err) => { 96 | console.error(err.message); 97 | process.exit(1); 98 | }); 99 | 100 | socket.once('connect', () => { 101 | socket.write(util.trimFQDN(name) + '\r\n'); 102 | }); 103 | 104 | socket.setEncoding('utf8'); 105 | socket.pipe(process.stdout); 106 | socket.connect(43, server); 107 | })().catch((err) => { 108 | console.error(err.stack); 109 | process.exit(1); 110 | }); 111 | -------------------------------------------------------------------------------- /bin/zone2json: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | process.title = 'zone2json'; 6 | 7 | const {read} = require('./read'); 8 | const wire = require('../lib/wire'); 9 | 10 | read(async (file, input, origin) => { 11 | const zone = wire.fromZone(input, origin, file); 12 | const json = []; 13 | 14 | for (const rr of zone) 15 | json.push(rr.toJSON()); 16 | 17 | const str = JSON.stringify(json, null, 2); 18 | 19 | process.stdout.write(str + '\n'); 20 | }); 21 | -------------------------------------------------------------------------------- /hints/anchors.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": ".", 4 | "ttl": 172800, 5 | "class": "IN", 6 | "type": "DS", 7 | "data": { 8 | "keyTag": 20326, 9 | "algorithm": 8, 10 | "digestType": 2, 11 | "digest": "e06d44b80b8f1d39a95c0b0d7c65d08458e880409bbc683457104237c7f8ec8d" 12 | } 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /hints/anchors.zone: -------------------------------------------------------------------------------- 1 | . 172800 IN DS 20326 8 2 E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D 2 | -------------------------------------------------------------------------------- /hints/hints.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": ".", 4 | "ttl": 3600000, 5 | "class": "IN", 6 | "type": "NS", 7 | "data": { 8 | "ns": "A.ROOT-SERVERS.NET." 9 | } 10 | }, 11 | { 12 | "name": "A.ROOT-SERVERS.NET.", 13 | "ttl": 3600000, 14 | "class": "IN", 15 | "type": "A", 16 | "data": { 17 | "address": "198.41.0.4" 18 | } 19 | }, 20 | { 21 | "name": "A.ROOT-SERVERS.NET.", 22 | "ttl": 3600000, 23 | "class": "IN", 24 | "type": "AAAA", 25 | "data": { 26 | "address": "2001:503:ba3e::2:30" 27 | } 28 | }, 29 | { 30 | "name": ".", 31 | "ttl": 3600000, 32 | "class": "IN", 33 | "type": "NS", 34 | "data": { 35 | "ns": "B.ROOT-SERVERS.NET." 36 | } 37 | }, 38 | { 39 | "name": "B.ROOT-SERVERS.NET.", 40 | "ttl": 3600000, 41 | "class": "IN", 42 | "type": "A", 43 | "data": { 44 | "address": "199.9.14.201" 45 | } 46 | }, 47 | { 48 | "name": "B.ROOT-SERVERS.NET.", 49 | "ttl": 3600000, 50 | "class": "IN", 51 | "type": "AAAA", 52 | "data": { 53 | "address": "2001:500:200::b" 54 | } 55 | }, 56 | { 57 | "name": ".", 58 | "ttl": 3600000, 59 | "class": "IN", 60 | "type": "NS", 61 | "data": { 62 | "ns": "C.ROOT-SERVERS.NET." 63 | } 64 | }, 65 | { 66 | "name": "C.ROOT-SERVERS.NET.", 67 | "ttl": 3600000, 68 | "class": "IN", 69 | "type": "A", 70 | "data": { 71 | "address": "192.33.4.12" 72 | } 73 | }, 74 | { 75 | "name": "C.ROOT-SERVERS.NET.", 76 | "ttl": 3600000, 77 | "class": "IN", 78 | "type": "AAAA", 79 | "data": { 80 | "address": "2001:500:2::c" 81 | } 82 | }, 83 | { 84 | "name": ".", 85 | "ttl": 3600000, 86 | "class": "IN", 87 | "type": "NS", 88 | "data": { 89 | "ns": "D.ROOT-SERVERS.NET." 90 | } 91 | }, 92 | { 93 | "name": "D.ROOT-SERVERS.NET.", 94 | "ttl": 3600000, 95 | "class": "IN", 96 | "type": "A", 97 | "data": { 98 | "address": "199.7.91.13" 99 | } 100 | }, 101 | { 102 | "name": "D.ROOT-SERVERS.NET.", 103 | "ttl": 3600000, 104 | "class": "IN", 105 | "type": "AAAA", 106 | "data": { 107 | "address": "2001:500:2d::d" 108 | } 109 | }, 110 | { 111 | "name": ".", 112 | "ttl": 3600000, 113 | "class": "IN", 114 | "type": "NS", 115 | "data": { 116 | "ns": "E.ROOT-SERVERS.NET." 117 | } 118 | }, 119 | { 120 | "name": "E.ROOT-SERVERS.NET.", 121 | "ttl": 3600000, 122 | "class": "IN", 123 | "type": "A", 124 | "data": { 125 | "address": "192.203.230.10" 126 | } 127 | }, 128 | { 129 | "name": "E.ROOT-SERVERS.NET.", 130 | "ttl": 3600000, 131 | "class": "IN", 132 | "type": "AAAA", 133 | "data": { 134 | "address": "2001:500:a8::e" 135 | } 136 | }, 137 | { 138 | "name": ".", 139 | "ttl": 3600000, 140 | "class": "IN", 141 | "type": "NS", 142 | "data": { 143 | "ns": "F.ROOT-SERVERS.NET." 144 | } 145 | }, 146 | { 147 | "name": "F.ROOT-SERVERS.NET.", 148 | "ttl": 3600000, 149 | "class": "IN", 150 | "type": "A", 151 | "data": { 152 | "address": "192.5.5.241" 153 | } 154 | }, 155 | { 156 | "name": "F.ROOT-SERVERS.NET.", 157 | "ttl": 3600000, 158 | "class": "IN", 159 | "type": "AAAA", 160 | "data": { 161 | "address": "2001:500:2f::f" 162 | } 163 | }, 164 | { 165 | "name": ".", 166 | "ttl": 3600000, 167 | "class": "IN", 168 | "type": "NS", 169 | "data": { 170 | "ns": "G.ROOT-SERVERS.NET." 171 | } 172 | }, 173 | { 174 | "name": "G.ROOT-SERVERS.NET.", 175 | "ttl": 3600000, 176 | "class": "IN", 177 | "type": "A", 178 | "data": { 179 | "address": "192.112.36.4" 180 | } 181 | }, 182 | { 183 | "name": "G.ROOT-SERVERS.NET.", 184 | "ttl": 3600000, 185 | "class": "IN", 186 | "type": "AAAA", 187 | "data": { 188 | "address": "2001:500:12::d0d" 189 | } 190 | }, 191 | { 192 | "name": ".", 193 | "ttl": 3600000, 194 | "class": "IN", 195 | "type": "NS", 196 | "data": { 197 | "ns": "H.ROOT-SERVERS.NET." 198 | } 199 | }, 200 | { 201 | "name": "H.ROOT-SERVERS.NET.", 202 | "ttl": 3600000, 203 | "class": "IN", 204 | "type": "A", 205 | "data": { 206 | "address": "198.97.190.53" 207 | } 208 | }, 209 | { 210 | "name": "H.ROOT-SERVERS.NET.", 211 | "ttl": 3600000, 212 | "class": "IN", 213 | "type": "AAAA", 214 | "data": { 215 | "address": "2001:500:1::53" 216 | } 217 | }, 218 | { 219 | "name": ".", 220 | "ttl": 3600000, 221 | "class": "IN", 222 | "type": "NS", 223 | "data": { 224 | "ns": "I.ROOT-SERVERS.NET." 225 | } 226 | }, 227 | { 228 | "name": "I.ROOT-SERVERS.NET.", 229 | "ttl": 3600000, 230 | "class": "IN", 231 | "type": "A", 232 | "data": { 233 | "address": "192.36.148.17" 234 | } 235 | }, 236 | { 237 | "name": "I.ROOT-SERVERS.NET.", 238 | "ttl": 3600000, 239 | "class": "IN", 240 | "type": "AAAA", 241 | "data": { 242 | "address": "2001:7fe::53" 243 | } 244 | }, 245 | { 246 | "name": ".", 247 | "ttl": 3600000, 248 | "class": "IN", 249 | "type": "NS", 250 | "data": { 251 | "ns": "J.ROOT-SERVERS.NET." 252 | } 253 | }, 254 | { 255 | "name": "J.ROOT-SERVERS.NET.", 256 | "ttl": 3600000, 257 | "class": "IN", 258 | "type": "A", 259 | "data": { 260 | "address": "192.58.128.30" 261 | } 262 | }, 263 | { 264 | "name": "J.ROOT-SERVERS.NET.", 265 | "ttl": 3600000, 266 | "class": "IN", 267 | "type": "AAAA", 268 | "data": { 269 | "address": "2001:503:c27::2:30" 270 | } 271 | }, 272 | { 273 | "name": ".", 274 | "ttl": 3600000, 275 | "class": "IN", 276 | "type": "NS", 277 | "data": { 278 | "ns": "K.ROOT-SERVERS.NET." 279 | } 280 | }, 281 | { 282 | "name": "K.ROOT-SERVERS.NET.", 283 | "ttl": 3600000, 284 | "class": "IN", 285 | "type": "A", 286 | "data": { 287 | "address": "193.0.14.129" 288 | } 289 | }, 290 | { 291 | "name": "K.ROOT-SERVERS.NET.", 292 | "ttl": 3600000, 293 | "class": "IN", 294 | "type": "AAAA", 295 | "data": { 296 | "address": "2001:7fd::1" 297 | } 298 | }, 299 | { 300 | "name": ".", 301 | "ttl": 3600000, 302 | "class": "IN", 303 | "type": "NS", 304 | "data": { 305 | "ns": "L.ROOT-SERVERS.NET." 306 | } 307 | }, 308 | { 309 | "name": "L.ROOT-SERVERS.NET.", 310 | "ttl": 3600000, 311 | "class": "IN", 312 | "type": "A", 313 | "data": { 314 | "address": "199.7.83.42" 315 | } 316 | }, 317 | { 318 | "name": "L.ROOT-SERVERS.NET.", 319 | "ttl": 3600000, 320 | "class": "IN", 321 | "type": "AAAA", 322 | "data": { 323 | "address": "2001:500:9f::42" 324 | } 325 | }, 326 | { 327 | "name": ".", 328 | "ttl": 3600000, 329 | "class": "IN", 330 | "type": "NS", 331 | "data": { 332 | "ns": "M.ROOT-SERVERS.NET." 333 | } 334 | }, 335 | { 336 | "name": "M.ROOT-SERVERS.NET.", 337 | "ttl": 3600000, 338 | "class": "IN", 339 | "type": "A", 340 | "data": { 341 | "address": "202.12.27.33" 342 | } 343 | }, 344 | { 345 | "name": "M.ROOT-SERVERS.NET.", 346 | "ttl": 3600000, 347 | "class": "IN", 348 | "type": "AAAA", 349 | "data": { 350 | "address": "2001:dc3::35" 351 | } 352 | }, 353 | { 354 | "name": ".", 355 | "ttl": 172800, 356 | "class": "IN", 357 | "type": "DS", 358 | "data": { 359 | "keyTag": 20326, 360 | "algorithm": 8, 361 | "digestType": 2, 362 | "digest": "e06d44b80b8f1d39a95c0b0d7c65d08458e880409bbc683457104237c7f8ec8d" 363 | } 364 | } 365 | ] 366 | -------------------------------------------------------------------------------- /hints/hints.zone: -------------------------------------------------------------------------------- 1 | ; 2 | ; Root Zone 3 | ; 4 | 5 | . 3600000 IN NS A.ROOT-SERVERS.NET. 6 | A.ROOT-SERVERS.NET. 3600000 IN A 198.41.0.4 7 | A.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:503:ba3e::2:30 8 | 9 | . 3600000 IN NS B.ROOT-SERVERS.NET. 10 | B.ROOT-SERVERS.NET. 3600000 IN A 199.9.14.201 11 | B.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:500:200::b 12 | 13 | . 3600000 IN NS C.ROOT-SERVERS.NET. 14 | C.ROOT-SERVERS.NET. 3600000 IN A 192.33.4.12 15 | C.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:500:2::c 16 | 17 | . 3600000 IN NS D.ROOT-SERVERS.NET. 18 | D.ROOT-SERVERS.NET. 3600000 IN A 199.7.91.13 19 | D.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:500:2d::d 20 | 21 | . 3600000 IN NS E.ROOT-SERVERS.NET. 22 | E.ROOT-SERVERS.NET. 3600000 IN A 192.203.230.10 23 | E.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:500:a8::e 24 | 25 | . 3600000 IN NS F.ROOT-SERVERS.NET. 26 | F.ROOT-SERVERS.NET. 3600000 IN A 192.5.5.241 27 | F.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:500:2f::f 28 | 29 | . 3600000 IN NS G.ROOT-SERVERS.NET. 30 | G.ROOT-SERVERS.NET. 3600000 IN A 192.112.36.4 31 | G.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:500:12::d0d 32 | 33 | . 3600000 IN NS H.ROOT-SERVERS.NET. 34 | H.ROOT-SERVERS.NET. 3600000 IN A 198.97.190.53 35 | H.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:500:1::53 36 | 37 | . 3600000 IN NS I.ROOT-SERVERS.NET. 38 | I.ROOT-SERVERS.NET. 3600000 IN A 192.36.148.17 39 | I.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:7fe::53 40 | 41 | . 3600000 IN NS J.ROOT-SERVERS.NET. 42 | J.ROOT-SERVERS.NET. 3600000 IN A 192.58.128.30 43 | J.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:503:c27::2:30 44 | 45 | . 3600000 IN NS K.ROOT-SERVERS.NET. 46 | K.ROOT-SERVERS.NET. 3600000 IN A 193.0.14.129 47 | K.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:7fd::1 48 | 49 | . 3600000 IN NS L.ROOT-SERVERS.NET. 50 | L.ROOT-SERVERS.NET. 3600000 IN A 199.7.83.42 51 | L.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:500:9f::42 52 | 53 | . 3600000 IN NS M.ROOT-SERVERS.NET. 54 | M.ROOT-SERVERS.NET. 3600000 IN A 202.12.27.33 55 | M.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:dc3::35 56 | 57 | ; 58 | ; Trust Anchors 59 | ; 60 | 61 | . 172800 IN DS 20326 8 2 E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D 62 | -------------------------------------------------------------------------------- /lib/authority.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * authority.js - authority object for bns 3 | * Copyright (c) 2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/chjj/bns 5 | * 6 | * Parts of this software are based on solvere: 7 | * https://github.com/rolandshoemaker/solvere 8 | */ 9 | 10 | 'use strict'; 11 | 12 | const assert = require('bsert'); 13 | 14 | /** 15 | * Authority 16 | */ 17 | 18 | class Authority { 19 | constructor(zone, name) { 20 | assert(zone == null || typeof zone === 'string'); 21 | assert(name == null || typeof name === 'string'); 22 | 23 | this.zone = zone || '.'; 24 | this.name = name || '.'; 25 | this.servers = []; 26 | } 27 | 28 | add(host, port) { 29 | assert(typeof host === 'string'); 30 | assert((port & 0xffff) === port); 31 | this.servers.push({ host, port }); 32 | return this; 33 | } 34 | 35 | inject(auth) { 36 | assert(auth instanceof this.constructor); 37 | this.zone = auth.zone; 38 | this.name = auth.name; 39 | this.servers = auth.servers.slice(); 40 | return this; 41 | } 42 | 43 | clone() { 44 | const copy = new this.constructor(); 45 | return copy.inject(this); 46 | } 47 | } 48 | 49 | /* 50 | * Expose 51 | */ 52 | 53 | module.exports = Authority; 54 | -------------------------------------------------------------------------------- /lib/bns.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * bns.js - dns module 3 | * Copyright (c) 2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/chjj/bns 5 | */ 6 | 7 | 'use strict'; 8 | 9 | exports.API = require('./api'); 10 | exports.Authority = require('./authority'); 11 | exports.AuthServer = require('./server/auth'); 12 | exports.Cache = require('./cache'); 13 | exports.constants = require('./constants'); 14 | exports.dane = require('./dane'); 15 | exports.dns = require('./dns'); 16 | exports.DNSResolver = require('./resolver/dns'); 17 | exports.DNSServer = require('./server/dns'); 18 | exports.dnssec = require('./dnssec'); 19 | exports.encoding = require('./encoding'); 20 | exports.DNSError = require('./error'); 21 | exports.Hints = require('./hints'); 22 | exports.Hosts = require('./hosts'); 23 | exports.hsig = require('./hsig'); 24 | exports.nsec3 = require('./nsec3'); 25 | exports.openpgpkey = require('./openpgpkey'); 26 | exports.Ownership = require('./ownership'); 27 | exports.punycode = require('./punycode'); 28 | exports.rdns = require('./rdns'); 29 | exports.RecursiveResolver = require('./resolver/recursive'); 30 | exports.RecursiveServer = require('./server/recursive'); 31 | exports.ResolvConf = require('./resolvconf'); 32 | exports.ROOT_HINTS = require('./roothints'); 33 | exports.RootResolver = require('./resolver/root'); 34 | exports.sig0 = require('./sig0'); 35 | exports.smimea = require('./smimea'); 36 | exports.srv = require('./srv'); 37 | exports.sshfp = require('./sshfp'); 38 | exports.StubResolver = require('./resolver/stub'); 39 | exports.StubServer = require('./server/stub'); 40 | exports.tlsa = require('./tlsa'); 41 | exports.tsig = require('./tsig'); 42 | exports.udns = require('./udns'); 43 | exports.UnboundResolver = require('./resolver/unbound'); 44 | exports.UnboundServer = require('./server/unbound'); 45 | exports.util = require('./util'); 46 | exports.wire = require('./wire'); 47 | exports.Zone = require('./zone'); 48 | 49 | exports.version = '0.15.0'; 50 | exports.unbound = exports.UnboundResolver.version; 51 | exports.native = exports.UnboundResolver.native; 52 | -------------------------------------------------------------------------------- /lib/cache.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * cache.js - resolver cache for bns 3 | * Copyright (c) 2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/chjj/bns 5 | * 6 | * Parts of this software are based on solvere: 7 | * https://github.com/rolandshoemaker/solvere 8 | */ 9 | 10 | 'use strict'; 11 | 12 | const assert = require('bsert'); 13 | const Heap = require('bheep'); 14 | const bio = require('bufio'); 15 | const wire = require('./wire'); 16 | const util = require('./util'); 17 | const {Message, Question} = wire; 18 | 19 | /** 20 | * Cache 21 | */ 22 | 23 | class Cache { 24 | constructor() { 25 | this.map = new Map(); 26 | this.queue = new Heap((a, b) => a[1] - b[1]); 27 | this.size = 0; 28 | this.maxSize = 5 << 20; 29 | } 30 | 31 | get(id) { 32 | return this.map.get(id) || null; 33 | } 34 | 35 | has(id) { 36 | return this.map.has(id); 37 | } 38 | 39 | set(id, entry) { 40 | this.map.set(id, entry); 41 | return this; 42 | } 43 | 44 | remove(id) { 45 | this.map.delete(id); 46 | return this; 47 | } 48 | 49 | hash(qs, zone) { 50 | const n = qs.name.toLowerCase(); 51 | const t = qs.type.toString(10); 52 | const z = zone.toLowerCase(); 53 | return `${n};${t};${z}`; 54 | } 55 | 56 | prune() { 57 | while (this.size > this.maxSize) { 58 | const [id, deadline] = this.queue.shift(); 59 | const entry = this.get(id); 60 | 61 | if (entry && entry.deadline() === deadline) { 62 | this.size -= entry.usage(id); 63 | this.remove(id); 64 | } 65 | 66 | this.size -= queueUsage(id); 67 | } 68 | 69 | return this; 70 | } 71 | 72 | insert(qs, zone, msg, ad, eternal = false) { 73 | assert(qs instanceof Question); 74 | assert(typeof zone === 'string'); 75 | assert(msg instanceof Message); 76 | assert(typeof ad === 'boolean'); 77 | assert(typeof eternal === 'boolean'); 78 | 79 | const id = this.hash(qs, zone); 80 | const ttl = msg.minTTL(); 81 | 82 | if (ttl === 0) 83 | return this; 84 | 85 | const item = this.get(id); 86 | 87 | if (item) { 88 | if (item.eternal) 89 | return this; 90 | 91 | const raw = msg.encode(); 92 | 93 | this.size -= item.usage(id); 94 | 95 | item.msg = raw; 96 | item.setAD(ad); 97 | item.time = util.now(); 98 | item.ttl = ttl; 99 | 100 | this.size += item.usage(id); 101 | 102 | this.size += queueUsage(id); 103 | this.queue.insert([id, item.deadline()]); 104 | this.prune(); 105 | 106 | return this; 107 | } 108 | 109 | const raw = msg.encode(); 110 | const entry = new CacheEntry(raw); 111 | 112 | entry.setAD(ad); 113 | entry.time = util.now(); 114 | entry.ttl = ttl; 115 | entry.eternal = eternal; 116 | 117 | this.set(id, entry); 118 | this.size += entry.usage(id); 119 | 120 | if (!eternal) { 121 | this.size += queueUsage(id); 122 | this.queue.insert([id, entry.deadline()]); 123 | this.prune(); 124 | } 125 | 126 | return this; 127 | } 128 | 129 | hit(qs, zone) { 130 | assert(qs instanceof Question); 131 | assert(typeof zone === 'string'); 132 | 133 | const id = this.hash(qs, zone); 134 | const entry = this.get(id); 135 | 136 | if (!entry) 137 | return null; 138 | 139 | const now = util.now(); 140 | 141 | if (entry.expired(now)) { 142 | this.size -= entry.usage(id); 143 | this.remove(id); 144 | return null; 145 | } 146 | 147 | const msg = Message.decode(entry.msg); 148 | const diff = now - entry.time; 149 | 150 | assert(diff >= 0); 151 | 152 | for (const rr of msg.records()) { 153 | if (rr.isOPT()) 154 | continue; 155 | 156 | if (rr.ttl === 0) 157 | continue; 158 | 159 | if (rr.ttl <= diff) { 160 | rr.ttl = 1; 161 | continue; 162 | } 163 | 164 | rr.ttl -= diff; 165 | } 166 | 167 | return msg; 168 | } 169 | } 170 | 171 | /** 172 | * CacheEntry 173 | */ 174 | 175 | class CacheEntry { 176 | constructor(msg) { 177 | assert(Buffer.isBuffer(msg)); 178 | this.msg = msg; 179 | this.time = 0; 180 | this.ttl = 0; 181 | this.eternal = false; 182 | } 183 | 184 | deadline() { 185 | if (this.eternal) 186 | return 0xffffffff; 187 | 188 | return this.time + this.ttl; 189 | } 190 | 191 | usage(id) { 192 | let size = 0; 193 | size += id.length * 2; 194 | size += 80 + this.msg.length; 195 | size += 8 * 3; 196 | return size; 197 | } 198 | 199 | setAD(ad) { 200 | let bits = bio.readU16BE(this.msg, 2); 201 | 202 | if (ad) 203 | bits |= wire.flags.AD; 204 | else 205 | bits &= ~wire.flags.AD; 206 | 207 | bio.writeU16BE(this.msg, bits, 2); 208 | } 209 | 210 | expired(now) { 211 | // Someone changed 212 | // their system time. 213 | // Clear cache. 214 | if (now < this.time) 215 | return true; 216 | 217 | if (this.eternal) 218 | return false; 219 | 220 | return now >= this.deadline(); 221 | } 222 | } 223 | 224 | /* 225 | * Helpers 226 | */ 227 | 228 | function queueUsage(id) { 229 | return id.length * 2 + 20; 230 | } 231 | 232 | /* 233 | * Expose 234 | */ 235 | 236 | module.exports = Cache; 237 | -------------------------------------------------------------------------------- /lib/dane.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * dane.js - DANE for bns 3 | * Copyright (c) 2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/chjj/bns 5 | * 6 | * Parts of this software are based on miekg/dns: 7 | * https://github.com/miekg/dns/blob/master/dane.go 8 | * 9 | * Resources: 10 | * https://tools.ietf.org/html/rfc6698 11 | */ 12 | 13 | 'use strict'; 14 | 15 | const assert = require('bsert'); 16 | const constants = require('./constants'); 17 | const crypto = require('./internal/crypto'); 18 | const util = require('./util'); 19 | 20 | const { 21 | usages, 22 | usagesByVal, 23 | selectors, 24 | selectorsByVal, 25 | matchingTypes, 26 | matchingTypesByVal 27 | } = constants; 28 | 29 | /* 30 | * DANE 31 | */ 32 | 33 | const dane = exports; 34 | 35 | dane.select = function select(cert, selector) { 36 | assert(Buffer.isBuffer(cert)); 37 | assert((selector & 0xff) === selector); 38 | 39 | switch (selector) { 40 | case selectors.FULL: 41 | return getCert(cert); 42 | case selectors.SPKI: 43 | return getPubkeyInfo(cert); 44 | } 45 | 46 | return null; 47 | }; 48 | 49 | dane.hash = function hash(data, matchingType) { 50 | assert(Buffer.isBuffer(data)); 51 | assert((matchingType & 0xff) === matchingType); 52 | 53 | switch (matchingType) { 54 | case matchingTypes.NONE: 55 | return data; 56 | case matchingTypes.SHA256: 57 | return crypto.sha256.digest(data); 58 | case matchingTypes.SHA512: 59 | return crypto.sha512.digest(data); 60 | } 61 | 62 | return null; 63 | }; 64 | 65 | dane.sign = function sign(cert, selector, matchingType) { 66 | const data = dane.select(cert, selector); 67 | 68 | if (!data) 69 | return null; 70 | 71 | const hash = dane.hash(data, matchingType); 72 | 73 | if (!hash) 74 | return null; 75 | 76 | return hash; 77 | }; 78 | 79 | dane.verify = function verify(cert, selector, matchingType, certificate) { 80 | const hash = dane.sign(cert, selector, matchingType); 81 | 82 | if (!hash) 83 | return false; 84 | 85 | return hash.equals(certificate); 86 | }; 87 | 88 | dane.encodeEmail = function encodeEmail(email, tag, bits) { 89 | assert(typeof email === 'string'); 90 | assert(email.length >= 3 && email.length <= 320); 91 | 92 | const index = email.indexOf('@'); 93 | assert(index !== -1); 94 | 95 | const local = email.substring(0, index); 96 | const name = email.substring(index + 1); 97 | 98 | return dane.encodeName(name, tag, local, bits); 99 | }; 100 | 101 | dane.hashLocal = function hashLocal(local, bits, enc) { 102 | if (bits == null) 103 | bits = 256; 104 | 105 | if (enc == null) 106 | enc = null; 107 | 108 | assert(typeof local === 'string'); 109 | assert(local.length <= 64); 110 | assert(local.indexOf('@') === -1); 111 | assert(typeof bits === 'number'); 112 | assert(bits === 224 || bits === 256); 113 | assert(enc === null || enc === 'hex'); 114 | 115 | const raw = Buffer.from(local, 'utf8'); 116 | const hash = bits === 224 117 | ? crypto.sha224.digest(raw) 118 | : crypto.sha256.digest(raw); 119 | 120 | if (enc === 'hex') 121 | return hash.toString('hex', 0, 28); 122 | 123 | return hash.slice(0, 28); 124 | }; 125 | 126 | dane.encodeName = function encodeName(name, tag, local, bits) { 127 | assert(util.isName(name)); 128 | assert(name.length === 0 || name[0] !== '_'); 129 | assert(util.isName(tag)); 130 | assert(tag.length >= 1 && tag.length <= 62); 131 | assert(tag[0] !== '_'); 132 | assert(tag.indexOf('.') === -1); 133 | 134 | if (name === '.') 135 | name = ''; 136 | 137 | const hash = dane.hashLocal(local, bits, 'hex'); 138 | const encoded = util.fqdn(`${hash}._${tag}.${name}`); 139 | 140 | assert(util.isName(encoded)); 141 | 142 | return encoded; 143 | }; 144 | 145 | dane.decodeName = function decodeName(name, tag) { 146 | assert(util.isName(name)); 147 | assert(util.isName(tag)); 148 | assert(tag.length >= 1 && tag.length <= 62); 149 | assert(tag[0] !== '_'); 150 | assert(tag.indexOf('.') === -1); 151 | 152 | const labels = util.split(name); 153 | 154 | assert(labels.length >= 3); 155 | 156 | const hex = util.label(name, labels, 0); 157 | const part = util.label(name, labels, 1); 158 | 159 | assert(hex.length >= 1); 160 | assert(part.length >= 2); 161 | assert(part[0] === '_'); 162 | 163 | if (part.toLowerCase() !== `_${tag}`) 164 | throw new Error('Invalid DANE name.'); 165 | 166 | if (hex.length !== 56) 167 | throw new Error('Invalid DANE hash.'); 168 | 169 | const hash = Buffer.from(hex, 'hex'); 170 | 171 | if (hash.length !== 28) 172 | throw new Error('Invalid DANE hash.'); 173 | 174 | return { 175 | name: util.fqdn(util.from(name, labels, 2)), 176 | hash: hash 177 | }; 178 | }; 179 | 180 | dane.isName = function isName(name, tag) { 181 | assert(util.isName(name)); 182 | assert(util.isName(tag)); 183 | assert(tag.length >= 1 && tag.length <= 62); 184 | assert(tag[0] !== '_'); 185 | assert(tag.indexOf('.') === -1); 186 | 187 | try { 188 | dane.decodeName(name, tag); 189 | return true; 190 | } catch (e) { 191 | return false; 192 | } 193 | }; 194 | 195 | /* 196 | * Helpers 197 | */ 198 | 199 | function getCert(data) { 200 | const size = gauge(data, 0); 201 | assert(size <= data.length); 202 | return data.slice(0, size); 203 | } 204 | 205 | function getPubkeyInfo(data) { 206 | let off = 0; 207 | 208 | // cert 209 | off = seq(data, off); 210 | 211 | // tbs 212 | off = seq(data, off); 213 | 214 | // version 215 | off = xint(data, off); 216 | 217 | // serial 218 | off = int(data, off); 219 | 220 | // alg ident 221 | off = skip(data, off); 222 | 223 | // issuer 224 | off = skip(data, off); 225 | 226 | // validity 227 | off = skip(data, off); 228 | 229 | // subject 230 | off = skip(data, off); 231 | 232 | // pubkeyinfo 233 | const size = gauge(data, off); 234 | 235 | assert(off + size <= data.length); 236 | 237 | return data.slice(off, off + size); 238 | } 239 | 240 | function tag(data, off, expect, explicit) { 241 | assert(off < data.length); 242 | 243 | const start = off; 244 | 245 | let type = data[off++]; 246 | 247 | const primitive = (type & 0x20) === 0; 248 | 249 | if ((type & 0x1f) === 0x1f) { 250 | let oct = type; 251 | type = 0; 252 | while ((oct & 0x80) === 0x80) { 253 | assert(off < data.length); 254 | oct = data[off++]; 255 | type <<= 7; 256 | type |= oct & 0x7f; 257 | } 258 | } else { 259 | type &= 0x1f; 260 | } 261 | 262 | if (type !== expect) { 263 | if (explicit) 264 | return [start, 0]; 265 | throw new Error(`Expected type: ${expect}. Got: ${type}.`); 266 | } 267 | 268 | assert(off < data.length); 269 | 270 | let size = data[off++]; 271 | 272 | if (!primitive && size === 0x80) 273 | throw new Error('Indefinite size.'); 274 | 275 | if ((size & 0x80) === 0) 276 | return [off, size]; 277 | 278 | const bytes = size & 0x7f; 279 | 280 | if (bytes > 3) 281 | throw new Error('Length octet is too long.'); 282 | 283 | size = 0; 284 | 285 | for (let i = 0; i < bytes; i++) { 286 | assert(off < data.length); 287 | size <<= 8; 288 | size |= data[off++]; 289 | } 290 | 291 | // Return: 292 | // [0]: Offset after the header. 293 | // [1]: Size of bytes to read next. 294 | return [off, size]; 295 | } 296 | 297 | function read(data, off) { 298 | // Read seq-header, update offset to after header. 299 | return tag(data, off, 0x10, false); 300 | } 301 | 302 | function gauge(data, off) { 303 | // Get total size of seq-header + data. 304 | const [pos, size] = read(data, off); 305 | return (pos - off) + size; 306 | } 307 | 308 | function seq(data, off) { 309 | // Read seq-header, return offset after header. 310 | return read(data, off)[0]; 311 | } 312 | 313 | function skip(data, off) { 314 | // Read seq-header, return offset after header+data. 315 | const [offset, size] = read(data, off); 316 | return offset + size; 317 | } 318 | 319 | function int(data, off) { 320 | // Read int-header, return offset after header+data. 321 | const [offset, size] = tag(data, off, 0x02, false); 322 | return offset + size; 323 | } 324 | 325 | function xint(data, off) { 326 | // Read int-header (explicit), return offset after header+data. 327 | const [offset, size] = tag(data, off, 0x00, true); 328 | return offset + size; 329 | } 330 | 331 | /* 332 | * Expose 333 | */ 334 | 335 | dane.usages = usages; 336 | dane.usagesByVal = usagesByVal; 337 | dane.selectors = selectors; 338 | dane.selectorsByVal = selectorsByVal; 339 | dane.matchingTypes = matchingTypes; 340 | dane.matchingTypesByVal = matchingTypesByVal; 341 | -------------------------------------------------------------------------------- /lib/dns.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * dns.js - replacement dns node.js module 3 | * Copyright (c) 2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/chjj/bns 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const API = require('./api'); 10 | const Hosts = require('./hosts'); 11 | const ResolvConf = require('./resolvconf'); 12 | const StubResolver = require('./resolver/stub'); 13 | 14 | let conf = null; 15 | let hosts = null; 16 | 17 | function createResolver(options, servers) { 18 | if (!conf) 19 | conf = ResolvConf.fromSystem(); 20 | 21 | if (!hosts) 22 | hosts = Hosts.fromSystem(); 23 | 24 | const resolver = new StubResolver(options); 25 | 26 | if (!options.conf) 27 | resolver.conf = conf.clone(); 28 | 29 | if (!options.hosts) 30 | resolver.hosts = hosts.clone(); 31 | 32 | if (servers) 33 | resolver.setServers(servers); 34 | 35 | return resolver; 36 | } 37 | 38 | module.exports = API.make(createResolver, { 39 | tcp: true, 40 | edns: false, 41 | dnssec: false 42 | }); 43 | -------------------------------------------------------------------------------- /lib/error.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * error.js - dns error for bns 3 | * Copyright (c) 2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/chjj/bns 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const constants = require('./constants'); 10 | 11 | const { 12 | codes, 13 | codeToString 14 | } = constants; 15 | 16 | /** 17 | * DNS Error 18 | * @extends {Error} 19 | */ 20 | 21 | class DNSError extends Error { 22 | constructor(msg, code) { 23 | super(); 24 | 25 | if (typeof msg === 'number') { 26 | code = msg; 27 | msg = ''; 28 | } 29 | 30 | if (code == null) 31 | code = codes.SERVFAIL; 32 | 33 | if (msg) 34 | msg = `: ${msg}.`; 35 | else 36 | msg = ''; 37 | 38 | this.type = 'DNSError'; 39 | this.name = 'DNSError'; 40 | this.code = `E${codeToString(code)}`; 41 | this.errno = code; 42 | this.message = `${this.code}${msg}`; 43 | 44 | if (Error.captureStackTrace) 45 | Error.captureStackTrace(this, DNSError); 46 | } 47 | } 48 | 49 | /* 50 | * Expose 51 | */ 52 | 53 | module.exports = DNSError; 54 | -------------------------------------------------------------------------------- /lib/hsig.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * hsig.js - HSIG for bns 3 | * Copyright (c) 2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/chjj/bns 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const assert = require('bsert'); 10 | const secp256k1 = require('bcrypto/lib/secp256k1'); 11 | const blake2b = require('bcrypto/lib/blake2b'); 12 | const sig0 = require('./sig0'); 13 | 14 | /* 15 | * Constants 16 | */ 17 | 18 | const SIG0_ALGO_NAME = 'blake2bsecp256k1.'; 19 | 20 | const FUDGE_WINDOW = 21600; // 6 hours 21 | 22 | /* 23 | * HSIG 24 | */ 25 | 26 | const hsig = exports; 27 | 28 | hsig.createPrivate = function createPrivate() { 29 | return secp256k1.privateKeyGenerate(); 30 | }; 31 | 32 | hsig.createPrivateAsync = hsig.createPrivate; 33 | 34 | hsig.createPublic = function createPublic(priv) { 35 | return secp256k1.publicKeyCreate(priv); 36 | }; 37 | 38 | hsig.makeKey = function makeKey(priv) { 39 | const pub = secp256k1.publicKeyCreate(priv); 40 | return hsig.createKey(pub); 41 | }; 42 | 43 | hsig.createKey = function createKey(pub) { 44 | return sig0.createKey(sig0.algs.PRIVATEDNS, pub, SIG0_ALGO_NAME); 45 | }; 46 | 47 | hsig.sign = function sign(msg, priv) { 48 | assert(Buffer.isBuffer(msg)); 49 | assert(Buffer.isBuffer(priv) && priv.length === 32); 50 | 51 | const pub = secp256k1.publicKeyCreate(priv, true); 52 | const key = hsig.createKey(pub); 53 | const fudge = FUDGE_WINDOW; 54 | 55 | return sig0.sign(msg, key, priv, fudge, (priv, data) => { 56 | const msg = blake2b.digest(data); 57 | return secp256k1.sign(msg, priv); 58 | }); 59 | }; 60 | 61 | hsig.verify = function verify(msg, pub) { 62 | assert(Buffer.isBuffer(msg)); 63 | assert(Buffer.isBuffer(pub) && pub.length === 33); 64 | 65 | const key = hsig.createKey(pub); 66 | 67 | return sig0.verify(msg, key, (sig, key, data) => { 68 | const msg = blake2b.digest(data); 69 | const sigbuf = sig.data.signature; 70 | const keybuf = key.data.publicKey; 71 | 72 | const publicKey = keybuf.slice(SIG0_ALGO_NAME.length + 1); 73 | 74 | return secp256k1.verify(msg, sigbuf, publicKey); 75 | }); 76 | }; 77 | 78 | hsig.SIG0_ALGO_NAME = SIG0_ALGO_NAME; 79 | -------------------------------------------------------------------------------- /lib/internal/lazy-browser.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * lazy.js - lazy require for bns 3 | * Copyright (c) 2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/chjj/bns 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const assert = require('bsert'); 10 | const iana = require('./iana'); 11 | const scan = require('./scan'); 12 | const schema = require('./schema'); 13 | 14 | /* 15 | * Lazy Require 16 | */ 17 | 18 | function lazy(name) { 19 | assert(typeof name === 'string'); 20 | 21 | switch (name) { 22 | case './iana': 23 | case './internal/iana': 24 | return iana; 25 | case './scan': 26 | case './internal/scan': 27 | return scan; 28 | case './schema': 29 | case './internal/schema': 30 | return schema; 31 | } 32 | 33 | throw new Error(`Unknown module: ${name}.`); 34 | } 35 | 36 | /* 37 | * Expose 38 | */ 39 | 40 | module.exports = lazy; 41 | -------------------------------------------------------------------------------- /lib/internal/lazy.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * lazy.js - lazy require for bns 3 | * Copyright (c) 2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/chjj/bns 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const assert = require('bsert'); 10 | 11 | let iana = null; 12 | let scan = null; 13 | let schema = null; 14 | 15 | /* 16 | * Lazy Require 17 | */ 18 | 19 | function lazy(name) { 20 | assert(typeof name === 'string'); 21 | 22 | switch (name) { 23 | case './iana': 24 | case './internal/iana': { 25 | if (!iana) 26 | iana = require('./iana'); 27 | return iana; 28 | } 29 | case './scan': 30 | case './internal/scan': { 31 | if (!scan) 32 | scan = require('./scan'); 33 | return scan; 34 | } 35 | case './schema': 36 | case './internal/schema': { 37 | if (!schema) 38 | schema = require('./schema'); 39 | return schema; 40 | } 41 | } 42 | 43 | throw new Error(`Unknown module: ${name}.`); 44 | } 45 | 46 | /* 47 | * Expose 48 | */ 49 | 50 | module.exports = lazy; 51 | -------------------------------------------------------------------------------- /lib/nsec3.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * nsec3.js - NSEC3 for bns 3 | * Copyright (c) 2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/chjj/bns 5 | * 6 | * Parts of this software are based on miekg/dns and golang/go: 7 | * https://github.com/miekg/dns/blob/master/nsecx.go 8 | * 9 | * Parts of this software are based on solvere: 10 | * https://github.com/rolandshoemaker/solvere 11 | */ 12 | 13 | 'use strict'; 14 | 15 | const assert = require('bsert'); 16 | const base32 = require('bs32'); 17 | const constants = require('./constants'); 18 | const crypto = require('./internal/crypto'); 19 | const encoding = require('./encoding'); 20 | const wire = require('./wire'); 21 | const util = require('./util'); 22 | 23 | const { 24 | types, 25 | nsecHashes, 26 | nsecHashesByVal 27 | } = constants; 28 | 29 | const { 30 | hasType, 31 | packName 32 | } = encoding; 33 | 34 | const { 35 | Question, 36 | Record 37 | } = wire; 38 | 39 | /* 40 | * Constants 41 | */ 42 | 43 | const MAX_ITERATIONS = 512; 44 | 45 | /* 46 | * NSEC3 47 | */ 48 | 49 | const nsec3 = exports; 50 | 51 | nsec3.hashName = function hashName(name, ha, iter, salt) { 52 | assert(typeof name === 'string'); 53 | assert((ha & 0xff) === ha); 54 | assert((iter & 0xffff) === iter); 55 | assert(Buffer.isBuffer(salt)); 56 | 57 | // DoS vector. 58 | if (iter > MAX_ITERATIONS) 59 | return null; 60 | 61 | const nameRaw = packName(name.toLowerCase()); 62 | const saltRaw = salt; 63 | 64 | let hash = null; 65 | 66 | switch (ha) { 67 | case nsecHashes.SHA1: 68 | hash = crypto.sha1; 69 | break; 70 | } 71 | 72 | if (!hash) 73 | return null; 74 | 75 | let nameHash = hash.multi(nameRaw, saltRaw); 76 | 77 | for (let i = 0; i < iter; i++) 78 | nameHash = hash.multi(nameHash, saltRaw); 79 | 80 | return nameHash; 81 | }; 82 | 83 | nsec3.cover = function cover(rr, name) { 84 | assert(rr instanceof Record); 85 | assert(rr.type === types.NSEC3); 86 | 87 | const rd = rr.data; 88 | const nameHash = nsec3.hashName(name, rd.hash, rd.iterations, rd.salt); 89 | 90 | if (!nameHash) 91 | return false; 92 | 93 | const owner = rr.name; 94 | const label = util.split(owner); 95 | 96 | if (label.length < 2) 97 | return false; 98 | 99 | const owner32 = owner.substring(0, label[1] - 1); 100 | const ownerZone = owner.substring(label[1]); 101 | const ownerHash = decodeHex(owner32); 102 | 103 | if (!ownerHash) 104 | return false; 105 | 106 | if (ownerHash.length !== nameHash.length) 107 | return false; 108 | 109 | if (!util.isSubdomain(ownerZone, name)) 110 | return false; 111 | 112 | const nextHash = rd.nextDomain; 113 | 114 | if (nextHash.length !== nameHash.length) 115 | return false; 116 | 117 | if (ownerHash.equals(nextHash)) 118 | return false; 119 | 120 | if (ownerHash.compare(nextHash) > 0) { 121 | if (nameHash.compare(ownerHash) > 0) 122 | return true; 123 | return nameHash.compare(nextHash) < 0; 124 | } 125 | 126 | if (nameHash.compare(ownerHash) < 0) 127 | return false; 128 | 129 | return nameHash.compare(nextHash) < 0; 130 | }; 131 | 132 | nsec3.match = function match(rr, name) { 133 | assert(rr instanceof Record); 134 | assert(rr.type === types.NSEC3); 135 | 136 | const rd = rr.data; 137 | const nameHash = nsec3.hashName(name, rd.hash, rd.iterations, rd.salt); 138 | 139 | if (!nameHash) 140 | return false; 141 | 142 | const owner = rr.name; 143 | const label = util.split(owner); 144 | 145 | if (label.length < 2) 146 | return false; 147 | 148 | const owner32 = owner.substring(0, label[1] - 1); 149 | const ownerZone = owner.substring(label[1]); 150 | const ownerHash = decodeHex(owner32); 151 | 152 | if (!ownerHash) 153 | return false; 154 | 155 | if (ownerHash.length !== nameHash.length) 156 | return false; 157 | 158 | if (!util.isSubdomain(ownerZone, name)) 159 | return false; 160 | 161 | if (ownerHash.equals(nameHash)) 162 | return true; 163 | 164 | return false; 165 | }; 166 | 167 | nsec3.findClosestEncloser = function findClosestEncloser(name, nsec) { 168 | assert(typeof name === 'string'); 169 | assert(Array.isArray(nsec)); 170 | 171 | const label = util.split(name); 172 | 173 | let nc = name; 174 | 175 | for (let i = 0; i < label.length; i++) { 176 | const z = name.substring(label[i]); 177 | const bm = nsec3.findMatching(z, nsec); 178 | 179 | if (!bm) 180 | continue; 181 | 182 | if (i !== 0) 183 | nc = name.substring(label[i - 1]); 184 | 185 | return [z, nc]; 186 | } 187 | 188 | return ['', '']; 189 | }; 190 | 191 | nsec3.findMatching = function findMatching(name, nsec) { 192 | assert(typeof name === 'string'); 193 | assert(Array.isArray(nsec)); 194 | 195 | for (const rr of nsec) { 196 | if (nsec3.match(rr, name)) 197 | return rr.data.typeBitmap; 198 | } 199 | 200 | return null; // NSEC missing coverage 201 | }; 202 | 203 | nsec3.findCoverer = function findCoverer(name, nsec) { 204 | assert(typeof name === 'string'); 205 | assert(Array.isArray(nsec)); 206 | 207 | for (const rr of nsec) { 208 | if (nsec3.cover(rr, name)) { 209 | const rd = rr.data; 210 | return [rd.typeBitmap, (rd.flags & 1) === 1]; 211 | } 212 | } 213 | 214 | return [null, false]; // NSEC missing coverage 215 | }; 216 | 217 | nsec3.verifyNameError = function verifyNameError(qs, nsec) { 218 | const [ce, nc] = nsec3.findClosestEncloser(qs.name, nsec); 219 | 220 | if (ce === '') 221 | return false; // NSEC missing coverage 222 | 223 | const [cv] = nsec3.findCoverer(nc, nsec); 224 | 225 | if (!cv) 226 | return false; // NSEC missing coverage 227 | 228 | return true; 229 | }; 230 | 231 | nsec3.verifyNoData = function verifyNoData(qs, nsec) { 232 | assert(qs instanceof Question); 233 | assert(Array.isArray(nsec)); 234 | 235 | const bm = nsec3.findMatching(qs.name, nsec); 236 | 237 | if (!bm) { 238 | if (qs.type !== types.DS) 239 | return false; // NSEC missing coverage 240 | 241 | const [ce, nc] = nsec3.findClosestEncloser(qs.name, nsec); 242 | 243 | if (ce === '') 244 | return false; // NSEC missing coverage 245 | 246 | const [b, optOut] = nsec3.findCoverer(nc, nsec); 247 | 248 | if (!b) 249 | return false; // NSEC missing coverage 250 | 251 | if (!optOut) 252 | return false; // NSEC opt out 253 | 254 | return true; 255 | } 256 | 257 | if (hasType(bm, qs.type)) 258 | return false; // NSEC type exists 259 | 260 | if (hasType(bm, types.CNAME)) 261 | return false; // NSEC type exists 262 | 263 | return true; 264 | }; 265 | 266 | nsec3.verifyDelegation = function verifyDelegation(delegation, nsec) { 267 | const bm = nsec3.findMatching(delegation, nsec); 268 | 269 | if (!bm) { 270 | const [ce, nc] = nsec3.findClosestEncloser(delegation, nsec); 271 | 272 | if (ce === '') 273 | return false; // NSEC missing coverage 274 | 275 | const [b, optOut] = nsec3.findCoverer(nc, nsec); 276 | 277 | if (!b) 278 | return false; // NSEC missing coverage 279 | 280 | if (!optOut) 281 | return false; // NSEC opt out 282 | 283 | return true; 284 | } 285 | 286 | if (!hasType(bm, types.NS)) 287 | return false; // NSEC NS missing 288 | 289 | if (hasType(bm, types.DS)) 290 | return false; // NSEC bad delegation 291 | 292 | if (hasType(bm, types.SOA)) 293 | return false; // NSEC bad delegation 294 | 295 | return true; 296 | }; 297 | 298 | /* 299 | * Helpers 300 | */ 301 | 302 | function decodeHex(str) { 303 | try { 304 | return base32.decodeHex(str); 305 | } catch (e) { 306 | return null; 307 | } 308 | } 309 | 310 | /* 311 | * Expose 312 | */ 313 | 314 | nsec3.hashes = nsecHashes; 315 | nsec3.hashesByVal = nsecHashesByVal; 316 | -------------------------------------------------------------------------------- /lib/openpgpkey.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * openpgpkey.js - OPENPGPKEY for bns 3 | * Copyright (c) 2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/chjj/bns 5 | * 6 | * Resources: 7 | * https://tools.ietf.org/html/rfc7929 8 | */ 9 | 10 | 'use strict'; 11 | 12 | const assert = require('bsert'); 13 | const dane = require('./dane'); 14 | const wire = require('./wire'); 15 | 16 | const { 17 | types, 18 | classes, 19 | Record, 20 | OPENPGPKEYRecord 21 | } = wire; 22 | 23 | /* 24 | * OPENPGPKEY 25 | */ 26 | 27 | const openpgpkey = exports; 28 | 29 | openpgpkey.create = function create(key, email, options = {}) { 30 | assert(Buffer.isBuffer(key)); 31 | assert(options && typeof options === 'object'); 32 | 33 | let {ttl} = options; 34 | 35 | if (ttl == null) 36 | ttl = 3600; 37 | 38 | assert((ttl >>> 0) === ttl); 39 | 40 | const rr = new Record(); 41 | const rd = new OPENPGPKEYRecord(); 42 | 43 | rr.name = openpgpkey.encodeEmail(email); 44 | rr.type = types.OPENPGPKEY; 45 | rr.class = classes.IN; 46 | rr.ttl = ttl; 47 | rr.data = rd; 48 | rd.publicKey = key; 49 | 50 | return rr; 51 | }; 52 | 53 | openpgpkey.verify = function verify(rr, key) { 54 | assert(rr instanceof Record); 55 | assert(rr.type === types.OPENPGPKEY); 56 | assert(Buffer.isBuffer(key)); 57 | 58 | const rd = rr.data; 59 | 60 | return rd.publicKey.equals(key); 61 | }; 62 | 63 | openpgpkey.encodeEmail = function encodeEmail(email, bits) { 64 | return dane.encodeEmail(email, 'openpgpkey', bits); 65 | }; 66 | 67 | openpgpkey.encodeName = function encodeName(name, local, bits) { 68 | return dane.encodeName(name, 'openpgpkey', local, bits); 69 | }; 70 | 71 | openpgpkey.decodeName = function decodeName(name) { 72 | return dane.decodeName(name, 'openpgpkey'); 73 | }; 74 | 75 | openpgpkey.isName = function isName(name) { 76 | return dane.isName(name, 'openpgpkey'); 77 | }; 78 | -------------------------------------------------------------------------------- /lib/punycode.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * punycode.js - punycode for bns 3 | * Copyright (c) 2019, Christopher Jeffrey (MIT License). 4 | * https://github.com/chjj/bns 5 | * 6 | * Parts of this software are based on bestiejs/punycode.js: 7 | * Copyright (c) 2011-2019, Mathias Bynens (MIT License) 8 | * https://github.com/bestiejs/punycode.js 9 | * https://mths.be/punycode 10 | * 11 | * Resources: 12 | * https://www.ietf.org/rfc/rfc3492.txt 13 | * https://en.wikipedia.org/wiki/Punycode 14 | * https://github.com/bestiejs/punycode.js/blob/master/punycode.js 15 | */ 16 | 17 | 'use strict'; 18 | 19 | const assert = require('bsert'); 20 | 21 | /* 22 | * Constants 23 | */ 24 | 25 | const MAX_INT = 2147483647; 26 | const BASE = 36; 27 | const T_MIN = 1; 28 | const T_MAX = 26; 29 | const SKEW = 38; 30 | const DAMP = 700; 31 | const INITIAL_BIAS = 72; 32 | const INITIAL_N = 128; 33 | const BASE_MINUS_T_MIN = BASE - T_MIN; 34 | const DELIMITER = '-'; 35 | const PUNYCODE_RX = /^xn--/; 36 | const NONASCII_RX = /[^\0-\x7e]/; 37 | const SEPARATORS_RX = /[\x2e\u3002\uff0e\uff61]/g; 38 | 39 | /* 40 | * Errors 41 | */ 42 | 43 | const errors = { 44 | OVERFLOW: 'Overflow: input needs wider integers to process.', 45 | NOT_BASIC: 'Illegal input >= 0x80 (not a basic code point).', 46 | INVALID_INPUT: 'Invalid input.' 47 | }; 48 | 49 | /* 50 | * API 51 | */ 52 | 53 | function encodeRaw(str) { 54 | assert(typeof str === 'string'); 55 | 56 | const codes = ucs2decode(str); 57 | 58 | let n = INITIAL_N; 59 | let delta = 0; 60 | let bias = INITIAL_BIAS; 61 | let len = 0; 62 | let output = ''; 63 | 64 | for (let i = 0; i < codes.length; i++) { 65 | const ch = codes[i]; 66 | 67 | if (ch >= 0x80) 68 | continue; 69 | 70 | output += String.fromCharCode(ch); 71 | len += 1; 72 | } 73 | 74 | let handled = len; 75 | 76 | if (len > 0) 77 | output += DELIMITER; 78 | 79 | while (handled < codes.length) { 80 | let m = MAX_INT; 81 | 82 | for (let i = 0; i < codes.length; i++) { 83 | const ch = codes[i]; 84 | 85 | if (ch >= n && ch < m) 86 | m = ch; 87 | } 88 | 89 | const hpo = handled + 1; 90 | 91 | if (m - n > Math.floor((MAX_INT - delta) / hpo)) 92 | throw new RangeError(errors.OVERFLOW); 93 | 94 | delta += (m - n) * hpo; 95 | n = m; 96 | 97 | for (let i = 0; i < codes.length; i++) { 98 | const ch = codes[i]; 99 | 100 | if (ch < n) { 101 | delta += 1; 102 | if (delta > MAX_INT) 103 | throw new RangeError(errors.OVERFLOW); 104 | } 105 | 106 | if (ch !== n) 107 | continue; 108 | 109 | let q = delta; 110 | let k = BASE; 111 | 112 | for (;;) { 113 | let t = T_MIN; 114 | 115 | if (k > bias) { 116 | if (k >= bias + T_MAX) 117 | t = T_MAX; 118 | else 119 | t = k - bias; 120 | } 121 | 122 | if (q < t) 123 | break; 124 | 125 | const qmt = q - t; 126 | const bmt = BASE - t; 127 | 128 | output += basic(t + qmt % bmt, 0); 129 | 130 | q = Math.floor(qmt / bmt); 131 | k += BASE; 132 | } 133 | 134 | output += basic(q, 0); 135 | 136 | bias = adapt(delta, hpo, handled === len); 137 | delta = 0; 138 | handled += 1; 139 | } 140 | 141 | delta += 1; 142 | n += 1; 143 | } 144 | 145 | return output; 146 | } 147 | 148 | function decodeRaw(str) { 149 | assert(typeof str === 'string'); 150 | 151 | let delim = str.lastIndexOf(DELIMITER); 152 | 153 | if (delim < 0) 154 | delim = 0; 155 | 156 | const codes = []; 157 | 158 | for (let i = 0; i < delim; i++) { 159 | const ch = str.charCodeAt(i); 160 | 161 | if (ch >= 0x80) 162 | throw new RangeError(errors.NOT_BASIC); 163 | 164 | codes.push(ch); 165 | } 166 | 167 | let i = 0; 168 | let n = INITIAL_N; 169 | let bias = INITIAL_BIAS; 170 | let index = delim > 0 ? delim + 1 : 0; 171 | 172 | while (index < str.length) { 173 | const j = i; 174 | 175 | let w = 1; 176 | let k = BASE; 177 | 178 | for (;;) { 179 | if (index >= str.length) 180 | throw new RangeError(errors.INVALID_INPUT); 181 | 182 | const ch = digit(str, index); 183 | 184 | index += 1; 185 | 186 | if (ch >= BASE || ch > Math.floor((MAX_INT - i) / w)) 187 | throw new RangeError(errors.OVERFLOW); 188 | 189 | i += ch * w; 190 | 191 | let t = T_MIN; 192 | 193 | if (k > bias) { 194 | if (k >= bias + T_MAX) 195 | t = T_MAX; 196 | else 197 | t = k - bias; 198 | } 199 | 200 | if (ch < t) 201 | break; 202 | 203 | const bmt = BASE - t; 204 | 205 | if (w > Math.floor(MAX_INT / bmt)) 206 | throw new RangeError(errors.OVERFLOW); 207 | 208 | w *= bmt; 209 | k += BASE; 210 | } 211 | 212 | const out = codes.length + 1; 213 | 214 | bias = adapt(i - j, out, j === 0); 215 | 216 | if (Math.floor(i / out) > MAX_INT - n) 217 | throw new RangeError(errors.OVERFLOW); 218 | 219 | n += Math.floor(i / out); 220 | i %= out; 221 | 222 | codes.splice(i, 0, n); 223 | i += 1; 224 | } 225 | 226 | return String.fromCodePoint(...codes); 227 | } 228 | 229 | function encode(str) { 230 | assert(typeof str === 'string'); 231 | 232 | return map(str, (label) => { 233 | return NONASCII_RX.test(label) 234 | ? 'xn--' + encodeRaw(label) 235 | : label; 236 | }); 237 | } 238 | 239 | function decode(str) { 240 | assert(typeof str === 'string'); 241 | 242 | return map(str, (label) => { 243 | return PUNYCODE_RX.test(label) 244 | ? decodeRaw(label.substring(4).toLowerCase()) 245 | : label; 246 | }); 247 | } 248 | 249 | /* 250 | * Helpers 251 | */ 252 | 253 | function ucs2encode(codes) { 254 | assert(Array.isArray(codes)); 255 | 256 | for (const code of codes) 257 | assert((code >>> 0) === code); 258 | 259 | return String.fromCodePoint(...codes); 260 | } 261 | 262 | function ucs2decode(str) { 263 | assert(typeof str === 'string'); 264 | 265 | const codes = []; 266 | 267 | for (let i = 0; i < str.length; i++) { 268 | const ch = str.charCodeAt(i); 269 | 270 | if (ch >= 0xd800 && ch <= 0xdbff && i + 1 < str.length) { 271 | const x = str.charCodeAt(i + 1); 272 | 273 | if ((x & 0xfc00) === 0xdc00) { 274 | codes.push(((ch & 0x3ff) << 10) + (x & 0x3ff) + 0x10000); 275 | i += 1; 276 | continue; 277 | } 278 | } 279 | 280 | codes.push(ch); 281 | } 282 | 283 | return codes; 284 | } 285 | 286 | function digit(str, index) { 287 | assert(typeof str === 'string'); 288 | assert((index >>> 0) === index); 289 | assert(index < str.length); 290 | 291 | const code = str.charCodeAt(index); 292 | 293 | if (code - 0x30 < 0x0a) 294 | return code - 0x16; 295 | 296 | if (code - 0x41 < 0x1a) 297 | return code - 0x41; 298 | 299 | if (code - 0x61 < 0x1a) 300 | return code - 0x61; 301 | 302 | return BASE; 303 | } 304 | 305 | function basic(ch, flag) { 306 | assert((ch >>> 0) === ch); 307 | assert((flag >>> 0) === flag); 308 | 309 | ch += 22 + 75 * (ch < 26); 310 | ch -= ((flag !== 0) << 5); 311 | 312 | return String.fromCharCode(ch); 313 | } 314 | 315 | function adapt(delta, points, first) { 316 | assert((delta >>> 0) === delta); 317 | assert((points >>> 0) === points); 318 | assert(typeof first === 'boolean'); 319 | 320 | let k = 0; 321 | 322 | delta = first ? Math.floor(delta / DAMP) : delta >> 1; 323 | delta += Math.floor(delta / points); 324 | 325 | for (; delta > BASE_MINUS_T_MIN * T_MAX >> 1; k += BASE) 326 | delta = Math.floor(delta / BASE_MINUS_T_MIN); 327 | 328 | return Math.floor(k + (BASE_MINUS_T_MIN + 1) * delta / (delta + SKEW)); 329 | } 330 | 331 | function map(str, fn) { 332 | assert(typeof str === 'string'); 333 | assert(typeof fn === 'function'); 334 | 335 | const index = str.indexOf('@'); 336 | 337 | let result = ''; 338 | 339 | if (index !== -1) { 340 | result = str.substring(0, index + 1); 341 | str = str.substring(index + 1); 342 | } 343 | 344 | str = str.replace(SEPARATORS_RX, '.'); 345 | 346 | const labels = str.split('.'); 347 | const encoded = []; 348 | 349 | for (const label of labels) 350 | encoded.push(fn(label)); 351 | 352 | return result + encoded.join('.'); 353 | } 354 | 355 | /* 356 | * Expose 357 | */ 358 | 359 | exports._ucs2encode = ucs2encode; 360 | exports._ucs2decode = ucs2decode; 361 | exports.encodeRaw = encodeRaw; 362 | exports.decodeRaw = decodeRaw; 363 | exports.encode = encode; 364 | exports.decode = decode; 365 | -------------------------------------------------------------------------------- /lib/rdns.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * rdns.js - replacement dns node.js module (recursive) 3 | * Copyright (c) 2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/chjj/bns 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const API = require('./api'); 10 | const Cache = require('./cache'); 11 | const Hints = require('./hints'); 12 | const RecursiveResolver = require('./resolver/recursive'); 13 | 14 | let hints = null; 15 | 16 | const cache = new Cache(); 17 | 18 | function createResolver(options) { 19 | if (!hints) 20 | hints = Hints.fromRoot(); 21 | 22 | const resolver = new RecursiveResolver(options); 23 | 24 | if (!options.hints) 25 | resolver.hints = hints.clone(); 26 | 27 | if (!options.cache) 28 | resolver.cache = cache; 29 | 30 | return resolver; 31 | } 32 | 33 | module.exports = API.make(createResolver, { 34 | tcp: true, 35 | edns: true, 36 | dnssec: true 37 | }); 38 | -------------------------------------------------------------------------------- /lib/resolver/root.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * root.js - root dns resolver for bns 3 | * Copyright (c) 2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/chjj/bns 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const assert = require('bsert'); 10 | const constants = require('../constants'); 11 | const DNSResolver = require('./dns'); 12 | const dnssec = require('../dnssec'); 13 | const Hints = require('../hints'); 14 | const ROOT_HINTS = require('../roothints'); 15 | const util = require('../util'); 16 | const wire = require('../wire'); 17 | 18 | const { 19 | types, 20 | codes, 21 | KSK_ARPA 22 | } = constants; 23 | 24 | const { 25 | hasType, 26 | extractSet 27 | } = util; 28 | 29 | const { 30 | Question, 31 | Message, 32 | Record 33 | } = wire; 34 | 35 | /* 36 | * Constants 37 | */ 38 | 39 | const CACHE_TIME = 6 * 60 * 60; 40 | 41 | /** 42 | * RootResolver 43 | * @extends DNSResolver 44 | */ 45 | 46 | class RootResolver extends DNSResolver { 47 | constructor(options) { 48 | super(options); 49 | 50 | const hints = getHints(this.inet6); 51 | 52 | this.rd = false; 53 | this.edns = true; 54 | this.dnssec = true; 55 | this.anchors = hints.anchors; 56 | this.servers = hints.servers; 57 | this.keyMap = new Map(); 58 | this.lastUpdate = 0; 59 | this.arpa = null; 60 | 61 | if (options) 62 | this.initOptions(options); 63 | } 64 | 65 | initOptions(options) { 66 | this.parseOptions(options); 67 | return this; 68 | } 69 | 70 | referArpa(qs) { 71 | assert(qs instanceof Question); 72 | 73 | if (!this.arpa) 74 | this.arpa = buildArpa(); 75 | 76 | const msg = Message.decode(this.arpa); 77 | msg.question.push(qs.clone()); 78 | return msg; 79 | } 80 | 81 | async lookupKeys(zone, ds) { 82 | const qs = new Question(zone, types.DNSKEY); 83 | const res = await this.query(qs, this.servers); 84 | 85 | const keyMap = new Map(); 86 | 87 | if (res.answer.length === 0 88 | || res.code !== codes.NOERROR) { 89 | return null; 90 | } 91 | 92 | // Pick out the valid KSK's. 93 | const kskMap = dnssec.verifyDS(res, ds, qs.name); 94 | 95 | if (!kskMap) 96 | return null; 97 | 98 | // Verify all ZSK's with KSK's. 99 | if (!dnssec.verifyZSK(res, kskMap, qs.name)) 100 | return null; 101 | 102 | const revoked = new Set(); 103 | 104 | // Grab all ZSK's from the answer. 105 | for (const rr of res.answer) { 106 | if (rr.type !== types.DNSKEY) 107 | continue; 108 | 109 | const rd = rr.data; 110 | 111 | if (!util.equal(rr.name, qs.name)) 112 | continue; 113 | 114 | if (!(rd.flags & dnssec.keyFlags.ZONE)) 115 | continue; 116 | 117 | if (rd.flags & dnssec.keyFlags.REVOKE) { 118 | revoked.add(rd.revTag()); 119 | continue; 120 | } 121 | 122 | keyMap.set(rd.keyTag(), rr); 123 | } 124 | 125 | for (const tag of revoked) 126 | keyMap.delete(tag); 127 | 128 | return keyMap; 129 | } 130 | 131 | isStale() { 132 | return util.now() > this.lastUpdate + CACHE_TIME; 133 | } 134 | 135 | async refreshKeys() { 136 | const keyMap = await this.lookupKeys('.', this.anchors); 137 | 138 | if (keyMap) { 139 | this.keyMap = keyMap; 140 | this.lastUpdate = util.now(); 141 | } 142 | } 143 | 144 | async checkSignatures(msg) { 145 | if (!this.dnssec) 146 | return true; 147 | 148 | if (msg.code !== codes.NOERROR 149 | && msg.code !== codes.NXDOMAIN) { 150 | return false; 151 | } 152 | 153 | if (this.isStale()) 154 | await this.refreshKeys(); 155 | 156 | if (!dnssec.verifyMessage(msg, this.keyMap)) 157 | return false; 158 | 159 | if (msg.code === codes.NXDOMAIN) 160 | return true; 161 | 162 | if (!hasType(msg.authority, types.NS)) 163 | return false; 164 | 165 | if (hasType(msg.authority, types.DS)) 166 | return true; 167 | 168 | const set = extractSet(msg.authority, '', types.NSEC); 169 | 170 | if (set.length !== 1) 171 | return false; 172 | 173 | const nsec = set[0].data; 174 | 175 | if (!nsec.hasType(types.NS)) 176 | return false; 177 | 178 | if (nsec.hasType(types.DS)) 179 | return false; 180 | 181 | if (nsec.hasType(types.SOA)) 182 | return false; 183 | 184 | return true; 185 | } 186 | 187 | async resolve(qs) { 188 | assert(qs instanceof Question); 189 | 190 | if (!util.isName(qs.name)) 191 | throw new Error('Invalid qname.'); 192 | 193 | if (util.countLabels(qs.name) !== 1) 194 | throw new Error('Invalid qname.'); 195 | 196 | // Special case for arpa. 197 | if (util.equal(qs.name, 'arpa.')) 198 | return this.referArpa(qs); 199 | 200 | const res = await this.query(qs, this.servers); 201 | const ad = await this.checkSignatures(res); 202 | 203 | const msg = new Message(); 204 | msg.code = res.code; 205 | msg.question = [qs.clone()]; 206 | msg.answer = res.answer; 207 | msg.authority = res.authority; 208 | msg.additional = res.additional; 209 | msg.qr = true; 210 | msg.ad = ad; 211 | 212 | dnssec.stripSignatures(msg); 213 | 214 | return msg; 215 | } 216 | 217 | async lookup(name) { 218 | const qs = new Question(name, types.NS); 219 | return this.resolve(qs); 220 | } 221 | } 222 | 223 | /* 224 | * Helpers 225 | */ 226 | 227 | function getHints(inet6) { 228 | const hints = new Hints(); 229 | 230 | hints.setRoot(); 231 | 232 | const auth = hints.getAuthority(inet6); 233 | 234 | return { 235 | anchors: hints.anchors, 236 | servers: auth.servers 237 | }; 238 | } 239 | 240 | function buildArpa() { 241 | const rrs = wire.fromZone(ROOT_HINTS); 242 | const msg = new Message(); 243 | 244 | msg.qr = true; 245 | msg.ad = true; 246 | 247 | for (const rr of rrs) { 248 | switch (rr.type) { 249 | case types.NS: 250 | rr.name = 'arpa.'; 251 | rr.canonical(); 252 | msg.authority.push(rr); 253 | break; 254 | case types.A: 255 | case types.AAAA: 256 | rr.canonical(); 257 | msg.additional.push(rr); 258 | break; 259 | } 260 | } 261 | 262 | const ds = Record.fromString(KSK_ARPA); 263 | 264 | msg.authority.push(ds); 265 | 266 | return msg.compress(); 267 | } 268 | 269 | /* 270 | * Expose 271 | */ 272 | 273 | module.exports = RootResolver; 274 | -------------------------------------------------------------------------------- /lib/resolver/stub.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * stub.js - stub dns resolver for bns 3 | * Copyright (c) 2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/chjj/bns 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const assert = require('bsert'); 10 | const constants = require('../constants'); 11 | const DNSResolver = require('./dns'); 12 | const encoding = require('../encoding'); 13 | const Hosts = require('../hosts'); 14 | const ResolvConf = require('../resolvconf'); 15 | const util = require('../util'); 16 | const wire = require('../wire'); 17 | const {MAX_EDNS_SIZE} = constants; 18 | 19 | const { 20 | Message, 21 | Question, 22 | opcodes, 23 | types, 24 | codes 25 | } = wire; 26 | 27 | /** 28 | * StubResolver 29 | * @extends DNSResolver 30 | */ 31 | 32 | class StubResolver extends DNSResolver { 33 | constructor(options) { 34 | super(options); 35 | 36 | this.rd = true; 37 | this.cd = false; 38 | this.conf = new ResolvConf(); 39 | this.hosts = new Hosts(); 40 | 41 | this.initOptions(options); 42 | } 43 | 44 | initOptions(options) { 45 | if (options == null) 46 | return this; 47 | 48 | this.parseOptions(options); 49 | 50 | if (options.conf != null) { 51 | assert(options.conf instanceof ResolvConf); 52 | this.conf = options.conf; 53 | } 54 | 55 | if (options.hosts != null) { 56 | if (Array.isArray(options.hosts)) { 57 | this.hosts.setHosts(options.hosts); 58 | } else { 59 | assert(options.hosts instanceof Hosts); 60 | this.hosts = options.hosts; 61 | } 62 | } 63 | 64 | if (options.rd != null) { 65 | assert(typeof options.rd === 'boolean'); 66 | this.rd = options.rd; 67 | } 68 | 69 | if (options.cd != null) { 70 | assert(typeof options.cd === 'boolean'); 71 | this.cd = options.cd; 72 | } 73 | 74 | if (options.servers != null) { 75 | assert(Array.isArray(options.servers)); 76 | this.conf.setServers(options.servers); 77 | } 78 | 79 | return this; 80 | } 81 | 82 | getRaw() { 83 | return this.conf.getRaw(this.inet6); 84 | } 85 | 86 | getServers() { 87 | return this.conf.getServers(); 88 | } 89 | 90 | setServers(servers) { 91 | this.conf.setServers(servers); 92 | return this; 93 | } 94 | 95 | getHosts() { 96 | return this.hosts.getHosts(); 97 | } 98 | 99 | setHosts(hosts) { 100 | this.hosts.setHosts(hosts); 101 | return this; 102 | } 103 | 104 | async resolve(qs) { 105 | assert(qs instanceof Question); 106 | 107 | const {name, type} = qs; 108 | const answer = this.hosts.query(name, type); 109 | 110 | if (answer) { 111 | const res = new Message(); 112 | 113 | res.id = util.id(); 114 | res.opcode = opcodes.QUERY; 115 | res.code = codes.NOERROR; 116 | res.qr = true; 117 | res.rd = true; 118 | res.ra = true; 119 | res.ad = true; 120 | res.question = [qs]; 121 | res.answer = answer; 122 | 123 | if (this.edns) 124 | res.setEDNS(MAX_EDNS_SIZE, this.dnssec); 125 | 126 | return res; 127 | } 128 | 129 | return this.query(qs, this.getRaw()); 130 | } 131 | 132 | async lookup(name, type) { 133 | const qs = new Question(name, type); 134 | return this.resolve(qs); 135 | } 136 | 137 | async reverse(addr) { 138 | const name = encoding.reverse(addr); 139 | return this.lookup(name, types.PTR); 140 | } 141 | } 142 | 143 | /* 144 | * Expose 145 | */ 146 | 147 | module.exports = StubResolver; 148 | -------------------------------------------------------------------------------- /lib/resolver/ub.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * unbound.js - unbound dns resolver for bns 3 | * Copyright (c) 2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/chjj/bns 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const assert = require('bsert'); 10 | const EventEmitter = require('events'); 11 | const Path = require('path'); 12 | const os = require('os'); 13 | const fs = require('bfile'); 14 | const IP = require('binet'); 15 | const Unbound = require('unbound'); 16 | const constants = require('../constants'); 17 | const encoding = require('../encoding'); 18 | const util = require('../util'); 19 | const wire = require('../wire'); 20 | const Cache = require('../cache'); 21 | const Hints = require('../hints'); 22 | 23 | const { 24 | MAX_UDP_SIZE, 25 | MAX_EDNS_SIZE 26 | } = constants; 27 | 28 | const { 29 | Message, 30 | Question, 31 | Record, 32 | opcodes, 33 | types 34 | } = wire; 35 | 36 | let defaultString = null; 37 | 38 | /** 39 | * UnboundResolver 40 | * @extends EventEmitter 41 | */ 42 | 43 | class UnboundResolver extends EventEmitter { 44 | constructor(options) { 45 | super(); 46 | 47 | this.inet6 = false; 48 | this.tcp = false; 49 | this.forceTCP = false; 50 | this.maxAttempts = 3; 51 | this.maxTimeout = 2000; 52 | this.rd = false; 53 | this.edns = false; 54 | this.ednsSize = MAX_EDNS_SIZE; 55 | this.dnssec = false; 56 | this.minimize = false; 57 | 58 | this.cache = new Cache(); 59 | this.hints = new Hints(); 60 | this.ub = new Unbound(); 61 | this.hasStub = false; 62 | this.hintsFile = null; 63 | this.opened = false; 64 | 65 | this.initOptions(options); 66 | } 67 | 68 | initOptions(options) { 69 | if (options == null) 70 | return this; 71 | 72 | assert(options && typeof options === 'object'); 73 | 74 | if (options.tcp != null) { 75 | assert(typeof options.tcp === 'boolean'); 76 | this.tcp = options.tcp; 77 | } 78 | 79 | if (options.forceTCP != null) { 80 | assert(typeof options.forceTCP === 'boolean'); 81 | if (options.forceTCP) 82 | this.tcp = true; 83 | } 84 | 85 | if (options.forceTCP != null) { 86 | assert(typeof options.forceTCP === 'boolean'); 87 | this.forceTCP = options.forceTCP; 88 | } 89 | 90 | if (options.maxAttempts != null) { 91 | assert((options.maxAttempts >>> 0) === options.maxAttempts); 92 | this.maxAttempts = options.maxAttempts; 93 | } 94 | 95 | if (options.maxTimeout != null) { 96 | assert((options.maxTimeout >>> 0) === options.maxTimeout); 97 | this.maxTimeout = options.maxTimeout; 98 | } 99 | 100 | if (options.edns != null) { 101 | assert(typeof options.edns === 'boolean'); 102 | this.edns = options.edns; 103 | } 104 | 105 | if (options.ednsSize != null) { 106 | assert((options.ednsSize >>> 0) === options.ednsSize); 107 | assert(options.ednsSize >= MAX_UDP_SIZE); 108 | assert(options.ednsSize <= MAX_EDNS_SIZE); 109 | this.ednsSize = options.ednsSize; 110 | } 111 | 112 | if (options.dnssec != null) { 113 | assert(typeof options.dnssec === 'boolean'); 114 | this.dnssec = options.dnssec; 115 | if (this.dnssec) 116 | this.edns = true; 117 | } 118 | 119 | if (options.cache != null) { 120 | assert(options.cache instanceof Cache); 121 | this.cache = options.cache; 122 | } 123 | 124 | if (options.hints != null) { 125 | assert(options.hints instanceof Hints); 126 | this.hints = options.hints; 127 | } 128 | 129 | if (options.maxReferrals != null) { 130 | assert((options.maxReferrals >>> 0) === options.maxReferrals); 131 | this.maxReferrals = options.maxReferrals; 132 | } 133 | 134 | if (options.cacheSize != null) { 135 | assert((options.cacheSize >>> 0) === options.cacheSize); 136 | this.cache.maxSize = options.cacheSize; 137 | } 138 | 139 | if (options.minimize != null) { 140 | assert(typeof options.minimize === 'boolean'); 141 | this.minimize = options.minimize; 142 | } 143 | 144 | return this; 145 | } 146 | 147 | setStub(host, port, ds) { 148 | assert(typeof host === 'string'); 149 | assert((port & 0xffff) === port); 150 | assert(port !== 0); 151 | assert(ds instanceof Record); 152 | assert(ds.type === types.DS); 153 | 154 | const ip = IP.normalize(host); 155 | 156 | assert(!this.opened); 157 | assert(!this.hasStub); 158 | assert(!this.ub.finalized); 159 | 160 | this.ub.setOption('root-hints', null); 161 | this.ub.setStub('.', `${ip}@${port}`, false); 162 | this.ub.addTrustAnchor(ds.toString()); 163 | this.hasStub = true; 164 | 165 | return this; 166 | } 167 | 168 | async open(...args) { 169 | assert(!this.opened); 170 | this.opened = true; 171 | 172 | if (this.ub.finalized) 173 | return; 174 | 175 | if (!this.hasStub) { 176 | if (!defaultString) { 177 | const h = new Hints(); 178 | h.setDefault(); 179 | defaultString = h.toHintString(); 180 | } 181 | 182 | if (this.hints.ns.length === 0) { 183 | this.ub.setOption('root-hints', null); 184 | } else { 185 | const hints = this.hints.toHintString(); 186 | 187 | if (hints !== defaultString) { 188 | const file = tempFile('hints'); 189 | 190 | await fs.writeFile(file, hints); 191 | 192 | this.ub.setOption('root-hints', file); 193 | 194 | this.hintsFile = file; 195 | } 196 | } 197 | 198 | for (const ds of this.hints.anchors) 199 | this.ub.addTrustAnchor(ds.toString()); 200 | } 201 | 202 | this.ub.setOption('logfile', null); 203 | this.ub.setOption('use-syslog', false); 204 | this.ub.tryOption('trust-anchor-signaling', false); 205 | this.ub.setOption('edns-buffer-size', this.ednsSize); 206 | this.ub.setOption('max-udp-size', this.ednsSize); 207 | this.ub.setOption('msg-cache-size', this.cache.maxSize); 208 | this.ub.setOption('key-cache-size', this.cache.maxSize); 209 | this.ub.setOption('neg-cache-size', this.cache.maxSize); 210 | this.ub.setOption('do-ip4', true); 211 | this.ub.setOption('do-ip6', this.inet6); 212 | this.ub.setOption('do-udp', !this.forceTCP); 213 | this.ub.setOption('do-tcp', this.tcp); 214 | this.ub.tryOption('qname-minimisation', this.minimize); 215 | this.ub.setOption('minimal-responses', false); 216 | 217 | if (this.hasStub) { 218 | try { 219 | this.ub.addZone('.', 'nodefault'); 220 | } catch (e) { 221 | this.ub.addZone('.', 'transparent'); 222 | } 223 | } 224 | } 225 | 226 | async close() { 227 | assert(this.opened); 228 | this.opened = false; 229 | 230 | this.ub = new Unbound(); 231 | this.hasStub = false; 232 | 233 | if (this.hintsFile) { 234 | try { 235 | await fs.unlink(this.hintsFile); 236 | } catch (e) { 237 | ; 238 | } 239 | this.hintsFile = null; 240 | } 241 | } 242 | 243 | async resolve(qs) { 244 | assert(qs instanceof Question); 245 | 246 | if (!util.isName(qs.name)) 247 | throw new Error('Invalid qname.'); 248 | 249 | const result = await this.ub.resolve(qs.name, qs.type, qs.class); 250 | 251 | let msg; 252 | 253 | if (result.answerPacket) { 254 | msg = Message.decode(result.answerPacket); 255 | } else { 256 | msg = new Message(); 257 | msg.id = 0; 258 | msg.opcode = opcodes.QUERY; 259 | msg.code = result.rcode; 260 | msg.qr = true; 261 | msg.ra = true; 262 | msg.ad = false; 263 | msg.question = [qs.clone()]; 264 | } 265 | 266 | if (result.secure && !result.bogus) 267 | msg.ad = true; 268 | else 269 | msg.ad = false; 270 | 271 | return msg; 272 | } 273 | 274 | async lookup(name, type) { 275 | const qs = new Question(name, type); 276 | return this.resolve(qs); 277 | } 278 | 279 | async reverse(addr) { 280 | const name = encoding.reverse(addr); 281 | return this.lookup(name, types.PTR); 282 | } 283 | } 284 | 285 | /* 286 | * Static 287 | */ 288 | 289 | UnboundResolver.version = Unbound.version(); 290 | UnboundResolver.native = 2; 291 | 292 | /* 293 | * Helpers 294 | */ 295 | 296 | function tempFile(name) { 297 | const rand = (Math.random() * 0x100000000) >>> 0; 298 | const pid = process.pid.toString(32); 299 | const now = Date.now().toString(32); 300 | const tag = rand.toString(32); 301 | const file = `${name}-${pid}-${now}-${tag}.zone`; 302 | 303 | return Path.resolve(os.tmpdir(), file); 304 | } 305 | 306 | /* 307 | * Expose 308 | */ 309 | 310 | module.exports = UnboundResolver; 311 | -------------------------------------------------------------------------------- /lib/resolver/unbound-browser.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * unbound.js - unbound dns resolver for bns 3 | * Copyright (c) 2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/chjj/bns 5 | */ 6 | 7 | 'use strict'; 8 | 9 | module.exports = require('./recursive'); 10 | -------------------------------------------------------------------------------- /lib/resolver/unbound.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * unbound.js - unbound dns resolver for bns 3 | * Copyright (c) 2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/chjj/bns 5 | */ 6 | 7 | 'use strict'; 8 | 9 | try { 10 | module.exports = require('./ub'); 11 | } catch (e) { 12 | module.exports = require('./recursive'); 13 | } 14 | -------------------------------------------------------------------------------- /lib/roothints.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint max-len: 0 */ 4 | 5 | module.exports = ` 6 | ; 7 | ; Root Zone 8 | ; 9 | 10 | . 3600000 IN NS A.ROOT-SERVERS.NET. 11 | A.ROOT-SERVERS.NET. 3600000 IN A 198.41.0.4 12 | A.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:503:ba3e::2:30 13 | 14 | . 3600000 IN NS B.ROOT-SERVERS.NET. 15 | B.ROOT-SERVERS.NET. 3600000 IN A 199.9.14.201 16 | B.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:500:200::b 17 | 18 | . 3600000 IN NS C.ROOT-SERVERS.NET. 19 | C.ROOT-SERVERS.NET. 3600000 IN A 192.33.4.12 20 | C.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:500:2::c 21 | 22 | . 3600000 IN NS D.ROOT-SERVERS.NET. 23 | D.ROOT-SERVERS.NET. 3600000 IN A 199.7.91.13 24 | D.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:500:2d::d 25 | 26 | . 3600000 IN NS E.ROOT-SERVERS.NET. 27 | E.ROOT-SERVERS.NET. 3600000 IN A 192.203.230.10 28 | E.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:500:a8::e 29 | 30 | . 3600000 IN NS F.ROOT-SERVERS.NET. 31 | F.ROOT-SERVERS.NET. 3600000 IN A 192.5.5.241 32 | F.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:500:2f::f 33 | 34 | . 3600000 IN NS G.ROOT-SERVERS.NET. 35 | G.ROOT-SERVERS.NET. 3600000 IN A 192.112.36.4 36 | G.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:500:12::d0d 37 | 38 | . 3600000 IN NS H.ROOT-SERVERS.NET. 39 | H.ROOT-SERVERS.NET. 3600000 IN A 198.97.190.53 40 | H.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:500:1::53 41 | 42 | . 3600000 IN NS I.ROOT-SERVERS.NET. 43 | I.ROOT-SERVERS.NET. 3600000 IN A 192.36.148.17 44 | I.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:7fe::53 45 | 46 | . 3600000 IN NS J.ROOT-SERVERS.NET. 47 | J.ROOT-SERVERS.NET. 3600000 IN A 192.58.128.30 48 | J.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:503:c27::2:30 49 | 50 | . 3600000 IN NS K.ROOT-SERVERS.NET. 51 | K.ROOT-SERVERS.NET. 3600000 IN A 193.0.14.129 52 | K.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:7fd::1 53 | 54 | . 3600000 IN NS L.ROOT-SERVERS.NET. 55 | L.ROOT-SERVERS.NET. 3600000 IN A 199.7.83.42 56 | L.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:500:9f::42 57 | 58 | . 3600000 IN NS M.ROOT-SERVERS.NET. 59 | M.ROOT-SERVERS.NET. 3600000 IN A 202.12.27.33 60 | M.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:dc3::35 61 | 62 | ; 63 | ; Trust Anchors 64 | ; 65 | 66 | . 172800 IN DS 19036 8 2 49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5 67 | . 172800 IN DS 20326 8 2 E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D 68 | `; 69 | -------------------------------------------------------------------------------- /lib/server/auth.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * auth.js - authoritative dns server for bns 3 | * Copyright (c) 2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/chjj/bns 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const DNSServer = require('./dns'); 10 | const Zone = require('../zone'); 11 | 12 | /** 13 | * AuthServer 14 | * @extends EventEmitter 15 | */ 16 | 17 | class AuthServer extends DNSServer { 18 | constructor(options) { 19 | super(options); 20 | this.zone = new Zone(); 21 | this.file = null; 22 | this.ra = false; 23 | this.initOptions(options); 24 | } 25 | 26 | setOrigin(name) { 27 | this.zone.setOrigin(name); 28 | return this; 29 | } 30 | 31 | setFile(file) { 32 | this.zone.clearRecords(); 33 | this.zone.fromFile(file); 34 | this.file = file; 35 | return this; 36 | } 37 | 38 | async resolve(req, rinfo) { 39 | const [qs] = req.question; 40 | const {name, type} = qs; 41 | return this.zone.resolve(name, type); 42 | } 43 | } 44 | 45 | /* 46 | * Expose 47 | */ 48 | 49 | module.exports = AuthServer; 50 | -------------------------------------------------------------------------------- /lib/server/recursive.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * recursive.js - recursive dns server for bns 3 | * Copyright (c) 2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/chjj/bns 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const DNSServer = require('./dns'); 10 | const RecursiveResolver = require('../resolver/recursive'); 11 | 12 | /** 13 | * RecursiveServer 14 | * @extends EventEmitter 15 | */ 16 | 17 | class RecursiveServer extends DNSServer { 18 | constructor(options) { 19 | super(options); 20 | this.resolver = new RecursiveResolver(options); 21 | this.resolver.on('log', (...args) => this.emit('log', ...args)); 22 | this.resolver.on('error', err => this.emit('error', err)); 23 | this.ra = true; 24 | this.initOptions(options); 25 | } 26 | 27 | get cache() { 28 | return this.resolver.cache; 29 | } 30 | 31 | set cache(value) { 32 | this.resolver.cache = value; 33 | } 34 | 35 | get hints() { 36 | return this.resolver.hints; 37 | } 38 | 39 | set hints(value) { 40 | this.resolver.hints = value; 41 | } 42 | } 43 | 44 | /* 45 | * Expose 46 | */ 47 | 48 | module.exports = RecursiveServer; 49 | -------------------------------------------------------------------------------- /lib/server/stub.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * stub.js - stub dns server for bns 3 | * Copyright (c) 2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/chjj/bns 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const DNSServer = require('./dns'); 10 | const StubResolver = require('../resolver/stub'); 11 | 12 | /** 13 | * StubServer 14 | * @extends EventEmitter 15 | */ 16 | 17 | class StubServer extends DNSServer { 18 | constructor(options) { 19 | super(options); 20 | this.resolver = new StubResolver(options); 21 | this.resolver.on('log', (...args) => this.emit('log', ...args)); 22 | this.resolver.on('error', err => this.emit('error', err)); 23 | this.ra = true; 24 | this.initOptions(options); 25 | } 26 | 27 | getServers() { 28 | return this.resolver.getServers(); 29 | } 30 | 31 | setServers(servers) { 32 | this.resolver.setServers(servers); 33 | return this; 34 | } 35 | 36 | get conf() { 37 | return this.resolver.conf; 38 | } 39 | 40 | set conf(value) { 41 | this.resolver.conf = value; 42 | } 43 | 44 | get hosts() { 45 | return this.resolver.hosts; 46 | } 47 | 48 | set hosts(value) { 49 | this.resolver.hosts = value; 50 | } 51 | } 52 | 53 | /* 54 | * Expose 55 | */ 56 | 57 | module.exports = StubServer; 58 | -------------------------------------------------------------------------------- /lib/server/unbound.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * unbound.js - unbound recursive dns server for bns 3 | * Copyright (c) 2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/chjj/bns 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const DNSServer = require('./dns'); 10 | const UnboundResolver = require('../resolver/unbound'); 11 | 12 | /** 13 | * UnboundServer 14 | * @extends EventEmitter 15 | */ 16 | 17 | class UnboundServer extends DNSServer { 18 | constructor(options) { 19 | super(options); 20 | this.resolver = new UnboundResolver(options); 21 | this.resolver.on('log', (...args) => this.emit('log', ...args)); 22 | this.resolver.on('error', err => this.emit('error', err)); 23 | this.ra = true; 24 | this.initOptions(options); 25 | } 26 | 27 | get cache() { 28 | return this.resolver.cache; 29 | } 30 | 31 | set cache(value) { 32 | this.resolver.cache = value; 33 | } 34 | 35 | get hints() { 36 | return this.resolver.hints; 37 | } 38 | 39 | set hints(value) { 40 | this.resolver.hints = value; 41 | } 42 | } 43 | 44 | /* 45 | * Static 46 | */ 47 | 48 | UnboundServer.version = UnboundResolver.version; 49 | UnboundServer.native = UnboundResolver.native; 50 | 51 | /* 52 | * Expose 53 | */ 54 | 55 | module.exports = UnboundServer; 56 | -------------------------------------------------------------------------------- /lib/smimea.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * smimea.js - SMIMEA for bns 3 | * Copyright (c) 2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/chjj/bns 5 | * 6 | * Parts of this software are based on miekg/dns: 7 | * https://github.com/miekg/dns/blob/master/smimea.go 8 | * 9 | * Resources: 10 | * https://tools.ietf.org/html/rfc6698 11 | */ 12 | 13 | 'use strict'; 14 | 15 | const assert = require('bsert'); 16 | const dane = require('./dane'); 17 | const wire = require('./wire'); 18 | 19 | const { 20 | usages, 21 | selectors, 22 | matchingTypes 23 | } = dane; 24 | 25 | const { 26 | types, 27 | classes, 28 | Record, 29 | SMIMEARecord 30 | } = wire; 31 | 32 | /* 33 | * SMIMEA 34 | */ 35 | 36 | const smimea = exports; 37 | 38 | smimea.create = function create(cert, email, options = {}) { 39 | assert(Buffer.isBuffer(cert)); 40 | assert(options && typeof options === 'object'); 41 | 42 | let {ttl, usage, selector, matchingType} = options; 43 | 44 | if (ttl == null) 45 | ttl = 3600; 46 | 47 | if (usage == null) 48 | usage = usages.DIC; 49 | 50 | if (selector == null) 51 | selector = selectors.SPKI; 52 | 53 | if (matchingType == null) 54 | matchingType = matchingTypes.SHA256; 55 | 56 | assert((ttl >>> 0) === ttl); 57 | assert((usage & 0xff) === usage); 58 | assert((selector & 0xff) === selector); 59 | assert((matchingType & 0xff) === matchingType); 60 | 61 | const rr = new Record(); 62 | const rd = new SMIMEARecord(); 63 | 64 | rr.name = smimea.encodeEmail(email); 65 | rr.type = types.SMIMEA; 66 | rr.class = classes.IN; 67 | rr.ttl = ttl; 68 | rr.data = rd; 69 | rd.usage = usage; 70 | rd.selector = selector; 71 | rd.matchingType = matchingType; 72 | 73 | const hash = dane.sign(cert, selector, matchingType); 74 | 75 | if (!hash) 76 | throw new Error('Unknown selector or matching type.'); 77 | 78 | rd.certificate = hash; 79 | 80 | return rr; 81 | }; 82 | 83 | smimea.verify = function verify(rr, cert) { 84 | assert(rr instanceof Record); 85 | assert(rr.type === types.SMIMEA); 86 | 87 | const rd = rr.data; 88 | 89 | return dane.verify(cert, rd.selector, rd.matchingType, rd.certificate); 90 | }; 91 | 92 | smimea.encodeEmail = function encodeEmail(email, bits) { 93 | return dane.encodeEmail(email, 'smimecert', bits); 94 | }; 95 | 96 | smimea.encodeName = function encodeName(name, local, bits) { 97 | return dane.encodeName(name, 'smimecert', local, bits); 98 | }; 99 | 100 | smimea.decodeName = function decodeName(name) { 101 | return dane.decodeName(name, 'smimecert'); 102 | }; 103 | 104 | smimea.isName = function isName(name) { 105 | return dane.isName(name, 'smimecert'); 106 | }; 107 | 108 | /* 109 | * Expose 110 | */ 111 | 112 | smimea.usages = usages; 113 | smimea.selectors = selectors; 114 | smimea.matchingTypes = matchingTypes; 115 | -------------------------------------------------------------------------------- /lib/srv.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * srv.js - SRV for bns 3 | * Copyright (c) 2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/chjj/bns 5 | * 6 | * Resources: 7 | * https://tools.ietf.org/html/rfc2782 8 | */ 9 | 10 | 'use strict'; 11 | 12 | const assert = require('bsert'); 13 | const util = require('./util'); 14 | 15 | /* 16 | * SRV 17 | */ 18 | 19 | const srv = exports; 20 | 21 | srv.encodeName = function encodeName(name, protocol, service) { 22 | assert(util.isName(name)); 23 | assert(name.length === 0 || name[0] !== '_'); 24 | assert(util.isName(protocol)); 25 | assert(protocol.length >= 1 && protocol.length <= 62); 26 | assert(protocol[0] !== '_'); 27 | assert(protocol.indexOf('.') === -1); 28 | assert(util.isName(service)); 29 | assert(service.length >= 1 && service.length <= 62); 30 | assert(service[0] !== '_'); 31 | assert(service.indexOf('.') === -1); 32 | 33 | if (name === '.') 34 | name = ''; 35 | 36 | const encoded = util.fqdn(`_${service}._${protocol}.${name}`); 37 | 38 | assert(util.isName(encoded)); 39 | 40 | return encoded; 41 | }; 42 | 43 | srv.decodeName = function decodeName(name) { 44 | assert(util.isName(name)); 45 | 46 | const labels = util.split(name); 47 | 48 | assert(labels.length >= 3); 49 | 50 | const service = util.label(name, labels, 0); 51 | const protocol = util.label(name, labels, 1); 52 | 53 | assert(service.length >= 2); 54 | assert(protocol.length >= 2); 55 | assert(service[0] === '_'); 56 | assert(protocol[0] === '_'); 57 | 58 | return { 59 | name: util.fqdn(util.from(name, labels, 2)), 60 | protocol: protocol.substring(1).toLowerCase(), 61 | service: service.substring(1).toLowerCase() 62 | }; 63 | }; 64 | 65 | srv.isName = function isName(name) { 66 | assert(util.isName(name)); 67 | 68 | try { 69 | srv.decodeName(name); 70 | return true; 71 | } catch (e) { 72 | return false; 73 | } 74 | }; 75 | -------------------------------------------------------------------------------- /lib/sshfp.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * sshfp.js - SSHFP for bns 3 | * Copyright (c) 2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/chjj/bns 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const assert = require('bsert'); 10 | const constants = require('./constants'); 11 | const crypto = require('./internal/crypto'); 12 | const util = require('./util'); 13 | const wire = require('./wire'); 14 | 15 | const { 16 | types, 17 | classes, 18 | sshAlgs, 19 | sshAlgsByVal, 20 | sshHashes, 21 | sshHashesByVal 22 | } = constants; 23 | 24 | const { 25 | Record, 26 | SSHFPRecord 27 | } = wire; 28 | 29 | /* 30 | * SSHFP 31 | */ 32 | 33 | const sshfp = exports; 34 | 35 | sshfp.hash = function hash(key, digestType) { 36 | assert(Buffer.isBuffer(key)); 37 | assert((digestType & 0xff) === digestType); 38 | 39 | switch (digestType) { 40 | case sshHashes.SHA1: 41 | return crypto.sha1.digest(key); 42 | case sshHashes.SHA256: 43 | return crypto.sha256.digest(key); 44 | } 45 | 46 | return null; 47 | }; 48 | 49 | sshfp.validate = function validate(key, digestType, fingerprint) { 50 | assert(Buffer.isBuffer(fingerprint)); 51 | 52 | const hash = sshfp.hash(key, digestType); 53 | 54 | if (!hash) 55 | return false; 56 | 57 | return hash.equals(fingerprint); 58 | }; 59 | 60 | sshfp.create = function create(key, name, alg, digest) { 61 | assert(Buffer.isBuffer(key)); 62 | assert(typeof name === 'string'); 63 | assert((alg & 0xff) === alg); 64 | assert((digest & 0xff) === digest); 65 | 66 | const rr = new Record(); 67 | const rd = new SSHFPRecord(); 68 | 69 | rr.name = util.fqdn(name); 70 | rr.type = types.SSHFP; 71 | rr.class = classes.IN; 72 | rr.ttl = 172800; 73 | rr.data = rd; 74 | rd.algorithm = alg; 75 | rd.digestType = digest; 76 | 77 | return sshfp.sign(rr, key); 78 | }; 79 | 80 | sshfp.sign = function sign(rr, key) { 81 | assert(rr instanceof Record); 82 | assert(rr.type === types.SSHFP); 83 | 84 | const rd = rr.data; 85 | const hash = sshfp.hash(key, rd.digestType); 86 | 87 | if (!hash) 88 | throw new Error('Unknown digest type.'); 89 | 90 | rd.fingerprint = hash; 91 | 92 | return rr; 93 | }; 94 | 95 | sshfp.verify = function verify(rr, key) { 96 | assert(rr instanceof Record); 97 | assert(rr.type === types.SSHFP); 98 | 99 | const rd = rr.data; 100 | 101 | return sshfp.validate(key, rd.digestType, rd.fingerprint); 102 | }; 103 | 104 | /* 105 | * Expose 106 | */ 107 | 108 | sshfp.algs = sshAlgs; 109 | sshfp.algsByVal = sshAlgsByVal; 110 | sshfp.hashes = sshHashes; 111 | sshfp.hashesByVal = sshHashesByVal; 112 | -------------------------------------------------------------------------------- /lib/tlsa.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * tlsa.js - TLSA for bns 3 | * Copyright (c) 2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/chjj/bns 5 | * 6 | * Parts of this software are based on miekg/dns: 7 | * https://github.com/miekg/dns/blob/master/tlsa.go 8 | * 9 | * Resources: 10 | * https://tools.ietf.org/html/rfc6698 11 | */ 12 | 13 | 'use strict'; 14 | 15 | const assert = require('bsert'); 16 | const dane = require('./dane'); 17 | const util = require('./util'); 18 | const wire = require('./wire'); 19 | 20 | const { 21 | usages, 22 | selectors, 23 | matchingTypes 24 | } = dane; 25 | 26 | const { 27 | types, 28 | classes, 29 | Record, 30 | TLSARecord 31 | } = wire; 32 | 33 | /* 34 | * TLSA 35 | */ 36 | 37 | const tlsa = exports; 38 | 39 | tlsa.create = function create(cert, name, protocol, port, options = {}) { 40 | assert(Buffer.isBuffer(cert)); 41 | assert(options && typeof options === 'object'); 42 | 43 | let {ttl, usage, selector, matchingType} = options; 44 | 45 | if (ttl == null) 46 | ttl = 3600; 47 | 48 | if (usage == null) 49 | usage = usages.DIC; 50 | 51 | if (selector == null) 52 | selector = selectors.SPKI; 53 | 54 | if (matchingType == null) 55 | matchingType = matchingTypes.SHA256; 56 | 57 | assert((ttl >>> 0) === ttl); 58 | assert((usage & 0xff) === usage); 59 | assert((selector & 0xff) === selector); 60 | assert((matchingType & 0xff) === matchingType); 61 | 62 | const rr = new Record(); 63 | const rd = new TLSARecord(); 64 | 65 | rr.name = tlsa.encodeName(name, protocol, port); 66 | rr.type = types.TLSA; 67 | rr.class = classes.IN; 68 | rr.ttl = ttl; 69 | rr.data = rd; 70 | rd.usage = usage; 71 | rd.selector = selector; 72 | rd.matchingType = matchingType; 73 | 74 | const hash = dane.sign(cert, selector, matchingType); 75 | 76 | if (!hash) 77 | throw new Error('Unknown selector or matching type.'); 78 | 79 | rd.certificate = hash; 80 | 81 | return rr; 82 | }; 83 | 84 | tlsa.verify = function verify(rr, cert) { 85 | assert(rr instanceof Record); 86 | assert(rr.type === types.TLSA); 87 | 88 | const rd = rr.data; 89 | 90 | return dane.verify(cert, rd.selector, rd.matchingType, rd.certificate); 91 | }; 92 | 93 | tlsa.encodeName = function encodeName(name, protocol, port) { 94 | assert(util.isName(name)); 95 | assert(name.length === 0 || name[0] !== '_'); 96 | assert(typeof protocol === 'string'); 97 | assert(protocol.length >= 1 && protocol.length <= 62); 98 | assert(protocol[0] !== '_'); 99 | assert(protocol.indexOf('.') === -1); 100 | assert((port & 0xffff) === port); 101 | 102 | if (name === '.') 103 | name = ''; 104 | 105 | const encoded = util.fqdn(`_${port.toString(10)}._${protocol}.${name}`); 106 | 107 | assert(util.isName(encoded)); 108 | 109 | return encoded; 110 | }; 111 | 112 | tlsa.decodeName = function decodeName(name) { 113 | assert(util.isName(name)); 114 | 115 | const labels = util.split(name); 116 | 117 | assert(labels.length >= 3); 118 | 119 | const port = util.label(name, labels, 0); 120 | const protocol = util.label(name, labels, 1); 121 | 122 | assert(port.length >= 2); 123 | assert(protocol.length >= 2); 124 | assert(port[0] === '_'); 125 | assert(protocol[0] === '_'); 126 | 127 | return { 128 | name: util.fqdn(util.from(name, labels, 2)), 129 | protocol: protocol.substring(1).toLowerCase(), 130 | port: util.parseU16(port.substring(1)) 131 | }; 132 | }; 133 | 134 | tlsa.isName = function isName(name) { 135 | assert(util.isName(name)); 136 | 137 | try { 138 | tlsa.decodeName(name); 139 | return true; 140 | } catch (e) { 141 | return false; 142 | } 143 | }; 144 | 145 | /* 146 | * Expose 147 | */ 148 | 149 | tlsa.usages = usages; 150 | tlsa.selectors = selectors; 151 | tlsa.matchingTypes = matchingTypes; 152 | -------------------------------------------------------------------------------- /lib/tsig.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * tsig.js - TSIG for bns 3 | * Copyright (c) 2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/chjj/bns 5 | * 6 | * Parts of this software are based on miekg/dns: 7 | * https://github.com/miekg/dns/blob/master/tsig.go 8 | */ 9 | 10 | 'use strict'; 11 | 12 | const assert = require('bsert'); 13 | const bio = require('bufio'); 14 | const constants = require('./constants'); 15 | const crypto = require('./internal/crypto'); 16 | const encoding = require('./encoding'); 17 | const util = require('./util'); 18 | const wire = require('./wire'); 19 | 20 | const { 21 | types, 22 | classes, 23 | tsigAlgs 24 | } = constants; 25 | 26 | const { 27 | readNameBR, 28 | writeNameBW 29 | } = encoding; 30 | 31 | const { 32 | Record, 33 | TSIGRecord 34 | } = wire; 35 | 36 | /* 37 | * Constants 38 | */ 39 | 40 | const DEFAULT_FUDGE = 300; 41 | 42 | /* 43 | * TSIG 44 | */ 45 | 46 | const tsig = exports; 47 | 48 | tsig.sign = function sign(msg, sig, secret, requestMAC, timersOnly) { 49 | if (typeof sig === 'string') { 50 | const alg = sig; 51 | sig = new Record(); 52 | sig.type = types.TSIG; 53 | sig.data = new TSIGRecord(); 54 | sig.data.algorithm = alg; 55 | } 56 | 57 | if (requestMAC == null) 58 | requestMAC = null; 59 | 60 | if (timersOnly == null) 61 | timersOnly = false; 62 | 63 | assert(Buffer.isBuffer(msg)); 64 | assert(msg.length >= 12); 65 | assert(sig instanceof Record); 66 | assert(sig.type === types.TSIG); 67 | assert(Buffer.isBuffer(secret)); 68 | assert(requestMAC === null || Buffer.isBuffer(requestMAC)); 69 | assert(typeof timersOnly === 'boolean'); 70 | 71 | const id = bio.readU16BE(msg, 0); 72 | const rr = new Record(); 73 | const rd = sig.data.clone(); 74 | 75 | rr.name = '.'; 76 | rr.type = types.TSIG; 77 | rr.class = classes.ANY; 78 | rr.ttl = 0; 79 | rr.data = rd; 80 | 81 | if (rd.algorithm === '.') 82 | rd.algorithm = tsigAlgs.SHA256; 83 | 84 | if (rd.timeSigned === 0) 85 | rd.timeSigned = util.now(); 86 | 87 | if (rd.fudge === 0) 88 | rd.fudge = DEFAULT_FUDGE; 89 | 90 | rd.origID = id; 91 | 92 | const pre = removeTSIG(msg); 93 | const data = tsigData(pre, rd, requestMAC, timersOnly, 0); 94 | const hash = tsigHash(rd.algorithm, data, secret); 95 | 96 | if (!hash) 97 | throw new Error(`Unknown TSIG algorithm: ${rd.algorithm}.`); 98 | 99 | rd.mac = hash; 100 | 101 | const arcount = bio.readU16BE(pre, 10); 102 | const size = rr.getSize(); 103 | const bw = bio.write(pre.length + size); 104 | 105 | bw.copy(pre, 0, 10); 106 | bw.writeU16BE(arcount + 1); 107 | bw.copy(pre, 12, pre.length); 108 | rr.write(bw); 109 | 110 | return bw.render(); 111 | }; 112 | 113 | tsig.verify = function verify(msg, secret, requestMAC, timersOnly) { 114 | if (requestMAC == null) 115 | requestMAC = null; 116 | 117 | if (timersOnly == null) 118 | timersOnly = false; 119 | 120 | assert(Buffer.isBuffer(msg)); 121 | assert(Buffer.isBuffer(secret)); 122 | assert(requestMAC === null || Buffer.isBuffer(requestMAC)); 123 | assert(typeof timersOnly === 'boolean'); 124 | 125 | const [pos, rr] = findTSIG(msg); 126 | 127 | // No TSIG found. 128 | if (pos === -1) 129 | return false; 130 | 131 | const rd = rr.data; 132 | const inception = rd.timeSigned - rd.fudge; 133 | const expiration = rd.timeSigned + rd.fudge; 134 | const now = util.now(); 135 | 136 | if (now < inception) 137 | return false; 138 | 139 | if (now > expiration) 140 | return false; 141 | 142 | const pre = msg.slice(0, pos); 143 | const data = tsigData(pre, rd, requestMAC, timersOnly, -1); 144 | const hash = tsigHash(rd.algorithm, data, secret); 145 | 146 | // Unknown algorithm. 147 | if (!hash) 148 | return false; 149 | 150 | // Constant time equals. 151 | return crypto.safeEqual(rd.mac, hash); 152 | }; 153 | 154 | /* 155 | * Helpers 156 | */ 157 | 158 | function findTSIG(msg) { 159 | assert(Buffer.isBuffer(msg)); 160 | 161 | try { 162 | return _findTSIG(msg); 163 | } catch (e) { 164 | return [-1, null]; 165 | } 166 | } 167 | 168 | function _findTSIG(msg) { 169 | const br = bio.read(msg); 170 | 171 | br.readU16BE(); 172 | br.readU16BE(); 173 | 174 | const qdcount = br.readU16BE(); 175 | const ancount = br.readU16BE(); 176 | const nscount = br.readU16BE(); 177 | const arcount = br.readU16BE(); 178 | 179 | if (arcount === 0) 180 | return [-1, null]; 181 | 182 | for (let i = 0; i < qdcount; i++) { 183 | if (br.left() === 0) 184 | return [-1, null]; 185 | 186 | readNameBR(br); 187 | br.seek(4); 188 | } 189 | 190 | for (let i = 0; i < ancount; i++) { 191 | if (br.left() === 0) 192 | return [-1, null]; 193 | 194 | readNameBR(br); 195 | br.seek(8); 196 | br.seek(br.readU16BE()); 197 | } 198 | 199 | for (let i = 0; i < nscount; i++) { 200 | if (br.left() === 0) 201 | return [-1, null]; 202 | 203 | readNameBR(br); 204 | br.seek(8); 205 | br.seek(br.readU16BE()); 206 | } 207 | 208 | for (let i = 0; i < arcount - 1; i++) { 209 | if (br.left() === 0) 210 | return [-1, null]; 211 | 212 | readNameBR(br); 213 | br.seek(8); 214 | br.seek(br.readU16BE()); 215 | } 216 | 217 | const offset = br.offset; 218 | const rr = Record.read(br); 219 | 220 | if (rr.name !== '.') 221 | return [-1, null]; 222 | 223 | if (rr.type !== types.TSIG) 224 | return [-1, null]; 225 | 226 | if (rr.class !== classes.ANY) 227 | return [-1, null]; 228 | 229 | if (rr.ttl !== 0) 230 | return [-1, null]; 231 | 232 | return [offset, rr]; 233 | } 234 | 235 | function removeTSIG(msg) { 236 | assert(Buffer.isBuffer(msg)); 237 | assert(msg.length >= 12); 238 | 239 | const [pos] = findTSIG(msg); 240 | 241 | if (pos === -1) 242 | return msg; 243 | 244 | const arcount = bio.readU16BE(msg, 10); 245 | const buf = Buffer.allocUnsafe(pos); 246 | msg.copy(buf, 0, 0, pos); 247 | bio.writeU16BE(buf, arcount - 1, 10); 248 | 249 | return buf; 250 | } 251 | 252 | function tsigData(msg, sig, requestMAC, timersOnly, offset) { 253 | assert(Buffer.isBuffer(msg)); 254 | assert(msg.length >= 12); 255 | assert(sig instanceof TSIGRecord); 256 | assert(requestMAC === null || Buffer.isBuffer(requestMAC)); 257 | assert(typeof timersOnly === 'boolean'); 258 | assert(Number.isSafeInteger(offset)); 259 | 260 | const arcount = bio.readU16BE(msg, 10); 261 | 262 | if (arcount + offset < 0) 263 | throw new Error('Bad offset.'); 264 | 265 | let size = 0; 266 | 267 | if (requestMAC) { 268 | size += 2; 269 | size += requestMAC.length; 270 | } 271 | 272 | size += msg.length; 273 | 274 | if (timersOnly) { 275 | // Time signed and fudge. 276 | size += 8; 277 | } else { 278 | // Header, minus rdlen. 279 | size += 9; 280 | 281 | // TSIG minus mac and origID. 282 | size += sig.getSize(); 283 | size -= 2 + sig.mac.length + 2; 284 | } 285 | 286 | const bw = bio.write(size); 287 | 288 | if (requestMAC) { 289 | bw.writeU16BE(requestMAC.length); 290 | bw.writeBytes(requestMAC); 291 | } 292 | 293 | bw.writeU16BE(sig.origID); 294 | bw.copy(msg, 2, 10); 295 | bw.writeU16BE(arcount + offset); 296 | bw.copy(msg, 12, msg.length); 297 | 298 | if (timersOnly) { 299 | bw.writeU16BE((sig.timeSigned / 0x100000000) >>> 0); 300 | bw.writeU32BE(sig.timeSigned >>> 0); 301 | bw.writeU16BE(sig.fudge); 302 | } else { 303 | const alg = sig.algorithm.toLowerCase(); 304 | 305 | bw.writeU8(0); 306 | bw.writeU16BE(types.TSIG); 307 | bw.writeU16BE(classes.ANY); 308 | bw.writeU32BE(0); 309 | 310 | // No rdlen. 311 | 312 | writeNameBW(bw, alg); 313 | bw.writeU16BE((sig.timeSigned / 0x100000000) >>> 0); 314 | bw.writeU32BE(sig.timeSigned >>> 0); 315 | bw.writeU16BE(sig.fudge); 316 | 317 | // No mac or origID. 318 | 319 | bw.writeU16BE(sig.error); 320 | bw.writeU16BE(sig.other.length); 321 | bw.writeBytes(sig.other); 322 | } 323 | 324 | return bw.render(); 325 | } 326 | 327 | function tsigHash(alg, data, secret) { 328 | assert(typeof alg === 'string'); 329 | assert(Buffer.isBuffer(data)); 330 | assert(Buffer.isBuffer(secret)); 331 | 332 | switch (alg.toLowerCase()) { 333 | case tsigAlgs.MD5: 334 | return crypto.md5.mac(data, secret); 335 | case tsigAlgs.SHA1: 336 | return crypto.sha1.mac(data, secret); 337 | case tsigAlgs.SHA256: 338 | return crypto.sha256.mac(data, secret); 339 | case tsigAlgs.SHA512: 340 | return crypto.sha512.mac(data, secret); 341 | } 342 | 343 | return null; 344 | } 345 | 346 | /* 347 | * Expose 348 | */ 349 | 350 | tsig.algs = tsigAlgs; 351 | -------------------------------------------------------------------------------- /lib/udns.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * udns.js - replacement dns node.js module (recursive) 3 | * Copyright (c) 2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/chjj/bns 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const API = require('./api'); 10 | const Cache = require('./cache'); 11 | const Hints = require('./hints'); 12 | const UnboundResolver = require('./resolver/unbound'); 13 | 14 | let hints = null; 15 | let ub = null; 16 | 17 | const cache = new Cache(); 18 | 19 | function createResolver(options) { 20 | if (!hints) 21 | hints = Hints.fromRoot(); 22 | 23 | const resolver = new UnboundResolver(options); 24 | 25 | if (!ub) 26 | ub = resolver.ub; 27 | 28 | if (!options.hints) 29 | resolver.hints = hints.clone(); 30 | 31 | if (!options.cache) 32 | resolver.cache = cache; 33 | 34 | resolver.ub = ub; 35 | 36 | return resolver; 37 | } 38 | 39 | const api = API.make(createResolver, { 40 | tcp: true, 41 | edns: true, 42 | dnssec: true 43 | }); 44 | 45 | api.version = UnboundResolver.version; 46 | api.native = UnboundResolver.native; 47 | 48 | module.exports = api; 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bns", 3 | "version": "0.15.0", 4 | "description": "DNS bike-shed", 5 | "keywords": [ 6 | "bns", 7 | "dns", 8 | "udp", 9 | "tcp" 10 | ], 11 | "license": "MIT", 12 | "repository": "git://github.com/chjj/bns.git", 13 | "homepage": "https://github.com/chjj/bns", 14 | "bugs": { 15 | "url": "https://github.com/chjj/bns/issues" 16 | }, 17 | "author": "Christopher Jeffrey ", 18 | "main": "./lib/bns.js", 19 | "bin": { 20 | "bns-keygen": "./bin/bns-keygen", 21 | "bns-prove": "./bin/bns-prove", 22 | "dig.js": "./bin/dig.js", 23 | "named.js": "./bin/named.js", 24 | "dig2json": "./bin/dig2json", 25 | "json2dig": "./bin/json2dig", 26 | "rr2json": "./bin/rr2json", 27 | "json2rr": "./bin/json2rr", 28 | "zone2json": "./bin/zone2json", 29 | "json2zone": "./bin/json2zone", 30 | "whois.js": "./bin/whois.js" 31 | }, 32 | "scripts": { 33 | "lint": "eslint bin/* lib/ test/ || exit 0", 34 | "test": "bmocha --reporter spec test/*-test.js", 35 | "test-browser": "bmocha -H --reporter spec test/*-test.js", 36 | "test-file": "bmocha --reporter spec" 37 | }, 38 | "dependencies": { 39 | "bcrypto": "~5.4.0", 40 | "bfile": "~0.2.2", 41 | "bheep": "~0.1.5", 42 | "binet": "~0.3.6", 43 | "bs32": "~0.1.6", 44 | "bsert": "~0.0.10", 45 | "btcp": "~0.1.5", 46 | "budp": "~0.1.6", 47 | "bufio": "~1.0.7" 48 | }, 49 | "optionalDependencies": { 50 | "unbound": "~0.4.3" 51 | }, 52 | "devDependencies": { 53 | "bmocha": "^2.1.5" 54 | }, 55 | "engines": { 56 | "node": ">=8.0.0" 57 | }, 58 | "browser": { 59 | "./lib/internal/lazy": "./lib/internal/lazy-browser.js", 60 | "./lib/resolver/unbound": "./lib/resolver/unbound-browser.js" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /test/authority-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* eslint prefer-arrow-callback: "off" */ 3 | 4 | 'use strict'; 5 | 6 | const assert = require('bsert'); 7 | const Authority = require('../lib/authority'); 8 | 9 | describe('Authority', function() { 10 | it('should add servers', () => { 11 | const auth = new Authority('com.', 'ns1.com.'); 12 | assert.strictEqual(auth.servers.length, 0); 13 | 14 | auth.add('127.0.0.1', 53); 15 | 16 | assert.strictEqual(auth.servers.length, 1); 17 | assert.strictEqual(auth.servers[0].host, '127.0.0.1'); 18 | assert.strictEqual(auth.servers[0].port, 53); 19 | 20 | assert.deepStrictEqual(auth.clone(), auth); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/bns-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* eslint prefer-arrow-callback: "off" */ 3 | 4 | 'use strict'; 5 | 6 | const assert = require('bsert'); 7 | 8 | describe('BNS', function() { 9 | it('should require BNS', () => { 10 | const bns = require('../'); 11 | assert(bns); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /test/cache-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* eslint prefer-arrow-callback: "off" */ 3 | 4 | 'use strict'; 5 | 6 | const assert = require('bsert'); 7 | const wire = require('../lib/wire'); 8 | const Cache = require('../lib/cache'); 9 | 10 | const { 11 | types, 12 | Message, 13 | Question, 14 | Record, 15 | ARecord 16 | } = wire; 17 | 18 | describe('Cache', function() { 19 | it('should cache message', () => { 20 | const qs = new Question('example.com.', 'A'); 21 | const msg = new Message(); 22 | 23 | msg.qr = true; 24 | msg.ad = true; 25 | msg.question.push(qs); 26 | 27 | const rr = new Record(); 28 | const rd = new ARecord(); 29 | 30 | rr.name = 'google.com.'; 31 | rr.type = types.A; 32 | rr.ttl = 3600; 33 | rr.data = rd; 34 | rd.address = '127.0.0.1'; 35 | 36 | msg.answer.push(rr); 37 | 38 | const cache = new Cache(); 39 | 40 | cache.insert(qs, 'com.', msg, true); 41 | assert.strictEqual(cache.size, 255); 42 | 43 | cache.insert(qs, 'com.', msg, true); 44 | assert.strictEqual(cache.size, 313); 45 | 46 | const msg2 = cache.hit(qs, 'com.'); 47 | assert(msg2); 48 | 49 | assert.bufferEqual(msg2.encode(), msg.encode()); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /test/constants-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* eslint prefer-arrow-callback: "off" */ 3 | 4 | 'use strict'; 5 | 6 | const assert = require('bsert'); 7 | const constants = require('../lib/constants'); 8 | 9 | const { 10 | opcodes, 11 | opcodeToString, 12 | stringToOpcode, 13 | isOpcodeString, 14 | 15 | codes, 16 | codeToString, 17 | stringToCode, 18 | isCodeString, 19 | 20 | types, 21 | typeToString, 22 | stringToType, 23 | isTypeString, 24 | 25 | classes, 26 | classToString, 27 | stringToClass, 28 | isClassString, 29 | 30 | algs, 31 | algToString, 32 | stringToAlg, 33 | isAlgString 34 | } = constants; 35 | 36 | describe('Constants', function() { 37 | it('should convert types', () => { 38 | assert.strictEqual(opcodeToString(opcodes.QUERY), 'QUERY'); 39 | assert.strictEqual(stringToOpcode('QUERY'), opcodes.QUERY); 40 | assert.strictEqual(stringToOpcode(`OPCODE${opcodes.QUERY}`), opcodes.QUERY); 41 | assert.strictEqual(isOpcodeString('QUERY'), true); 42 | assert.strictEqual(isOpcodeString('QUERY_'), false); 43 | 44 | assert.strictEqual(codeToString(codes.NXDOMAIN), 'NXDOMAIN'); 45 | assert.strictEqual(stringToCode('NXDOMAIN'), codes.NXDOMAIN); 46 | assert.strictEqual(stringToCode(`RCODE${codes.NXDOMAIN}`), codes.NXDOMAIN); 47 | assert.strictEqual(isCodeString('NXDOMAIN'), true); 48 | assert.strictEqual(isCodeString('NXDOMAIN_'), false); 49 | 50 | assert.strictEqual(typeToString(types.AAAA), 'AAAA'); 51 | assert.strictEqual(stringToType('AAAA'), types.AAAA); 52 | assert.strictEqual(stringToType(`TYPE${types.AAAA}`), types.AAAA); 53 | assert.strictEqual(isTypeString('AAAA'), true); 54 | assert.strictEqual(isTypeString('AAAA_'), false); 55 | 56 | assert.strictEqual(classToString(classes.IN), 'IN'); 57 | assert.strictEqual(stringToClass('IN'), classes.IN); 58 | assert.strictEqual(stringToClass(`CLASS${classes.IN}`), classes.IN); 59 | assert.strictEqual(isClassString('IN'), true); 60 | assert.strictEqual(isClassString('IN_'), false); 61 | 62 | assert.strictEqual(algToString(algs.RSASHA256), 'RSASHA256'); 63 | assert.strictEqual(stringToAlg('RSASHA256'), algs.RSASHA256); 64 | assert.strictEqual(stringToAlg(`ALG${algs.RSASHA256}`), algs.RSASHA256); 65 | assert.strictEqual(isAlgString('RSASHA256'), true); 66 | assert.strictEqual(isAlgString('RSASHA256_'), false); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /test/dane-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* eslint prefer-arrow-callback: "off" */ 3 | 4 | 'use strict'; 5 | 6 | const assert = require('bsert'); 7 | const tls = require('tls'); 8 | const dane = require('../lib/dane'); 9 | const dns = require('../lib/dns'); 10 | const tlsa = require('../lib/tlsa'); 11 | const {usages} = require('../lib/constants'); 12 | const wire = require('../lib/wire'); 13 | const {Record} = wire; 14 | 15 | function fromBase64(str) { 16 | return Buffer.from(str.replace(/\s+/g, ''), 'base64'); 17 | } 18 | 19 | describe('DANE', function() { 20 | it('should verify spki+sha256 cert (www.ietf.org)', () => { 21 | // $ dig.js _443._tcp.www.ietf.org. TLSA 22 | const str = '_443._tcp.www.ietf.org. 1500 IN TLSA' 23 | + ' 3 1 1 0C72AC70B745AC19998811B131D662C9AC69DBDBE7CB23E5B514B566' 24 | + ' 64C5D3D6'; 25 | const rr = Record.fromString(str); 26 | const rd = rr.data; 27 | 28 | // $ openssl s_client -showcerts -connect www.ietf.org:443 < /dev/null 29 | const cert = fromBase64(` 30 | MIIFUTCCBDmgAwIBAgIIITAshaEP0OswDQYJKoZIhvcNAQELBQAwgcYxCzAJBgNV 31 | BAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUw 32 | IwYDVQQKExxTdGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTMwMQYDVQQLEypo 33 | dHRwOi8vY2VydHMuc3RhcmZpZWxkdGVjaC5jb20vcmVwb3NpdG9yeS8xNDAyBgNV 34 | BAMTK1N0YXJmaWVsZCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IC0gRzIw 35 | HhcNMTcwNjEyMTAxMjAwWhcNMTgwODExMjMxMjUwWjA4MSEwHwYDVQQLExhEb21h 36 | aW4gQ29udHJvbCBWYWxpZGF0ZWQxEzARBgNVBAMMCiouaWV0Zi5vcmcwggEiMA0G 37 | CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2eMubW2zWELh8023dSdAP3LvdsNeC 38 | KhPJZhIjdxr8o1+5PJ2MVMRgCaqe4asE5R+BuYfc9FDQCamqWOBZNvd3crwfhQW8 39 | NZBM9JLbUgyObyip3X2cTkbFaKsa7SgNHOFYsd7VFntmuiEI+D/U5yzLjtBm4raV 40 | oUHSsSatFYGYRhsOXf/DF/ld+oiqk7KckHTa2FetMJxMztHPUWoIW39lVkHmEpjZ 41 | L4JN0T04hUqWvhYcx+69Rh46PToaTAsUkc2/a1T62i8jeZhHFS5jhS6mRLcwL461 42 | 7LtcqbU/4g2NZah6CbqIIC3dW6ylXP7qlTbGCXeesBUxAcHh9F5A8fSlAgMBAAGj 43 | ggHOMIIByjAMBgNVHRMBAf8EAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEF 44 | BQcDAjAOBgNVHQ8BAf8EBAMCBaAwPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2Ny 45 | bC5zdGFyZmllbGR0ZWNoLmNvbS9zZmlnMnMxLTU2LmNybDBjBgNVHSAEXDBaME4G 46 | C2CGSAGG/W4BBxcBMD8wPQYIKwYBBQUHAgEWMWh0dHA6Ly9jZXJ0aWZpY2F0ZXMu 47 | c3RhcmZpZWxkdGVjaC5jb20vcmVwb3NpdG9yeS8wCAYGZ4EMAQIBMIGGBggrBgEF 48 | BQcBAQR6MHgwKgYIKwYBBQUHMAGGHmh0dHA6Ly9vY3NwLnN0YXJmaWVsZHRlY2gu 49 | Y29tLzBKBggrBgEFBQcwAoY+aHR0cDovL2NlcnRpZmljYXRlcy5zdGFyZmllbGR0 50 | ZWNoLmNvbS9yZXBvc2l0b3J5L3NmaWcyLmNydC5kZXIwHwYDVR0jBBgwFoAUJUWB 51 | aFAmOD07LSy+zWrZtj2zZmMwHwYDVR0RBBgwFoIKKi5pZXRmLm9yZ4IIaWV0Zi5v 52 | cmcwHQYDVR0OBBYEFAb+C6vY5nRu/MRzAoX3qUh+0TRPMA0GCSqGSIb3DQEBCwUA 53 | A4IBAQDkjdd7Mz2F83bfBNjAS0uN0mGIn2Z67dcWP+klzp7JzGb+qdbPZsI0aHKZ 54 | UEh0Pl71hcn8LlhYl+n7GJUGhW7CaOVqhzHkxfyfIls6BJ+pL6mIx5be8xqSV04b 55 | zyPBZcPnuFdi/dXAgjE9iSFHfNH8gthiXgzgiIPIjQp2xuJDeQHWT5ZQ5gUxF8qP 56 | ecO5L6IwMzZFRuE6SYzFynsOMOGjsPYJkYLm3JYwUulDz7OtRABwN5wegc5tTgq5 57 | 9HaFOULLCdMakLIRmMC0PzSI+m3+cYoZ6ue/8q9my7HgekcVMYQ5lRKncrs3GMxo 58 | WNyYOpbGqBfooA8nwwE20fpacX2i 59 | `); 60 | 61 | assert(dane.verify(cert, rd.selector, rd.matchingType, rd.certificate)); 62 | assert(tlsa.verify(rr, cert, 'www.ietf.org', 'tcp', 443)); 63 | }); 64 | 65 | it('should verify spki+sha256 cert (www.huque.com)', () => { 66 | // $ dig.js _443._tcp.www.huque.com. TLSA 67 | const str = '_443._tcp.www.huque.com. 4270 IN TLSA' 68 | + ' 3 1 1 F6D8BB3FD6B09E73EBFC347F8F34E7ABB6AFB31105AE20ACEC4F1F57' 69 | + ' 63FE7FC1'; 70 | const rr = Record.fromString(str); 71 | const rd = rr.data; 72 | 73 | // $ openssl s_client -showcerts -connect www.huque.com:443 < /dev/null 74 | const cert = fromBase64(` 75 | MIIE/TCCA+WgAwIBAgISA+XMxBpZ8QPRypKtzNXcAMCuMA0GCSqGSIb3DQEBCwUA 76 | MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD 77 | ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0xODAxMTkwMDMwMjFaFw0x 78 | ODA0MTkwMDMwMjFaMBgxFjAUBgNVBAMTDXd3dy5odXF1ZS5jb20wggEiMA0GCSqG 79 | SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDiDkfG6zL1hkc64yKAY/IrzQYemhJkyl+G 80 | i8WAL6BiP18z1vleCN/HMkApddgkWCV2xEbHQC6hExxWs5h27d9BfC4bRKbXVWzH 81 | wDW1ciaMAKRMibLUSyKcUu9/C+bJRJMtfLN8+Zmsh2ftrArvkiy3VfdhZQJ7ZH9q 82 | kAqSAKn/4IVTsxwzCjNGPpjxSy1S/CTuKfoydOdoJBc6e5qPV7CQNjSdI/I/24VB 83 | 2ckhAb+X7sF88Pcy3JDfSMMnYEOaAKsaKJfr6SPsHcVdHqn43Bjqui3+m+tTQwgO 84 | wmnCWc/scJ5M5zZEWkMatcVgmSSdpLU1uEP1qoqT4TK+c8NcbrH/AgMBAAGjggIN 85 | MIICCTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUF 86 | BwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFNLptiUj9iGzhrPB/sj2CJP++46y 87 | MB8GA1UdIwQYMBaAFKhKamMEfd265tE5t6ZFZe/zqOyhMG8GCCsGAQUFBwEBBGMw 88 | YTAuBggrBgEFBQcwAYYiaHR0cDovL29jc3AuaW50LXgzLmxldHNlbmNyeXB0Lm9y 89 | ZzAvBggrBgEFBQcwAoYjaHR0cDovL2NlcnQuaW50LXgzLmxldHNlbmNyeXB0Lm9y 90 | Zy8wGAYDVR0RBBEwD4INd3d3Lmh1cXVlLmNvbTCB/gYDVR0gBIH2MIHzMAgGBmeB 91 | DAECATCB5gYLKwYBBAGC3xMBAQEwgdYwJgYIKwYBBQUHAgEWGmh0dHA6Ly9jcHMu 92 | bGV0c2VuY3J5cHQub3JnMIGrBggrBgEFBQcCAjCBngyBm1RoaXMgQ2VydGlmaWNh 93 | dGUgbWF5IG9ubHkgYmUgcmVsaWVkIHVwb24gYnkgUmVseWluZyBQYXJ0aWVzIGFu 94 | ZCBvbmx5IGluIGFjY29yZGFuY2Ugd2l0aCB0aGUgQ2VydGlmaWNhdGUgUG9saWN5 95 | IGZvdW5kIGF0IGh0dHBzOi8vbGV0c2VuY3J5cHQub3JnL3JlcG9zaXRvcnkvMA0G 96 | CSqGSIb3DQEBCwUAA4IBAQBvBaLlfwTLwVWyEbMzXS6Parm0BR0Sp+EwGT4NHXj7 97 | x/ofVThcocSS/o4cRApeB1P6gVyHazCq0fVmaYlB8LeS7lfDr/DO8mlGpNGL7rQ5 98 | rAx/ZAK28FB6N2+kVkEB6cOTa3+YfNfY+x35BO1SpHtwP9WZPCjuLQVG2nXEwoMR 99 | MK0l+JJx2fw6dzScDjzKRn8jqCzi7rrqCycHwuFwSFEd1VqJXGuLOymVg1XzcVTM 100 | FhCadti+z6LFapAgGP68HdOdv4VRLITjpe/8vmYhPZa+/G1fDjsweU5fUBiy1OWS 101 | y/6AZMy11PrBQJ1Ogz6SxgwQkARCvgkgXRTjDKUUvDpH 102 | `); 103 | 104 | assert(dane.verify(cert, rd.selector, rd.matchingType, rd.certificate)); 105 | assert(tlsa.verify(rr, cert, 'www.huque.com', 'tcp', 443)); 106 | }); 107 | 108 | describe('Live DANE with resolver', function () { 109 | if (process.browser) 110 | this.skip(); 111 | 112 | // https://www.internetsociety.org/resources/deploy360/dane-test-sites/ 113 | const hosts = [ 114 | 'jhcloos.com', 115 | 'torproject.org', 116 | 'fedoraproject.org', 117 | 'www.afnic.fr', 118 | 'good.dane.huque.com' 119 | ]; 120 | 121 | // Nodejs has its own cert store and older versions may be missing some CAs 122 | const rejectUnauthorized = false; 123 | 124 | before(() => { 125 | // ignore DNSSEC errors for this test 126 | dns._allowInsecure = true; 127 | }); 128 | 129 | after(() => { 130 | dns._allowInsecure = false; 131 | }); 132 | 133 | for (const host of hosts) { 134 | it(`should verify spki+sha256 cert (${host})`, async () => { 135 | let cert = await new Promise((resolve, reject) => { 136 | const socket = tls.connect( 137 | {port: 443, host, rejectUnauthorized, servername: host}, 138 | () => { 139 | const cert = socket.getPeerCertificate(true); 140 | socket.destroy(); 141 | resolve(cert); 142 | } 143 | ); 144 | }); 145 | 146 | const rrs = await dns.resolveTLSA(host, 'tcp', 443); 147 | 148 | assert(Array.isArray(rrs)); 149 | assert(rrs.length >= 1); 150 | 151 | let valid = false; 152 | 153 | for (const rr of rrs) { 154 | if (dns.verifyTLSA(rr, cert.raw)) { 155 | valid = true; 156 | break; 157 | } 158 | 159 | if (rr.usage === usages.CAC) { 160 | while (!cert.issuerCertificate.raw.equals(cert.raw)) { 161 | if (cert.issuerCertificate) 162 | cert = cert.issuerCertificate; 163 | else 164 | break; 165 | 166 | if (dns.verifyTLSA(rr, cert.raw)) { 167 | valid = true; 168 | break; 169 | } 170 | } 171 | } 172 | } 173 | 174 | assert(valid); 175 | }); 176 | } 177 | }); 178 | }); 179 | -------------------------------------------------------------------------------- /test/data/Knlnetlabs.nl.+003+03793.key: -------------------------------------------------------------------------------- 1 | nlnetlabs.nl. 3600 IN DNSKEY 256 3 3 BOrvuRSPx+VpsMkz1geStC43oFW9lHKDKRMhV1yBk/gXk3IL29jkPwWw OqEskebo/hC0ieobdQkeuf9B3AgzCdn2hQOWVGoIMWyxChhqHVLwnQzU GY/uAhTZgSXBG47eHZC+Pj1hgX9tkB+9kzoK5jKhstR9hWj33Fnu7b9v hIriw6nXnJKpeus9pffjSaKzVJBNnlWTMXbo3+w3rObnJlbkVLfRsY4F 8boWn1EbUUHCaRIW3bsqziE739S8HBJDDwxYx85n0xRqkg0djWoCG2e4 uv4oORFJhDQMHGQNdWXlh05vAJJ8Fqm6u+72qsIY2pnSgWL7vQIL6sKK JL14oIVJbsZW9FIjQCFpqe19leUdzUDQa9AxB8WSRAzmh4S6tWkmbAGp UjoAUJSLtqV1NgvH8ESg ; ZSK ; alg = DSA ; key id = 3793 2 | -------------------------------------------------------------------------------- /test/data/Knlnetlabs.nl.+003+03793.private: -------------------------------------------------------------------------------- 1 | Private-key-format: v1.3 2 | Algorithm: 3 (DSA) 3 | Prime(p): lHKDKRMhV1yBk/gXk3IL29jkPwWwOqEskebo/hC0ieobdQkeuf9B3AgzCdn2hQOWVGoIMWyxChhqHVLwnQzUGY/uAhTZgSXBG47eHZC+Pj1hgX9tkB+9kzoK5jKhstR9 4 | Subprime(q): 6u+5FI/H5WmwyTPWB5K0LjegVb0= 5 | Base(g): hWj33Fnu7b9vhIriw6nXnJKpeus9pffjSaKzVJBNnlWTMXbo3+w3rObnJlbkVLfRsY4F8boWn1EbUUHCaRIW3bsqziE739S8HBJDDwxYx85n0xRqkg0djWoCG2e4uv4o 6 | Private_value(x): xSLjPW1PE6twDgObqfkUk6EXO+g= 7 | Public_value(y): ORFJhDQMHGQNdWXlh05vAJJ8Fqm6u+72qsIY2pnSgWL7vQIL6sKKJL14oIVJbsZW9FIjQCFpqe19leUdzUDQa9AxB8WSRAzmh4S6tWkmbAGpUjoAUJSLtqV1NgvH8ESg 8 | Created: 20180918230905 9 | Publish: 20180918230905 10 | Activate: 20180918230905 11 | -------------------------------------------------------------------------------- /test/data/com-glue.zone: -------------------------------------------------------------------------------- 1 | a.gtld-servers.net. 172800 IN A 192.5.6.30 2 | a.gtld-servers.net. 172800 IN AAAA 2001:503:a83e::2:30 3 | b.gtld-servers.net. 172800 IN A 192.33.14.30 4 | b.gtld-servers.net. 172800 IN AAAA 2001:503:231d::2:30 5 | c.gtld-servers.net. 172800 IN A 192.26.92.30 6 | c.gtld-servers.net. 172800 IN AAAA 2001:503:83eb::30 7 | d.gtld-servers.net. 172800 IN A 192.31.80.30 8 | d.gtld-servers.net. 172800 IN AAAA 2001:500:856e::30 9 | e.gtld-servers.net. 172800 IN A 192.12.94.30 10 | e.gtld-servers.net. 172800 IN AAAA 2001:502:1ca1::30 11 | f.gtld-servers.net. 172800 IN A 192.35.51.30 12 | f.gtld-servers.net. 172800 IN AAAA 2001:503:d414::30 13 | g.gtld-servers.net. 172800 IN A 192.42.93.30 14 | g.gtld-servers.net. 172800 IN AAAA 2001:503:eea3::30 15 | h.gtld-servers.net. 172800 IN A 192.54.112.30 16 | h.gtld-servers.net. 172800 IN AAAA 2001:502:8cc::30 17 | i.gtld-servers.net. 172800 IN A 192.43.172.30 18 | i.gtld-servers.net. 172800 IN AAAA 2001:503:39c1::30 19 | j.gtld-servers.net. 172800 IN A 192.48.79.30 20 | j.gtld-servers.net. 172800 IN AAAA 2001:502:7094::30 21 | k.gtld-servers.net. 172800 IN A 192.52.178.30 22 | k.gtld-servers.net. 172800 IN AAAA 2001:503:d2d::30 23 | l.gtld-servers.net. 172800 IN A 192.41.162.30 24 | l.gtld-servers.net. 172800 IN AAAA 2001:500:d937::30 25 | m.gtld-servers.net. 172800 IN A 192.55.83.30 26 | m.gtld-servers.net. 172800 IN AAAA 2001:501:b1f9::30 27 | -------------------------------------------------------------------------------- /test/data/com-response.zone: -------------------------------------------------------------------------------- 1 | com. 172800 IN NS a.gtld-servers.net. 2 | com. 172800 IN NS b.gtld-servers.net. 3 | com. 172800 IN NS c.gtld-servers.net. 4 | com. 172800 IN NS d.gtld-servers.net. 5 | com. 172800 IN NS e.gtld-servers.net. 6 | com. 172800 IN NS f.gtld-servers.net. 7 | com. 172800 IN NS g.gtld-servers.net. 8 | com. 172800 IN NS h.gtld-servers.net. 9 | com. 172800 IN NS i.gtld-servers.net. 10 | com. 172800 IN NS j.gtld-servers.net. 11 | com. 172800 IN NS k.gtld-servers.net. 12 | com. 172800 IN NS l.gtld-servers.net. 13 | com. 172800 IN NS m.gtld-servers.net. 14 | com. 86400 IN DS 30909 8 2 E2D3C916F6DEEAC73294E8268FB5885044A833FC5459588F4A9184CF C41A5766 ; alg = RSASHA256 ; hash = SHA256 15 | com. 86400 IN RRSIG DS 8 1 86400 20180815050000 20180802040000 41656 . 0rCZPgC5uy59E8NFIthfPIfD0h0FgIZ7hgOJ3utHZT1FGORI7nkQrtAx MM0y82ZLtjc6mnsC+AJCSVFl1z+t0XI4WGXlMGjfDZujaAe32T+pso0y /nsbqKdeildsye+SLnHL1Ns4c27tWzR5liCKQji6VsN1+ztkZDypZ2M/ EIQRJYHWk9h/3bbJPNwEreSOJZfDfIDbRaRWLbU8MoVUTSXF6IyMruzf mkuo7saOqudtMGGHJcFIaRbwTdXhUQwQQO6u83vDUeXQoh6l7xovGYir l2hdi9zie5HL+S77K2ZZhs7VR+jZ6tS4E+SfjqOuaAb/rJo+qJMQtSLQ m21acw== ; alg = RSASHA256 16 | -------------------------------------------------------------------------------- /test/data/dnssec-verify-2.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "sig": "096e6c6e65746c616273026e6c00002e0001000027d8004900060302000027d8482c3c324804af320ed1096e6c6e65746c616273026e6c0004732dd5e58df07cdf14eaaba8bd4faec103e372550abc9b0d82f51895f298a9645910236abd304faa", 4 | "key": "096e6c6e65746c616273026e6c000030000100000e1001390100030304eaefb9148fc7e569b0c933d60792b42e37a055bd947283291321575c8193f81793720bdbd8e43f05b03aa12c91e6e8fe10b489ea1b75091eb9ff41dc083309d9f6850396546a08316cb10a186a1d52f09d0cd4198fee0214d98125c11b8ede1d90be3e3d61817f6d901fbd933a0ae632a1b2d47d8568f7dc59eeedbf6f848ae2c3a9d79c92a97aeb3da5f7e349a2b354904d9e55933176e8dfec37ace6e72656e454b7d1b18e05f1ba169f511b5141c2691216ddbb2ace213bdfd4bc1c12430f0c58c7ce67d3146a920d1d8d6a021b67b8bafe2839114984340c1c640d7565e5874e6f00927c16a9babbeef6aac218da99d28162fbbd020beac28a24bd78a085496ec656f45223402169a9ed7d95e51dcd40d06bd03107c592440ce68784bab569266c01a9523a0050948bb6a575360bc7f044a0", 5 | "rrset": [ 6 | "096e6c6e65746c616273026e6c0000060001000027d80040046f70656e096e6c6e65746c616273026e6c000a686f73746d6173746572096e6c6e65746c616273026e6c0077b042a40000708000001c2000093a8000000e10" 7 | ], 8 | "result": true 9 | }, 10 | { 11 | "sig": "096e6c6e65746c616273026e6c00002e0001000027d8004900020302000027d8482c3c324804af320ed1096e6c6e65746c616273026e6c0004965a4e182405a17793ab76abeb6bd2780a705d849ed48e9fd81752df578a98f709d8a8665f1bd1c6", 12 | "key": "096e6c6e65746c616273026e6c000030000100000e1001390100030304eaefb9148fc7e569b0c933d60792b42e37a055bd947283291321575c8193f81793720bdbd8e43f05b03aa12c91e6e8fe10b489ea1b75091eb9ff41dc083309d9f6850396546a08316cb10a186a1d52f09d0cd4198fee0214d98125c11b8ede1d90be3e3d61817f6d901fbd933a0ae632a1b2d47d8568f7dc59eeedbf6f848ae2c3a9d79c92a97aeb3da5f7e349a2b354904d9e55933176e8dfec37ace6e72656e454b7d1b18e05f1ba169f511b5141c2691216ddbb2ace213bdfd4bc1c12430f0c58c7ce67d3146a920d1d8d6a021b67b8bafe2839114984340c1c640d7565e5874e6f00927c16a9babbeef6aac218da99d28162fbbd020beac28a24bd78a085496ec656f45223402169a9ed7d95e51dcd40d06bd03107c592440ce68784bab569266c01a9523a0050948bb6a575360bc7f044a0", 13 | "rrset": [ 14 | "096e6c6e65746c616273026e6c0000020001000027d80011056f6d76616c067465646e6574026e6c00", 15 | "096e6c6e65746c616273026e6c0000020001000027d80018036e73370f646f6d61696e2d7265676973747279026e6c00", 16 | "096e6c6e65746c616273026e6c0000020001000027d80013046f70656e096e6c6e65746c616273026e6c00" 17 | ], 18 | "result": true 19 | } 20 | ] 21 | -------------------------------------------------------------------------------- /test/data/dnssec-verify-4.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "sig": "03777777076578616d706c6503636f6d00002e000100000e10005f00010f0300000e1059523524592d4b24df3b076578616d706c6503636f6d0075a606eb3649dc127018e4b83c2d2d0e7c6cb15358a1e9ce1e872821fc7419e5cd90a1d2c9717e5c7803e4b29b1b764dd1d649ff84e8e5e9e2f505ce5e247808", 4 | "key": "076578616d706c6503636f6d000030000100000e1000240100030ffac66773c1c823ac7103d2258b96dba22287d08a6ffc0a756ae708b7f0951763", 5 | "rrset": [ 6 | "03777777076578616d706c6503636f6d000001000100000e1000040a000001" 7 | ], 8 | "result": true 9 | } 10 | ] 11 | -------------------------------------------------------------------------------- /test/data/dsa-test.zone: -------------------------------------------------------------------------------- 1 | ; Borrowed from: 2 | ; https://github.com/NLnetLabs/unbound/blob/23f475b/testdata/test_signatures.4 3 | 4 | ; Private-key-format: v1.2 5 | ; Algorithm: 3 (DSA) 6 | ; Prime(p): lHKDKRMhV1yBk/gXk3IL29jkPwWwOqEskebo/hC0ieobdQkeuf9B3AgzCdn2hQOWVGoIMWyxChhqHVLwnQzUGY/uAhTZgSXBG47eHZC+Pj1hgX9tkB+9kzoK5jKhstR9 7 | ; Subprime(q): 6u+5FI/H5WmwyTPWB5K0LjegVb0= 8 | ; Base(g): hWj33Fnu7b9vhIriw6nXnJKpeus9pffjSaKzVJBNnlWTMXbo3+w3rObnJlbkVLfRsY4F8boWn1EbUUHCaRIW3bsqziE739S8HBJDDwxYx85n0xRqkg0djWoCG2e4uv4o 9 | ; Private_value(x): xSLjPW1PE6twDgObqfkUk6EXO+g= 10 | ; Public_value(y): ORFJhDQMHGQNdWXlh05vAJJ8Fqm6u+72qsIY2pnSgWL7vQIL6sKKJL14oIVJbsZW9FIjQCFpqe19leUdzUDQa9AxB8WSRAzmh4S6tWkmbAGpUjoAUJSLtqV1NgvH8ESg 11 | 12 | nlnetlabs.nl. IN DNSKEY 256 3 3 BOrvuRSPx+VpsMkz1geStC43oFW9lHKDKRMhV1yBk/gXk3IL29jkPwWw OqEskebo/hC0ieobdQkeuf9B3AgzCdn2hQOWVGoIMWyxChhqHVLwnQzU GY/uAhTZgSXBG47eHZC+Pj1hgX9tkB+9kzoK5jKhstR9hWj33Fnu7b9v hIriw6nXnJKpeus9pffjSaKzVJBNnlWTMXbo3+w3rObnJlbkVLfRsY4F 8boWn1EbUUHCaRIW3bsqziE739S8HBJDDwxYx85n0xRqkg0djWoCG2e4 uv4oORFJhDQMHGQNdWXlh05vAJJ8Fqm6u+72qsIY2pnSgWL7vQIL6sKK JL14oIVJbsZW9FIjQCFpqe19leUdzUDQa9AxB8WSRAzmh4S6tWkmbAGp UjoAUJSLtqV1NgvH8ESg 13 | 14 | nlnetlabs.nl. 10200 IN SOA open.nlnetlabs.nl. hostmaster.nlnetlabs.nl. ( 2008040100 28800 7200 604800 3600 ) 15 | nlnetlabs.nl. 10200 RRSIG SOA 3 2 10200 20080515133546 ( 20080415133546 3793 nlnetlabs.nl. BHMt1eWN8HzfFOqrqL1PrsED43JVCrybDYL1 GJXymKlkWRAjar0wT6o= ) 16 | 17 | nlnetlabs.nl. 10200 NS omval.tednet.nl. 18 | nlnetlabs.nl. 10200 NS ns7.domain-registry.nl. 19 | nlnetlabs.nl. 10200 NS open.nlnetlabs.nl. 20 | nlnetlabs.nl. 10200 RRSIG NS 3 2 10200 20080515133546 ( 20080415133546 3793 nlnetlabs.nl. BJZaThgkBaF3k6t2q+tr0ngKcF2EntSOn9gX Ut9Xipj3CdioZl8b0cY= ) 21 | -------------------------------------------------------------------------------- /test/data/ed25519-test.zone: -------------------------------------------------------------------------------- 1 | ; Borrowed from: 2 | ; https://github.com/NLnetLabs/unbound/blob/007123e/testdata/test_sigs.ed25519 3 | 4 | example.com. 3600 IN DNSKEY 256 3 15 +sZnc8HII6xxA9Ili5bboiKH0Ipv/Ap1aucIt/CVF2M= ;{id = 57147 (zsk), size = 256b} 5 | 6 | www.example.com. 3600 IN A 10.0.0.1 7 | 8 | www.example.com. 3600 IN RRSIG A 15 3 3600 20170627103620 20170530103620 57147 example.com. daYG6zZJ3BJwGOS4PC0tDnxssVNYoenOHocoIfx0GeXNkKHSyXF+XHgD5LKbG3ZN0dZJ/4To5eni9QXOXiR4CA== 9 | -------------------------------------------------------------------------------- /test/data/ed448-test.zone: -------------------------------------------------------------------------------- 1 | ed448.nl. 1800 IN DNSKEY 257 3 16 8pYFjTum61L0k+q8HiIkEPipTTbuYnZceMVTJvqMZbhZdwzpkiYHRBHc VxmnOp1RJsGyt6I0myOA ; KSK ; alg = ED448 ; key id = 24480 2 | ed448.nl. 3600 IN A 195.191.112.47 3 | ed448.nl. 3600 IN RRSIG A 16 2 3600 20181206000000 20181115000000 24480 ed448.nl. +a79rRNW0n/vRFX6f9Sk1vIPjJCCbDDSEPyxJERhRqMeiVHEVVzei0Vm VRiUp/N1M4JK6y98VGQANu9pAyIXGbNU1zKvZyiVACGJjp6aArC6bVE0 MT9dklE6EZyO/GyoBZMb19wSpAzYHwGgkAchSxkA ; alg = ED448 4 | -------------------------------------------------------------------------------- /test/data/nx-response.zone: -------------------------------------------------------------------------------- 1 | . 86400 IN SOA a.root-servers.net. nstld.verisign-grs.com. ( 2 | 2018080200 1800 900 604800 86400 3 | ) 4 | 5 | . 86400 IN RRSIG SOA 8 0 86400 20180815050000 20180802040000 41656 . ( 6 | X/yeZjlX2H6BugnNCekXYRXSNkzq8zW7XKfRyBq0F9Z0aZ+BGcUNSRWG 7 | rrHXDWfcTSDTBlWq0Vq7Bec5ZOvDwRm1anCWhG0wejliC3rxhCK4O+Eg 8 | LelKscLA99K3jaKL3CKRRVitk08IRGxHCX725kk+GAR3/gWQnhXmO3DM 9 | vmC5DVWCMCa3Jywnij4CsoaNqMczm/KKztk/i/lRlw0h+nVND73fgRMc 10 | 0NDXkv/oJJo9zzk877nfvS1B0fNwmgwRjA6Luj753u5VDYbpxDjUxXXn 11 | eklu1LBO0SMvCk2opUvB5ADJ5JCYRvmB4Rll42vaB6gUbuJOoOTnY/tU 12 | KgV9gg== 13 | ) 14 | 15 | id. 86400 IN NSEC ie. NS DS RRSIG NSEC 16 | 17 | id. 86400 IN RRSIG NSEC 8 1 86400 20180815050000 20180802040000 41656 . ( 18 | TkoEX0Eb9ObbVUvZ7CzCTIOSg6dF/IQMWwUFOyXxL2jwZiEGOpMw6YDY 19 | yGl1rl5SD3zXd3/Gs0XICu4DA7E3PALCWttwRC5K47qBqx5RgfL53rT9 20 | r0wINeuf0hhtYGJKvOxXOxqnzrop48xWbpFBu/ftA1CeRsNxqqyWbGzQ 21 | QFoArL+kdbFbivyUDFWHXBdwZ8t7iN1APhHf9R0ZNR2CRMqeTw4C/Bls 22 | aF26wviT+6TkkQBcLYPlUnZWj+R1eJjA5hlUvvjY53x9EYapIpr+qf49 23 | QyUq/H3QtdNrrU+pNcbxuJby0jB+txvrAQfWXJ0hXYqHUnMqfQIny/gN 24 | ihwlkA== 25 | ) 26 | 27 | . 86400 IN NSEC aaa. NS SOA RRSIG NSEC DNSKEY 28 | 29 | . 86400 IN RRSIG NSEC 8 0 86400 20180815050000 20180802040000 41656 . ( 30 | gyyjLKjueKD4ho7bMZJ5Vvlxf7y0sDz9uzHCV4w06zNtCzMNkrkjKYR+ 31 | z0UsoNBHaSSKU1HfIVZCr7VDnrT9V68CAG1Ry4qXJZiNudmXNVkNhMJw 32 | fBEIhiTiQpW8XxdRuaQz1aPSmI4uViiJ2mxjoBysSqJY3wrjK5sa/7dL 33 | T+LEdEBchPDQPQqLFCAfkjgaCXIn8iqtegqSbrjhMXkSq3E43Gw5YHnE 34 | rw+dgI4osARUMP1MdsWUH9CAsa0hXsXA/MJUgr2RYmdLdghZHPZPiCwf 35 | cGS7GqyJ2LHm+5twVDcsVnQzRDwoaoFG6i49bq75/qAWB1gmKs0kzd6I 36 | 0kyi7A== 37 | ) 38 | -------------------------------------------------------------------------------- /test/data/openpgpkey.zone: -------------------------------------------------------------------------------- 1 | d08ee310438ca124a6149ea5cc21b6313b390dce485576eff96f8722._openpgpkey.fedoraproject.org. 86400 IN OPENPGPKEY mQINBFBHPMsBEACeInGYJCb+7TurKfb6wGyTottCDtiSJB310i37/6ZY oeIay/5soJjlMyfMFQ9T2XNT/0LM6gTa0MpC1st9LnzYTMsT6tzRly1D 1UbVI6xw0g0vE5y2Cjk3xUwAynCsSsgg5KrjdYWRqLSTZ3zEABm/gNg6 OgA5l6QU+geXcQ9+P285WoUuj0j7HN6T217Bd+RcVxNWOMxsqx+b0rjW a8db1KiwM95wddCwzMPB2S/6IswD1P8nVfGnkgp7pfoTyMuDkVU6hmO5 RHq9M26eNoQ4sJZuXe5YjODnjgxkKKilFLY8hUkjwa1VPrx4QnTwzIn1 6JlUO03At9tpe+9SnShDV0cBlHxo3DhnHmCPWJ0HquLGpdDVi8d9tn0n lit96z9Svb9ii6Uq/J8zR1Bp+hxCMN/ON1c4U+cf1jfADPO5c3KV89y5 wvvQvzjTjuzVolR4ZZmkNSql+4vspo94JrssymEv9WWiMJyOjN50QhLb gmWiuzYjodZiL0CTB4MAC+hTrDZrZfyAnbAttBLfNWd/jcdK+AGVRXtq U997sZPzj8z3b7v2N5YJqgm2aQTiDehtHtHDJ8rKh7kcsssnhzzoZluT Kl96JHgllFWUC6sedAFVxHDmb7cxb+Sr0krwbt22is+41gPCuoz1MRKw QYQPTYgcCzX/PzyOHj6KEYZCIQARAQABtDBaYmlnbmlldyBKxJlkcnpl amV3c2tpLVN6bWVrIDx6YnlzemVrQGluLndhdy5wbD6JAjgEEwECACIF AlBHPMsCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEMVMozbP 61V+T80QAIHvIeDdKKrqiN98ob+aNe4McpqXWgiLoDMWaxQ7R8K+2Uia HMn2J6abhKidvUr7mnixkyBZaRxi1JiT8uzX4HTQ3B/UVJgf2QiRHRvZ pdSVn7O7OF0u4SqH6BEw5UaA30hMWtgz7m6aXSAN1aitd4efgKjBYKtf sHJ63HhFrpJyIyOGg0eLGObXJxjW04cUbzPoCoNuFcpphzW3WhdaJ5PX blfjNxWxKzwvAzRhevDjrrKU4jARNAIkLUMi4gUoC+7th6ATGWjYV8iO vju1cLExn8ktYMZl+BhbkYiRMddMZaZ/nY2T2SqQ8kkEXJyY6SNtd/BW uCPyt0RlTgPSK4SK9JGArVJ/PSXJrn53JUl1MUc4/75JE2KEBPkN4jQp eshlPfm0mzo/+opyi6iyVnPTZT7m7r9P7Vxc18J+IlPdfl0ws0YPnR+0 oUvo370zoNqqhJ9aNU+5d4VCPUHVIvEWEF3MHuXHKq0mnnI/4jJMvZn3 0+okZZfYABYXkMZVTA0XprkIxZm38X9s/uw886xvMqPh8fhqpVdTHD5/ 2h8ahkMMG1zKs6W6gCfM7gYh+BT37Ce1szo/7RHtbvYq5BTwcWXhpSKz ywluRe6rYUPJ0MCZ17Jk6AXStD1aRYS6rCykryRL0OvMz/4Gd8f+dcQj g5Si23mAj8VJtyrX1MaXuQINBFBHPMsBEACtDR2e2G4uL/MKtDsJu3cw QFlK9kmGEX4UqePBc29xn1BTfU6o1Y4pAXRoLrGvXuVruOwznNdkpjF8 kb1BpO/li8qNU6LKyv2n9Hyg0bxRQA24TVC4bF4mfdqaGGYLqxe3iXI/ TRmhsmiSg7WoEWxj0NEaEjydTAieT4kz2ASCYtnzhGM8iS2Te+scUXYc GNyE2nPjiacJGiLeKiOj21+j6sICTrKX8TAcXSU7btPEy2IIocxBoxZe Ofp0rNw4293cLVu0kEasB4h43lE1Uax7JYX1q9LC4TkqLaLDa3YyDGvK 2FOPNNIrsKcoYG6Y43DcmaSPZCJ1ApVvoxPct7UI+EYy9VBu3wwY0obR adweXSNgscZZNvExZgdjRXJypv8A9A+nvc2xBMWw/9fAlHzrpjds+3Z2 RxbGC4Qav/sdP0WqQZ8xo5U2YPxBSHwWCjSxvQWcoDLLOgMlB05oheR2 g1VDk4QA+AXDwmxurpvJLh/fyX3mi7nPVUynTLV/UeWaXbZneh+mT3Lc 1ZVYnntSoZv7aYQqnA+a2ajm08lVMmpb5v8L7ZiadvW2xptVATlWI1De BTyNwZYyx7GuUsfFTSyQJixtjuWim0acpqNUp8z6TgXj02HtRfk9Swzv BCgJT5mWoGlSu04FR/0pn5ReVCM8RSb6/HOROnrfswGeGQARAQABiQIf BBgBAgAJBQJQRzzLAhsMAAoJEMVMozbP61V+qg8P/1BuLn6+bVgDdye3 7GV4kXSVxB5SQZj8ElwTj+daWq8ZEIoZ0ySyRz2uC7Haeh5XulF1hj13 AYfM4Ary9Whx9hCQ98D4+JK5eiagBuSpIApCkQk+jj44q7VKLanyZV0k WRNBSfr0TnE6GoBSL1gTjpsqt/mUR2R5zgCE59Ex4HHBwvosIcXgGopb PGNtX9S4Rm7f2wWOSdXGc6pfnuFsVtkbk8z+uITyK3WX+jHiW5JRgyHW aFyYqwDjds8q0LkmIL80scPa3sEl9QzfT7+7xqcviKfemg6XgCwYmSOh PHSK/E6MIC6Wb4QY6H3ixCuMfaic6AsB4sH4vFPoPnJWmIGmQlU3L1UJ z4VNvzCaClaxWPa5nZZAWyFRMof4VtO2Q1LTZa6NQbGNFRRLPDBXpcOq CNicjZjSaHO9Zxp/V+9W9GgH6u7i/eAnxifwUFvN0BfkwbDnp4BNyvyA +fpZ4oPWInygfP1P/fvALssBvJjo/q6eZ4b5O11Ut/13JzO4IYNISK8u Knt5AbU9YUnSKClg1MtTRlBCD3v+UYy102F7p8rJnVTHelfgmjP9UPhP 7AUwZ0UQYq9QypNeoRvR4GjL/3Yz53yHFeYaN/lBglm4jNQOmHTQSibv z8lx8ALGbLxTaUr8j+UG4Gu2z3tFpYo0NHq9Ahd8L7JVIsbKtcoP 2 | 3 | d08ee310438ca124a6149ea5cc21b6313b390dce485576eff96f8722._openpgpkey.fedoraproject.org. 86400 IN RRSIG OPENPGPKEY 8 4 86400 20181011154836 20180911154836 57393 _openpgpkey.fedoraproject.org. LUNQJvIKbXLJBdLtoTdSyaE7d1t8Z77C8L92Osv2xic2A0joUUDaLzOZ OMwPmvPLGKNttrSfRYsJwgKKv8S/wRqozsc3AFB2Ajl4RKN9QLGeot0Q szlugGPwJfv7XffReKU3de+e8QSp4N8wi2isglnXf3oA0rWYLXsQJHDi 7cM= ; alg = RSASHA256 4 | 5 | _openpgpkey.fedoraproject.org. 86400 IN DNSKEY 256 3 8 AwEAAb8dZXiSuAbPIeORXxww5EpZskSZ51Oa/PVXITRe0TEa2TPrTHvC YqSPueyu5wRkvb4h3SmGEnxuTSHt6QWXuzClVZu2RlFXox6TeA5i2ROZ /x23ZbhzltVsJT/az9dfRQfqEm6i/R5KynmqiNSkHjNx4ZHxIHVQMxQy NnX1Rtaj ; ZSK ; alg = RSASHA256 ; bits = 1024,17 ; key id = 57393 6 | -------------------------------------------------------------------------------- /test/data/server-records.json: -------------------------------------------------------------------------------- 1 | { 2 | "ANY": [ 3 | "google.com. 300 IN A 216.58.195.78", 4 | "google.com. 300 IN AAAA 2607:f8b0:4005:807::200e", 5 | "google.com. 3600 IN TXT \"v=spf1 include:_spf.google.com ~all\"", 6 | "google.com. 600 IN MX 10 aspmx.l.google.com.", 7 | "google.com. 600 IN MX 30 alt2.aspmx.l.google.com.", 8 | "google.com. 86400 IN CAA 0 issue \"pki.goog\"", 9 | "google.com. 60 IN SOA ns1.google.com. dns-admin.google.com. 213603989 900 900 1800 60", 10 | "google.com. 3600 IN TXT \"facebook-domain-verification=22rm551cu4k0ab0bxsw536tlds4h95\"", 11 | "google.com. 600 IN MX 50 alt4.aspmx.l.google.com.", 12 | "google.com. 345600 IN NS ns2.google.com.", 13 | "google.com. 345600 IN NS ns4.google.com.", 14 | "google.com. 345600 IN NS ns3.google.com.", 15 | "google.com. 600 IN MX 40 alt3.aspmx.l.google.com.", 16 | "google.com. 300 IN TXT \"docusign=05958488-4752-4ef2-95eb-aa7ba8a3bd0e\"", 17 | "google.com. 345600 IN NS ns1.google.com.", 18 | "google.com. 600 IN MX 20 alt1.aspmx.l.google.com." 19 | ], 20 | "A": [ 21 | "icanhazip.com. 300 IN A 147.75.40.2" 22 | ], 23 | "AAAA": [ 24 | "icanhazip.com. 300 IN AAAA 2604:1380:1000:af00::1", 25 | "icanhazip.com. 300 IN AAAA 2604:1380:3000:3b00::1", 26 | "icanhazip.com. 300 IN AAAA 2604:1380:1:cd00::1" 27 | ], 28 | "CNAME": [ 29 | "mail.google.com. 604800 IN CNAME googlemail.l.google.com." 30 | ], 31 | "MX": [ 32 | "google.com. 600 IN MX 40 alt3.aspmx.l.google.com.", 33 | "google.com. 600 IN MX 20 alt1.aspmx.l.google.com.", 34 | "google.com. 600 IN MX 50 alt4.aspmx.l.google.com.", 35 | "google.com. 600 IN MX 30 alt2.aspmx.l.google.com.", 36 | "google.com. 600 IN MX 10 aspmx.l.google.com." 37 | ], 38 | "NAPTR": [ 39 | "apple.com. 86400 IN NAPTR 50 50 \"se\" \"SIPS+D2T\" \"\" _sips._tcp.apple.com.", 40 | "apple.com. 86400 IN NAPTR 90 50 \"se\" \"SIP+D2T\" \"\" _sip._tcp.apple.com.", 41 | "apple.com. 86400 IN NAPTR 100 50 \"se\" \"SIP+D2U\" \"\" _sip._udp.apple.com." 42 | ], 43 | "PTR": [ 44 | "46.0.217.172.in-addr.arpa. 86400 IN PTR lga15s43-in-f46.1e100.net.", 45 | "46.0.217.172.in-addr.arpa. 86400 IN PTR sfo07s26-in-f14.1e100.net.", 46 | "46.0.217.172.in-addr.arpa. 86400 IN PTR lga15s43-in-f14.1e100.net.", 47 | "46.0.217.172.in-addr.arpa. 86400 IN PTR lga15s43-in-f46.1e100.net.", 48 | "46.0.217.172.in-addr.arpa. 86400 IN PTR sfo07s26-in-f14.1e100.net.", 49 | "46.0.217.172.in-addr.arpa. 86400 IN PTR lga15s43-in-f14.1e100.net." 50 | ], 51 | "SOA": [ 52 | "google.com. 60 IN SOA ns1.google.com. dns-admin.google.com. 213603989 900 900 1800 60" 53 | ], 54 | "SRV": [ 55 | "_xmpp-server._tcp.gmail.com. 900 IN SRV 20 0 5269 alt4.xmpp-server.l.google.com.", 56 | "_xmpp-server._tcp.gmail.com. 900 IN SRV 20 0 5269 alt3.xmpp-server.l.google.com.", 57 | "_xmpp-server._tcp.gmail.com. 900 IN SRV 5 0 5269 xmpp-server.l.google.com.", 58 | "_xmpp-server._tcp.gmail.com. 900 IN SRV 20 0 5269 alt1.xmpp-server.l.google.com.", 59 | "_xmpp-server._tcp.gmail.com. 900 IN SRV 20 0 5269 alt2.xmpp-server.l.google.com." 60 | ], 61 | "TXT": [ 62 | "google.com. 3600 IN TXT \"facebook-domain-verification=22rm551cu4k0ab0bxsw536tlds4h95\"", 63 | "google.com. 300 IN TXT \"docusign=05958488-4752-4ef2-95eb-aa7ba8a3bd0e\"", 64 | "google.com. 3600 IN TXT \"v=spf1 include:_spf.google.com ~all\"" 65 | ] 66 | } 67 | -------------------------------------------------------------------------------- /test/dnssec-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* eslint prefer-arrow-callback: "off" */ 3 | 4 | 'use strict'; 5 | 6 | const assert = require('bsert'); 7 | const Path = require('path'); 8 | const fs = require('bfile'); 9 | const dnssec = require('../lib/dnssec'); 10 | const wire = require('../lib/wire'); 11 | const vectors1 = require('./data/dnssec-verify-1.json'); 12 | const vectors2 = require('./data/dnssec-verify-2.json'); 13 | const vectors3 = require('./data/dnssec-verify-3.json'); 14 | const vectors4 = require('./data/dnssec-verify-4.json'); 15 | const vectors5 = require('./data/dnssec-verify-5.json'); 16 | const {algs, keyFlags} = dnssec; 17 | const {Record} = wire; 18 | 19 | const DSA_TEST = Path.resolve(__dirname, 'data', 'dsa-test.zone'); 20 | const ED25519_TEST = Path.resolve(__dirname, 'data', 'ed25519-test.zone'); 21 | const ED448_TEST = Path.resolve(__dirname, 'data', 'ed448-test.zone'); 22 | const KEY_DIR = Path.resolve(__dirname, 'data'); 23 | 24 | describe('DNSSEC', function() { 25 | this.timeout(40000); 26 | 27 | for (const vectors of [vectors1, vectors2, vectors3, vectors4, vectors5]) { 28 | it(`should parse and verify ${vectors.length} signatures`, () => { 29 | for (const vector of vectors) { 30 | const sig = Record.fromHex(vector.sig); 31 | const key = Record.fromHex(vector.key); 32 | const rrset = vector.rrset.map(hex => Record.fromHex(hex)); 33 | const result = vector.result; 34 | 35 | assert.strictEqual(dnssec.verify(sig, key, rrset), result); 36 | } 37 | }); 38 | } 39 | 40 | { 41 | const str = fs.readFileSync(ED25519_TEST, 'utf8'); 42 | const parts = str.split('\n\n'); 43 | const keyText = parts[1].trim(); 44 | const rrText = parts[2].trim(); 45 | const sigText = parts[3].trim(); 46 | 47 | it('should parse and verify ED25519 signature', () => { 48 | const key = Record.fromString(keyText); 49 | const rr = Record.fromString(rrText); 50 | const sig = Record.fromString(sigText); 51 | 52 | assert.strictEqual(dnssec.verify(sig, key, [rr]), true); 53 | }); 54 | } 55 | 56 | { 57 | const str = fs.readFileSync(DSA_TEST, 'utf8'); 58 | const parts = str.split('\n\n'); 59 | const dsaPriv = parts[1].replace(/^; /gm, '').trim(); 60 | const dsaPub = parts[2].trim(); 61 | const rrset1 = parts[3].trim(); 62 | const rrset2 = parts[4].trim(); 63 | 64 | it('should parse DSA private key', async () => { 65 | const [alg, priv] = dnssec.decodePrivate(dsaPriv); 66 | 67 | assert.strictEqual(alg, algs.DSA); 68 | assert(Buffer.isBuffer(priv)); 69 | 70 | const key = Record.fromString(dsaPub); 71 | const ds = dnssec.createDS(key); 72 | 73 | dnssec.writeKeys(KEY_DIR, key, priv, 1537312145); 74 | 75 | const key2 = dnssec.readPublic(KEY_DIR, ds); 76 | 77 | assert.bufferEqual(key.encode(), key2.encode()); 78 | 79 | await dnssec.writeKeysAsync(KEY_DIR, key, priv, 1537312145); 80 | 81 | const key3 = await dnssec.readPublicAsync(KEY_DIR, ds); 82 | 83 | assert.bufferEqual(key.encode(), key3.encode()); 84 | }); 85 | 86 | it('should create DSA private key and read public key', async () => { 87 | const [alg, priv] = dnssec.decodePrivate(dsaPriv); 88 | 89 | assert.strictEqual(alg, algs.DSA); 90 | assert(Buffer.isBuffer(priv)); 91 | 92 | const key = dnssec.makeKey('nlnetlabs.nl.', alg, priv, keyFlags.ZSK); 93 | key.ttl = 3600; 94 | 95 | const ds = dnssec.createDS(key); 96 | const key2 = dnssec.readPublic(KEY_DIR, ds); 97 | 98 | assert.bufferEqual(key.encode(), key2.encode()); 99 | assert.bufferEqual(key.encode(), Record.fromString(dsaPub).encode()); 100 | 101 | const key3 = await dnssec.readPublicAsync(KEY_DIR, ds); 102 | assert.bufferEqual(key.encode(), key3.encode()); 103 | }); 104 | 105 | it('should read DSA private key', async () => { 106 | const [alg, priv] = dnssec.decodePrivate(dsaPriv); 107 | 108 | assert.strictEqual(alg, algs.DSA); 109 | assert(Buffer.isBuffer(priv)); 110 | 111 | const key = Record.fromString(dsaPub); 112 | const priv2 = dnssec.readPrivate(KEY_DIR, key); 113 | 114 | assert.bufferEqual(priv, priv2); 115 | 116 | const priv3 = await dnssec.readPrivateAsync(KEY_DIR, key); 117 | 118 | assert.bufferEqual(priv, priv3); 119 | }); 120 | 121 | it('should verify DSA signature (1)', () => { 122 | const key = Record.fromString(dsaPub); 123 | const rrset = wire.fromZone(rrset1); 124 | const sig = rrset.pop(); 125 | 126 | assert.strictEqual(dnssec.verify(sig, key, rrset), true); 127 | }); 128 | 129 | it('should verify DSA signature (2)', () => { 130 | const key = Record.fromString(dsaPub); 131 | const rrset = wire.fromZone(rrset2); 132 | const sig = rrset.pop(); 133 | 134 | assert.strictEqual(dnssec.verify(sig, key, rrset), true); 135 | }); 136 | } 137 | 138 | for (const alg of [ 139 | algs.RSAMD5, 140 | algs.DSA, 141 | algs.RSASHA1, 142 | algs.DSANSEC3SHA1, 143 | algs.RSASHA1NSEC3SHA1, 144 | algs.RSASHA256, 145 | algs.RSASHA512, 146 | algs.ECDSAP256SHA256, 147 | algs.ECDSAP384SHA384, 148 | algs.ED25519 149 | ]) { 150 | let bits = 2048; 151 | 152 | if (alg === algs.DSA || alg === algs.DSANSEC3SHA1) 153 | bits = 1024; 154 | 155 | it(`should generate key and sign (${wire.algToString(alg)})`, async () => { 156 | const priv = await dnssec.createPrivateAsync(alg, bits); 157 | const key = dnssec.makeKey('example.com.', alg, priv, keyFlags.ZSK); 158 | 159 | assert.bufferEqual( 160 | dnssec.decodePrivate(dnssec.encodePrivate(alg, priv))[1], 161 | priv); 162 | 163 | const rr = new wire.Record(); 164 | const rd = new wire.TXTRecord(); 165 | 166 | rr.name = 'example.com.'; 167 | rr.type = wire.types.TXT; 168 | rr.ttl = 3600; 169 | rr.data = rd; 170 | rd.txt.push('Hello world'); 171 | 172 | const sig = dnssec.sign(key, priv, [rr]); 173 | 174 | assert(dnssec.verify(sig, key, [rr])); 175 | }); 176 | } 177 | 178 | it('should create GOST94 DS record', () => { 179 | // https://tools.ietf.org/html/rfc5933#section-4.1 180 | const rrText = ` 181 | example.net. 86400 DNSKEY 257 3 12 ( 182 | LMgXRHzSbIJGn6i16K+sDjaDf/k1o9DbxScO 183 | gEYqYS/rlh2Mf+BRAY3QHPbwoPh2fkDKBroF 184 | SRGR7ZYcx+YIQw== 185 | ) ; key id = 40692 186 | `.replace(/^ +/gm, ''); 187 | 188 | const dsText = ` 189 | example.net. 3600 IN DS 40692 12 3 ( 190 | 22261A8B0E0D799183E35E24E2AD6BB58533CBA7E3B14D659E9CA09B 191 | 2071398F 192 | ) 193 | `.replace(/^ +/gm, ''); 194 | 195 | const rr = Record.fromString(rrText); 196 | const ds = dnssec.createDS(rr, dnssec.hashes.GOST94); 197 | const expect = Record.fromString(dsText); 198 | 199 | assert.bufferEqual(ds.data.digest, expect.data.digest); 200 | }); 201 | 202 | { 203 | const str = fs.readFileSync(ED448_TEST, 'utf8'); 204 | const parts = str.split(/\n+/); 205 | const keyText = parts[0].trim(); 206 | const rrText = parts[1].trim(); 207 | const sigText = parts[2].trim(); 208 | 209 | it('should verify ed448 signature', () => { 210 | const key = Record.fromString(keyText); 211 | const rr = Record.fromString(rrText); 212 | const sig = Record.fromString(sigText); 213 | 214 | assert.strictEqual(dnssec.verify(sig, key, [rr]), true); 215 | sig.data.signature[sig.data.signature.length * Math.random() | 0] ^= 1; 216 | assert.strictEqual(!dnssec.verify(sig, key, [rr]), true); 217 | }); 218 | } 219 | }); 220 | -------------------------------------------------------------------------------- /test/encoding-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* eslint prefer-arrow-callback: "off" */ 3 | 4 | 'use strict'; 5 | 6 | const assert = require('bsert'); 7 | const constants = require('../lib/constants'); 8 | const encoding = require('../lib/encoding'); 9 | const {types} = constants; 10 | 11 | const array = [ 12 | types.NS, 13 | types.SOA, 14 | types.RRSIG, 15 | types.NSEC, 16 | types.DNSKEY 17 | ]; 18 | 19 | describe('Encoding', function() { 20 | it('should serialize type bitmap', () => { 21 | const bitmap = encoding.toBitmap(array); 22 | 23 | for (const type of array) 24 | assert(encoding.hasType(bitmap, type), constants.typeToString(type)); 25 | 26 | const arr = encoding.fromBitmap(bitmap); 27 | assert.deepStrictEqual(array, arr); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /test/hints-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* eslint prefer-arrow-callback: "off" */ 3 | 4 | 'use strict'; 5 | 6 | const assert = require('bsert'); 7 | const Authority = require('../lib/authority'); 8 | const Hints = require('../lib/hints'); 9 | 10 | describe('Hints', function() { 11 | it('should add servers', () => { 12 | const hints = new Hints(); 13 | 14 | hints.addServer('a.root-servers.net.', '127.0.0.1'); 15 | hints.addServer('a.root-servers.net.', '::1'); 16 | 17 | const auth = hints.getAuthority(); 18 | assert(auth instanceof Authority); 19 | assert.strictEqual(auth.servers.length, 1); 20 | assert.strictEqual(auth.servers[0].host, '127.0.0.1'); 21 | assert.strictEqual(auth.servers[0].port, 53); 22 | 23 | const auth2 = hints.getAuthority(true); 24 | assert(auth2 instanceof Authority); 25 | assert.strictEqual(auth.servers.length, 1); 26 | assert.strictEqual(auth2.servers[0].host, '::1'); 27 | assert.strictEqual(auth2.servers[0].port, 53); 28 | }); 29 | 30 | it('should add default', () => { 31 | const hints = new Hints(); 32 | hints.setDefault(); 33 | 34 | const auth = hints.getAuthority(); 35 | assert.strictEqual(auth.servers.length, 13); 36 | 37 | const str = hints.toString(); 38 | const hints2 = Hints.fromString(str); 39 | 40 | assert.deepStrictEqual(hints2, hints); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /test/hosts-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* eslint prefer-arrow-callback: "off" */ 3 | /* eslint no-tabs: "off" */ 4 | 5 | 'use strict'; 6 | 7 | const assert = require('bsert'); 8 | const {types} = require('../lib/constants'); 9 | const encoding = require('../lib/encoding'); 10 | const Hosts = require('../lib/hosts'); 11 | 12 | const hostsText = ` 13 | # 14 | # /etc/hosts: static lookup table for host names 15 | # 16 | 17 | # 18 | 127.0.0.1 localhost.localdomain localhost 19 | ::1 localhost.localdomain localhost 20 | 127.0.1.1 machine.localdomain machine 21 | 22 | # End of file 23 | `; 24 | 25 | describe('Hosts', function() { 26 | it('should add hosts', () => { 27 | const hosts = Hosts.fromString(hostsText); 28 | 29 | { 30 | const answer = hosts.query('localhost.', types.A); 31 | assert(answer.length === 1); 32 | const rr = answer[0]; 33 | assert(rr && rr.type === types.A); 34 | assert(rr.data.address === '127.0.0.1'); 35 | } 36 | 37 | { 38 | const answer = hosts.query('localhost.', types.AAAA); 39 | assert(answer.length === 1); 40 | const rr = answer[0]; 41 | assert(rr && rr.type === types.AAAA); 42 | assert(rr.data.address === '::1'); 43 | } 44 | 45 | { 46 | const answer = hosts.query('localhost.', types.ANY); 47 | assert(answer.length === 2); 48 | 49 | const rr1 = answer[0]; 50 | assert(rr1 && rr1.type === types.A); 51 | assert(rr1.data.address === '127.0.0.1'); 52 | 53 | const rr2 = answer[1]; 54 | assert(rr2 && rr2.type === types.AAAA); 55 | assert(rr2.data.address === '::1'); 56 | } 57 | 58 | { 59 | const answer = hosts.query(encoding.reverse('127.0.0.1'), types.A); 60 | assert(answer.length === 0); 61 | } 62 | 63 | { 64 | const answer = hosts.query(encoding.reverse('127.0.0.1'), types.PTR); 65 | assert(answer.length === 1); 66 | 67 | const rr = answer[0]; 68 | assert(rr && rr.type === types.PTR); 69 | assert(rr.data.ptr === 'localhost.'); 70 | } 71 | 72 | { 73 | const answer = hosts.query(encoding.reverse('::1'), types.PTR); 74 | assert(answer.length === 1); 75 | 76 | const rr = answer[0]; 77 | assert(rr && rr.type === types.PTR); 78 | assert(rr.data.ptr === 'localhost.'); 79 | } 80 | 81 | { 82 | const answer = hosts.query(encoding.reverse('::2'), types.PTR); 83 | assert(!answer); 84 | } 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /test/hsig-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* eslint prefer-arrow-callback: "off" */ 3 | 4 | 'use strict'; 5 | 6 | const assert = require('bsert'); 7 | const hsig = require('../lib/hsig'); 8 | const wire = require('../lib/wire'); 9 | 10 | const { 11 | types, 12 | Message, 13 | Question, 14 | Record 15 | } = wire; 16 | 17 | describe('HSIG', function() { 18 | it('should sign and verify message', () => { 19 | const msg = new Message(); 20 | 21 | msg.qr = true; 22 | msg.ad = true; 23 | 24 | msg.question.push(Question.fromString('example.com. IN A')); 25 | msg.answer.push(Record.fromString('example.com. 300 IN A 172.217.0.46')); 26 | 27 | const priv = hsig.createPrivate(); 28 | const pub = hsig.createPublic(priv); 29 | const key = hsig.createKey(pub); 30 | assert(key); 31 | 32 | const msgRaw = msg.compress(); 33 | const signedRaw = hsig.sign(msgRaw, priv); 34 | 35 | const signed = Message.decode(signedRaw); 36 | assert(signed.sig0 instanceof Record); 37 | assert(signed.sig0.type === types.SIG); 38 | assert(signed.sig0.data.typeCovered === 0); 39 | assert(signed.sig0.data.algorithm === 253); // PRIVATEDNS 40 | assert(signed.sig0.data.signature.length === 64); 41 | 42 | assert(hsig.verify(signedRaw, pub)); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /test/iana-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* eslint prefer-arrow-callback: "off" */ 3 | 4 | 'use strict'; 5 | 6 | const assert = require('bsert'); 7 | const iana = require('../lib/internal/iana'); 8 | 9 | describe('IANA', function() { 10 | it('should have services and ports', () => { 11 | assert.strictEqual(iana.getPort('ssh'), 22); 12 | assert.strictEqual(iana.getService(22), 'ssh'); 13 | assert.strictEqual(iana.protocolToString(iana.protocols.ICMP), 'ICMP'); 14 | assert.strictEqual(iana.stringToProtocol('ICMP'), iana.protocols.ICMP); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /test/nsec3-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* eslint prefer-arrow-callback: "off" */ 3 | 4 | 'use strict'; 5 | 6 | const assert = require('bsert'); 7 | const nsec3 = require('../lib/nsec3'); 8 | const wire = require('../lib/wire'); 9 | const vectors = require('./data/nsec3-vectors.json'); 10 | const {Question, Record} = wire; 11 | 12 | describe('NSEC3', function() { 13 | for (const vector of vectors.hash_name) { 14 | const name = vector.name; 15 | const ha = vector.ha; 16 | const iter = vector.iter; 17 | const salt = Buffer.from(vector.salt, 'hex'); 18 | const result = vector.result != null 19 | ? Buffer.from(vector.result, 'hex') 20 | : null; 21 | 22 | it(`should hash name: ${name}`, () => { 23 | const res = nsec3.hashName(name, ha, iter, salt); 24 | 25 | if (!result) 26 | assert.strictEqual(res, result); 27 | else 28 | assert.bufferEqual(res, result); 29 | }); 30 | } 31 | 32 | for (const vector of vectors.cover) { 33 | const rr = Record.fromHex(vector.rr); 34 | const name = vector.name; 35 | const result = vector.result; 36 | 37 | it(`should cover ${name}`, () => { 38 | assert.strictEqual(nsec3.cover(rr, name), result); 39 | }); 40 | } 41 | 42 | for (const vector of vectors.match) { 43 | const rr = Record.fromHex(vector.rr); 44 | const name = vector.name; 45 | const result = vector.result; 46 | 47 | it(`should match ${name}`, () => { 48 | assert.strictEqual(nsec3.match(rr, name), result); 49 | }); 50 | } 51 | 52 | for (const vector of vectors.find_closest_encloser) { 53 | const name = vector.name; 54 | const nsec = vector.nsec.map(hex => Record.fromHex(hex)); 55 | const result = vector.result; 56 | 57 | it(`should find closest encloser for ${name}`, () => { 58 | assert.deepStrictEqual(nsec3.findClosestEncloser(name, nsec), result); 59 | }); 60 | } 61 | 62 | for (const vector of vectors.find_coverer) { 63 | const name = vector.name; 64 | const nsec = vector.nsec.map(hex => Record.fromHex(hex)); 65 | const result = vector.result[0] != null 66 | ? [Buffer.from(vector.result[0], 'hex'), vector.result[1]] 67 | : vector.result; 68 | 69 | it(`should find coverer for ${name}`, () => { 70 | assert.deepStrictEqual(nsec3.findCoverer(name, nsec), result); 71 | }); 72 | } 73 | 74 | for (const vector of vectors.verify_name_error) { 75 | const qs = Question.fromHex(vector.qs); 76 | const nsec = vector.nsec.map(hex => Record.fromHex(hex)); 77 | const result = vector.result; 78 | 79 | it(`should verify NXDOMAIN for ${qs.name}`, () => { 80 | assert.strictEqual(nsec3.verifyNameError(qs, nsec), result); 81 | }); 82 | } 83 | 84 | for (const vector of vectors.verify_no_data) { 85 | const qs = Question.fromHex(vector.qs); 86 | const nsec = vector.nsec.map(hex => Record.fromHex(hex)); 87 | const result = vector.result; 88 | 89 | it(`should verify NODATA for ${qs.name}`, () => { 90 | assert.strictEqual(nsec3.verifyNoData(qs, nsec), result); 91 | }); 92 | } 93 | 94 | for (const vector of vectors.verify_delegation) { 95 | const delegation = vector.delegation; 96 | const nsec = vector.nsec.map(hex => Record.fromHex(hex)); 97 | const result = vector.result; 98 | 99 | it(`should verify delegation for ${delegation}`, () => { 100 | assert.strictEqual(nsec3.verifyDelegation(delegation, nsec), result); 101 | }); 102 | } 103 | }); 104 | -------------------------------------------------------------------------------- /test/openpgpkey-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* eslint prefer-arrow-callback: "off" */ 3 | 4 | 'use strict'; 5 | 6 | const assert = require('bsert'); 7 | const Path = require('path'); 8 | const fs = require('fs'); 9 | const wire = require('../lib/wire'); 10 | const opk = require('../lib/openpgpkey'); 11 | const {Record, types} = wire; 12 | 13 | const OPK_FILE = Path.resolve(__dirname, 'data', 'openpgpkey.zone'); 14 | const opkText = fs.readFileSync(OPK_FILE, 'utf8'); 15 | 16 | function fromBase64(str) { 17 | return Buffer.from(str.replace(/\s+/g, ''), 'base64'); 18 | } 19 | 20 | describe('OPENPGPKEY', function() { 21 | it('should serialize email', () => { 22 | const name1 = opk.encodeEmail('slippinjimmy@example.com.', 256); 23 | 24 | assert.strictEqual(name1, 25 | 'ae91629c1142f97683521f4b70cade48e95202aff15c16f0fdf34779' 26 | + '._openpgpkey.example.com.'); 27 | 28 | const name2 = opk.encodeEmail('slippinjimmy@example.com.', 224); 29 | 30 | assert.strictEqual(name2, 31 | 'b33f5890ccb3ea7d9c91a6459d75a8a27eb9e894ab25fd2b5fc26aef' 32 | + '._openpgpkey.example.com.'); 33 | 34 | assert(opk.isName(name1)); 35 | assert(opk.isName(name2)); 36 | 37 | const data1 = opk.decodeName(name1); 38 | assert.strictEqual(data1.name, 'example.com.'); 39 | assert.strictEqual(data1.hash.length, 28); 40 | 41 | const data2 = opk.decodeName(name1); 42 | assert.strictEqual(data2.name, 'example.com.'); 43 | assert.strictEqual(data2.hash.length, 28); 44 | }); 45 | 46 | it('should create openpgpkey record', () => { 47 | const key = Buffer.alloc(32, 0x00); 48 | const rr = opk.create(key, 'slippinjimmy@example.com.', { ttl: 3600 }); 49 | assert(rr.type === types.OPENPGPKEY); 50 | assert(opk.verify(rr, key)); 51 | const fake = Buffer.alloc(32, 0x00); 52 | fake[0] ^= 1; 53 | assert(!opk.verify(rr, fake)); 54 | }); 55 | 56 | it('should create openpgpkey record', () => { 57 | // $ dig.js zbyszek@fedoraproject.org OPENPGPKEY -b 224 58 | const key = fromBase64(` 59 | mQINBFBHPMsBEACeInGYJCb+7TurKfb6wGyTottCDtiSJB310i37/6ZY 60 | oeIay/5soJjlMyfMFQ9T2XNT/0LM6gTa0MpC1st9LnzYTMsT6tzRly1D 61 | 1UbVI6xw0g0vE5y2Cjk3xUwAynCsSsgg5KrjdYWRqLSTZ3zEABm/gNg6 62 | OgA5l6QU+geXcQ9+P285WoUuj0j7HN6T217Bd+RcVxNWOMxsqx+b0rjW 63 | a8db1KiwM95wddCwzMPB2S/6IswD1P8nVfGnkgp7pfoTyMuDkVU6hmO5 64 | RHq9M26eNoQ4sJZuXe5YjODnjgxkKKilFLY8hUkjwa1VPrx4QnTwzIn1 65 | 6JlUO03At9tpe+9SnShDV0cBlHxo3DhnHmCPWJ0HquLGpdDVi8d9tn0n 66 | lit96z9Svb9ii6Uq/J8zR1Bp+hxCMN/ON1c4U+cf1jfADPO5c3KV89y5 67 | wvvQvzjTjuzVolR4ZZmkNSql+4vspo94JrssymEv9WWiMJyOjN50QhLb 68 | gmWiuzYjodZiL0CTB4MAC+hTrDZrZfyAnbAttBLfNWd/jcdK+AGVRXtq 69 | U997sZPzj8z3b7v2N5YJqgm2aQTiDehtHtHDJ8rKh7kcsssnhzzoZluT 70 | Kl96JHgllFWUC6sedAFVxHDmb7cxb+Sr0krwbt22is+41gPCuoz1MRKw 71 | QYQPTYgcCzX/PzyOHj6KEYZCIQARAQABtDBaYmlnbmlldyBKxJlkcnpl 72 | amV3c2tpLVN6bWVrIDx6YnlzemVrQGluLndhdy5wbD6JAjgEEwECACIF 73 | AlBHPMsCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEMVMozbP 74 | 61V+T80QAIHvIeDdKKrqiN98ob+aNe4McpqXWgiLoDMWaxQ7R8K+2Uia 75 | HMn2J6abhKidvUr7mnixkyBZaRxi1JiT8uzX4HTQ3B/UVJgf2QiRHRvZ 76 | pdSVn7O7OF0u4SqH6BEw5UaA30hMWtgz7m6aXSAN1aitd4efgKjBYKtf 77 | sHJ63HhFrpJyIyOGg0eLGObXJxjW04cUbzPoCoNuFcpphzW3WhdaJ5PX 78 | blfjNxWxKzwvAzRhevDjrrKU4jARNAIkLUMi4gUoC+7th6ATGWjYV8iO 79 | vju1cLExn8ktYMZl+BhbkYiRMddMZaZ/nY2T2SqQ8kkEXJyY6SNtd/BW 80 | uCPyt0RlTgPSK4SK9JGArVJ/PSXJrn53JUl1MUc4/75JE2KEBPkN4jQp 81 | eshlPfm0mzo/+opyi6iyVnPTZT7m7r9P7Vxc18J+IlPdfl0ws0YPnR+0 82 | oUvo370zoNqqhJ9aNU+5d4VCPUHVIvEWEF3MHuXHKq0mnnI/4jJMvZn3 83 | 0+okZZfYABYXkMZVTA0XprkIxZm38X9s/uw886xvMqPh8fhqpVdTHD5/ 84 | 2h8ahkMMG1zKs6W6gCfM7gYh+BT37Ce1szo/7RHtbvYq5BTwcWXhpSKz 85 | ywluRe6rYUPJ0MCZ17Jk6AXStD1aRYS6rCykryRL0OvMz/4Gd8f+dcQj 86 | g5Si23mAj8VJtyrX1MaXuQINBFBHPMsBEACtDR2e2G4uL/MKtDsJu3cw 87 | QFlK9kmGEX4UqePBc29xn1BTfU6o1Y4pAXRoLrGvXuVruOwznNdkpjF8 88 | kb1BpO/li8qNU6LKyv2n9Hyg0bxRQA24TVC4bF4mfdqaGGYLqxe3iXI/ 89 | TRmhsmiSg7WoEWxj0NEaEjydTAieT4kz2ASCYtnzhGM8iS2Te+scUXYc 90 | GNyE2nPjiacJGiLeKiOj21+j6sICTrKX8TAcXSU7btPEy2IIocxBoxZe 91 | Ofp0rNw4293cLVu0kEasB4h43lE1Uax7JYX1q9LC4TkqLaLDa3YyDGvK 92 | 2FOPNNIrsKcoYG6Y43DcmaSPZCJ1ApVvoxPct7UI+EYy9VBu3wwY0obR 93 | adweXSNgscZZNvExZgdjRXJypv8A9A+nvc2xBMWw/9fAlHzrpjds+3Z2 94 | RxbGC4Qav/sdP0WqQZ8xo5U2YPxBSHwWCjSxvQWcoDLLOgMlB05oheR2 95 | g1VDk4QA+AXDwmxurpvJLh/fyX3mi7nPVUynTLV/UeWaXbZneh+mT3Lc 96 | 1ZVYnntSoZv7aYQqnA+a2ajm08lVMmpb5v8L7ZiadvW2xptVATlWI1De 97 | BTyNwZYyx7GuUsfFTSyQJixtjuWim0acpqNUp8z6TgXj02HtRfk9Swzv 98 | BCgJT5mWoGlSu04FR/0pn5ReVCM8RSb6/HOROnrfswGeGQARAQABiQIf 99 | BBgBAgAJBQJQRzzLAhsMAAoJEMVMozbP61V+qg8P/1BuLn6+bVgDdye3 100 | 7GV4kXSVxB5SQZj8ElwTj+daWq8ZEIoZ0ySyRz2uC7Haeh5XulF1hj13 101 | AYfM4Ary9Whx9hCQ98D4+JK5eiagBuSpIApCkQk+jj44q7VKLanyZV0k 102 | WRNBSfr0TnE6GoBSL1gTjpsqt/mUR2R5zgCE59Ex4HHBwvosIcXgGopb 103 | PGNtX9S4Rm7f2wWOSdXGc6pfnuFsVtkbk8z+uITyK3WX+jHiW5JRgyHW 104 | aFyYqwDjds8q0LkmIL80scPa3sEl9QzfT7+7xqcviKfemg6XgCwYmSOh 105 | PHSK/E6MIC6Wb4QY6H3ixCuMfaic6AsB4sH4vFPoPnJWmIGmQlU3L1UJ 106 | z4VNvzCaClaxWPa5nZZAWyFRMof4VtO2Q1LTZa6NQbGNFRRLPDBXpcOq 107 | CNicjZjSaHO9Zxp/V+9W9GgH6u7i/eAnxifwUFvN0BfkwbDnp4BNyvyA 108 | +fpZ4oPWInygfP1P/fvALssBvJjo/q6eZ4b5O11Ut/13JzO4IYNISK8u 109 | Knt5AbU9YUnSKClg1MtTRlBCD3v+UYy102F7p8rJnVTHelfgmjP9UPhP 110 | 7AUwZ0UQYq9QypNeoRvR4GjL/3Yz53yHFeYaN/lBglm4jNQOmHTQSibv 111 | z8lx8ALGbLxTaUr8j+UG4Gu2z3tFpYo0NHq9Ahd8L7JVIsbKtcoP 112 | `); 113 | 114 | const rr = Record.fromString(opkText); 115 | assert(rr.type === types.OPENPGPKEY); 116 | assert(opk.verify(rr, key)); 117 | }); 118 | }); 119 | -------------------------------------------------------------------------------- /test/ownership-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* eslint prefer-arrow-callback: "off" */ 3 | 4 | 'use strict'; 5 | 6 | const assert = require('bsert'); 7 | const Ownership = require('../lib/ownership'); 8 | const vectors = require('./data/ownership.json'); 9 | const {OwnershipProof} = Ownership; 10 | 11 | function verifyProof(name, ownership, proof, weak) { 12 | assert(ownership.isSane(proof), `${name}: invalid-sanity`); 13 | assert(ownership.verifyTimes(proof, 1580476262), `${name}: invalid-times`); 14 | assert(ownership.verifySignatures(proof), `${name}: invalid-signatures`); 15 | assert.strictEqual(ownership.isWeak(proof), weak, `${name}: invalid-weak`); 16 | } 17 | 18 | describe('Ownership', function() { 19 | this.timeout(10000); 20 | 21 | for (const vector of vectors) { 22 | const {name, secure, weak} = vector; 23 | const proof = OwnershipProof.fromHex(vector.proof); 24 | 25 | it(`should test ownership for ${name} (s=${secure}, w=${weak})`, () => { 26 | const ownership = new Ownership(); 27 | 28 | ownership.secure = secure; 29 | 30 | verifyProof(name, ownership, proof, weak); 31 | 32 | const raw = proof.toHex(); 33 | const unraw = OwnershipProof.fromHex(raw); 34 | 35 | assert.strictEqual(raw, unraw.toHex()); 36 | 37 | verifyProof(name, ownership, unraw, weak); 38 | 39 | const txt = proof.toString(); 40 | const untxt = OwnershipProof.fromString(txt); 41 | 42 | assert.strictEqual(raw, untxt.toHex()); 43 | }); 44 | } 45 | }); 46 | -------------------------------------------------------------------------------- /test/proof-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* eslint prefer-arrow-callback: "off" */ 3 | 4 | 'use strict'; 5 | 6 | const assert = require('bsert'); 7 | const Ownership = require('../lib/ownership'); 8 | const util = require('../lib/util'); 9 | const Resolver = require('../lib/resolver/stub'); 10 | const {OwnershipProof} = Ownership; 11 | 12 | function verifyProof(name, ownership, proof, weak) { 13 | assert(ownership.isSane(proof), `${name}: invalid-sanity`); 14 | assert(ownership.verifyTimes(proof, util.now()), `${name}: invalid-times`); 15 | assert(ownership.verifySignatures(proof), `${name}: invalid-signatures`); 16 | assert.strictEqual(ownership.isWeak(proof), weak, `${name}: invalid-weak`); 17 | } 18 | 19 | async function testOwnership(name, secure, weak) { 20 | const ownership = new Ownership(); 21 | 22 | ownership.Resolver = Resolver; 23 | ownership.secure = secure; 24 | 25 | const proof = await ownership.prove(name); 26 | 27 | verifyProof(name, ownership, proof, weak); 28 | 29 | const raw = proof.toHex(); 30 | const unraw = OwnershipProof.fromHex(raw); 31 | 32 | assert.strictEqual(raw, unraw.toHex()); 33 | 34 | verifyProof(name, ownership, unraw, weak); 35 | 36 | const txt = proof.toString(); 37 | const untxt = OwnershipProof.fromString(txt); 38 | 39 | assert.strictEqual(raw, untxt.toHex()); 40 | 41 | return proof; 42 | } 43 | 44 | const provableNames = [ 45 | ['cloudflare.com.', true, true], 46 | ['nlnetlabs.nl.', true, true], 47 | ['nlnet.nl.', true, true], 48 | ['verisigninc.com.', true, true], 49 | ['iis.se.', false, true], 50 | ['kirei.se.', true, false], 51 | ['opendnssec.org.', false, true], 52 | ['ietf.org.', false, true], 53 | ['iana.org.', false, true], 54 | ['internetsociety.org.', false, true] 55 | ]; 56 | 57 | if (process.browser) 58 | return; 59 | 60 | describe('Proof', function() { 61 | this.timeout(10000); 62 | 63 | for (const [name, secure, weak] of provableNames) { 64 | it(`should verify proof for ${util.trimFQDN(name)}`, async () => { 65 | const proof1 = await testOwnership(name, secure, weak); 66 | assert(proof1); 67 | if (secure) { 68 | const proof2 = await testOwnership(name, false, weak); 69 | assert(proof2); 70 | } 71 | }); 72 | } 73 | }); 74 | -------------------------------------------------------------------------------- /test/record-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* eslint prefer-arrow-callback: "off" */ 3 | 4 | 'use strict'; 5 | 6 | const assert = require('bsert'); 7 | const wire = require('../lib/wire'); 8 | const {Record, DNSKEYRecord} = wire; 9 | 10 | // $ dig SW1A2AA.find.me.uk. LOC 11 | const locTxt = 'SW1A2AA.find.me.uk. 2592000 IN LOC' 12 | + ' 51 30 12.748 N 0 7 39.611 W 0.00m 0.00m 0.00m 0.00m'; 13 | 14 | // $ dig apple.com. NAPTR 15 | const naptr = [ 16 | 'apple.com. 86400 IN NAPTR 50 50 "se" "SIPS+D2T" "" _sips._tcp.apple.com.', 17 | 'apple.com. 86400 IN NAPTR 90 50 "se" "SIP+D2T" "" _sip._tcp.apple.com.', 18 | 'apple.com. 86400 IN NAPTR 100 50 "se" "SIP+D2U" "" _sip._udp.apple.com.' 19 | ]; 20 | 21 | // $ dig . DNSKEY +dnssec 22 | const prefix = '. 172800 IN DNSKEY'; 23 | const keyTxt = ' 385 3 8' 24 | + ' AwEAAagAIKlVZrpC6Ia7gEzahOR+9W29euxhJhVVLOyQbSEW0O8gcCjF' 25 | + ' FVQUTf6v58fLjwBd0YI0EzrAcQqBGCzh/RStIoO8g0NfnfL2MTJRkxoX' 26 | + ' bfDaUeVPQuYEhg37NZWAJQ9VnMVDxP/VHL496M/QZxkjf5/Efucp2gaD' 27 | + ' X6RS6CXpoY68LsvPVjR0ZSwzz1apAzvN9dlzEheX7ICJBBtuA6G3LQpz' 28 | + ' W5hOA2hzCTMjJPJ8LbqF6dsV6DoBQzgul0sGIcGOYl7OyQdXfZ57relS' 29 | + ' Qageu+ipAdTTJ25AsRTAoub8ONGcLmqrAmRLKBP1dfwhYB4N7knNnulq' 30 | + ' QxA+Uk1ihz0= ; KSK ; alg = RSASHA256 ; bits = 2048,17 ; key id = 19164'; 31 | 32 | const keyJSON = 33 | { 34 | name: '.', 35 | ttl: 172800, 36 | class: 'IN', 37 | type: 'DNSKEY', 38 | data: { 39 | flags: 385, 40 | protocol: 3, 41 | algorithm: 8, 42 | publicKey: 'AwEAAagAIKlVZrpC6Ia7gEzahOR+9W29euxhJhVVLOyQbSEW0O8gcCjF' 43 | + 'FVQUTf6v58fLjwBd0YI0EzrAcQqBGCzh/RStIoO8g0NfnfL2MTJRkxoX' 44 | + 'bfDaUeVPQuYEhg37NZWAJQ9VnMVDxP/VHL496M/QZxkjf5/Efucp2gaD' 45 | + 'X6RS6CXpoY68LsvPVjR0ZSwzz1apAzvN9dlzEheX7ICJBBtuA6G3LQpz' 46 | + 'W5hOA2hzCTMjJPJ8LbqF6dsV6DoBQzgul0sGIcGOYl7OyQdXfZ57relS' 47 | + 'Qageu+ipAdTTJ25AsRTAoub8ONGcLmqrAmRLKBP1dfwhYB4N7knNnulqQxA+Uk1ihz0=', 48 | keyType: 'KSK', 49 | keyTag: 19164, 50 | algName: 'RSASHA256', 51 | bits: [2048, 17] 52 | } 53 | }; 54 | 55 | describe('Record', function() { 56 | it('should parse LOC record', () => { 57 | const rr = Record.fromString(locTxt); 58 | assert.strictEqual(rr.toString(), locTxt); 59 | }); 60 | 61 | it('should parse NAPTR records', () => { 62 | for (const txt of naptr) { 63 | const rr = Record.fromString(txt); 64 | assert.strictEqual(rr.toString(), txt); 65 | } 66 | }); 67 | 68 | it('should parse DNSKEY record', () => { 69 | const rr = Record.fromString(prefix + keyTxt); 70 | assert.deepStrictEqual(rr.getJSON(), keyJSON); 71 | }); 72 | 73 | it('should parse DNSKEY data from string', () => { 74 | const rr = DNSKEYRecord.fromString(keyTxt); 75 | assert.deepStrictEqual(rr.getJSON(), keyJSON.data); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /test/recursive-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* eslint prefer-arrow-callback: "off" */ 3 | 4 | 'use strict'; 5 | 6 | const assert = require('bsert'); 7 | const RecursiveResolver = require('../lib/resolver/recursive'); 8 | const UnboundResolver = require('../lib/resolver/unbound'); 9 | const rdns = require('../lib/rdns'); 10 | const udns = require('../lib/udns'); 11 | const {types, codes} = require('../lib/wire'); 12 | 13 | const dnssecNames = [ 14 | 'cloudflare.com', 15 | 'dnssec-name-and-shame.com', 16 | 'nlnetlabs.nl', 17 | 'nlnet.nl', 18 | 'labs.verisigninc.com', 19 | 'iis.se', 20 | 'www.kirei.se', 21 | 'www.opendnssec.org', 22 | 'www.ietf.org', 23 | 'www.iana.org', 24 | 'internetsociety.org', 25 | 'ed25519.nl', 26 | 'ed448.nl' 27 | ]; 28 | 29 | const nxNames = [ 30 | 'ww.dnssec-name-and-shame.com', 31 | 'ww.nlnet.nl', 32 | 'ww.opendnssec.org' 33 | ]; 34 | 35 | const nodataNames = [ 36 | 'dnssec-name-and-shame.com', 37 | 'nlnet.nl', 38 | 'www.opendnssec.org' 39 | ]; 40 | 41 | const noDnssecNames = [ 42 | 'google.com' 43 | ]; 44 | 45 | const noNxNames = [ 46 | 'nxdomain.google.com' 47 | ]; 48 | 49 | const noNodataNames = [ 50 | 'google.com' 51 | ]; 52 | 53 | if (process.browser) 54 | return; 55 | 56 | function checkUBversion(string) { 57 | const digits = string.split('.'); 58 | 59 | // Require support for ED448, at least version 1.8.x 60 | if (parseInt(digits[0]) > 1) 61 | return true; 62 | 63 | if (parseInt(digits[0]) < 1) 64 | return false; 65 | 66 | if (parseInt(digits[1]) < 8) 67 | return false; 68 | 69 | return true; 70 | } 71 | 72 | describe('Recursive', function() { 73 | this.timeout(20000); 74 | 75 | it(`should return the version of libunbound: ${udns.version}`, () => { 76 | ; 77 | }); 78 | 79 | for (const Resolver of [RecursiveResolver, UnboundResolver]) { 80 | it('should do a recursive resolution', async () => { 81 | const res = new Resolver({ 82 | tcp: true, 83 | inet6: false, 84 | edns: true, 85 | dnssec: true 86 | }); 87 | 88 | res.hints.setDefault(); 89 | 90 | res.on('error', (err) => { 91 | throw err; 92 | }); 93 | 94 | await res.open(); 95 | 96 | const msg = await res.lookup('google.com.', types.A); 97 | assert(msg.code === codes.NOERROR); 98 | assert(msg.answer.length > 0); 99 | assert(msg.answer[0].name === 'google.com.'); 100 | assert(msg.answer[0].type === types.A); 101 | 102 | await res.close(); 103 | }); 104 | } 105 | 106 | for (const dns of [rdns, udns]) { 107 | describe(`${dns === rdns ? 'JavaScript' : 'Unbound'}`, function () { 108 | for (const name of dnssecNames) { 109 | if (name === 'ed25519.nl' || name === 'ed448.nl') { 110 | if (dns === udns && !checkUBversion(udns.version)) 111 | continue; 112 | } 113 | 114 | it(`should validate trust chain for ${name}`, async () => { 115 | const res = await dns.resolveRaw(name, types.A); 116 | assert.strictEqual(res.code, codes.NOERROR); 117 | assert(res.answer.length > 0); 118 | assert(res.ad); 119 | }); 120 | } 121 | 122 | for (const name of nxNames) { 123 | it(`should validate NX proof for ${name}`, async () => { 124 | const res = await dns.resolveRaw(name, types.A); 125 | assert.strictEqual(res.code, codes.NXDOMAIN); 126 | assert(res.answer.length === 0); 127 | assert(res.ad); 128 | }); 129 | } 130 | 131 | for (const name of nodataNames) { 132 | it(`should validate NODATA proof for ${name}`, async () => { 133 | const res = await dns.resolveRaw(name, types.WKS); 134 | assert.strictEqual(res.code, codes.NOERROR); 135 | assert(res.answer.length === 0); 136 | assert(res.ad); 137 | }); 138 | } 139 | 140 | for (const name of noDnssecNames) { 141 | it(`should fail to validate trust chain for ${name}`, async () => { 142 | const res = await dns.resolveRaw(name, types.A); 143 | assert.strictEqual(res.code, codes.NOERROR); 144 | assert(res.answer.length > 0); 145 | assert(!res.ad); 146 | }); 147 | } 148 | 149 | for (const name of noNxNames) { 150 | it(`should fail to validate NX proof for ${name}`, async () => { 151 | const res = await dns.resolveRaw(name, types.A); 152 | assert.strictEqual(res.code, codes.NXDOMAIN); 153 | assert(res.answer.length === 0); 154 | assert(!res.ad); 155 | }); 156 | } 157 | 158 | for (const name of noNodataNames) { 159 | it(`should fail to validate NODATA proof for ${name}`, async () => { 160 | const res = await dns.resolveRaw(name, types.WKS); 161 | assert.strictEqual(res.code, codes.NOERROR); 162 | assert(res.answer.length === 0); 163 | assert(!res.ad); 164 | }); 165 | } 166 | }); 167 | } 168 | }); 169 | -------------------------------------------------------------------------------- /test/resolvconf-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* eslint prefer-arrow-callback: "off" */ 3 | 4 | 'use strict'; 5 | 6 | const assert = require('bsert'); 7 | const ResolvConf = require('../lib/resolvconf'); 8 | 9 | const rcTxt1 = ` 10 | # Testing 11 | nameserver 8.8.8.8 12 | nameserver 8.8.4.4 13 | search example.com 14 | sortlist 130.155.160.0/255.255.240.0 130.155.0.0 15 | `; 16 | 17 | const rcTxt2 = ` 18 | # Testing with ports 19 | nameserver 8.8.8.8:5300 20 | nameserver 8.8.4.4:5300 21 | search example.com 22 | sortlist 130.155.160.0/255.255.240.0 130.155.0.0 23 | `; 24 | 25 | const rcTxt3 = ` 26 | # Testing with ports and keys 27 | nameserver 8.8.8.8:5300 28 | nameserver 8.8.4.4:5300 29 | nameserver akqq3xoch6cgluhgqh2n7lm4lh4d2zjuzyiekudx6d37xckhp26dg@127.0.0.1:5300 30 | search example.com 31 | sortlist 130.155.160.0/255.255.240.0 130.155.0.0 32 | `; 33 | 34 | const rcTxt4 = ` 35 | # Generated by bns 36 | nameserver 208.67.222.222 37 | nameserver 208.67.220.220 38 | nameserver 208.67.222.220 39 | nameserver 208.67.220.222 40 | nameserver 2620:0:ccc::2 41 | nameserver 2620:0:ccd::2 42 | search example.com 43 | sortlist 130.155.160.0/255.255.240.0 130.155.0.0 44 | options ndots:2 timeout:6 attempts:3 rotate inet6 edns0 no-tld-query use-vc 45 | `; 46 | 47 | function strip(str) { 48 | str = str.replace(/^#.*/gm, ''); 49 | str = str.trim(); 50 | return str; 51 | } 52 | 53 | describe('ResolvConf', function() { 54 | it('should handle resolv.conf (1)', () => { 55 | const rc = ResolvConf.fromString(rcTxt1); 56 | 57 | assert(rc.ns4.length === 2); 58 | assert(rc.ns4[0].host === '8.8.8.8'); 59 | assert(rc.ns4[0].port === 53); 60 | assert(rc.ns4[1].host === '8.8.4.4'); 61 | assert(rc.ns4[1].port === 53); 62 | 63 | assert.deepStrictEqual(rc.search, ['example.com.']); 64 | 65 | assert.deepStrictEqual(rc.sortlist, [ 66 | { ip: '130.155.160.0', mask: '255.255.240.0' }, 67 | { ip: '130.155.0.0', mask: null } 68 | ]); 69 | 70 | assert.deepStrictEqual(rc.getServers(), [ 71 | '8.8.8.8:53', 72 | '8.8.4.4:53' 73 | ]); 74 | 75 | assert.strictEqual(strip(rc.toString()), strip(rcTxt1)); 76 | }); 77 | 78 | it('should handle resolv.conf (2)', () => { 79 | const rc = ResolvConf.fromString(rcTxt2); 80 | 81 | assert(rc.ns4.length === 2); 82 | assert(rc.ns4[0].host === '8.8.8.8'); 83 | assert(rc.ns4[0].port === 5300); 84 | assert(rc.ns4[1].host === '8.8.4.4'); 85 | assert(rc.ns4[1].port === 5300); 86 | 87 | assert.deepStrictEqual(rc.search, ['example.com.']); 88 | 89 | assert.deepStrictEqual(rc.sortlist, [ 90 | { ip: '130.155.160.0', mask: '255.255.240.0' }, 91 | { ip: '130.155.0.0', mask: null } 92 | ]); 93 | 94 | assert.deepStrictEqual(rc.getServers(), [ 95 | '8.8.8.8:5300', 96 | '8.8.4.4:5300' 97 | ]); 98 | 99 | assert.strictEqual(strip(rc.toString(true)), strip(rcTxt2)); 100 | }); 101 | 102 | it('should handle resolv.conf (3)', () => { 103 | const rc = ResolvConf.fromString(rcTxt3); 104 | 105 | assert(rc.ns4.length === 3); 106 | assert(rc.ns4[0].host === '8.8.8.8'); 107 | assert(rc.ns4[0].port === 5300); 108 | assert(rc.ns4[1].host === '8.8.4.4'); 109 | assert(rc.ns4[1].port === 5300); 110 | assert(rc.ns4[2].host === '127.0.0.1'); 111 | assert(rc.ns4[2].port === 5300); 112 | 113 | assert.deepStrictEqual(rc.search, ['example.com.']); 114 | 115 | assert.deepStrictEqual(rc.sortlist, [ 116 | { ip: '130.155.160.0', mask: '255.255.240.0' }, 117 | { ip: '130.155.0.0', mask: null } 118 | ]); 119 | 120 | assert.deepStrictEqual(rc.getServers(), [ 121 | '8.8.8.8:5300', 122 | '8.8.4.4:5300', 123 | '127.0.0.1:5300' 124 | ]); 125 | 126 | assert(rc.keys.get('127.0.0.1:5300')); 127 | 128 | assert.strictEqual(strip(rc.toString(true)), strip(rcTxt3)); 129 | }); 130 | 131 | it('should handle resolv.conf (4)', () => { 132 | const rc = ResolvConf.fromString(rcTxt4); 133 | assert(rc.rotate); 134 | assert.strictEqual(strip(rc.toString()), strip(rcTxt4)); 135 | }); 136 | }); 137 | -------------------------------------------------------------------------------- /test/sig0-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* eslint prefer-arrow-callback: "off" */ 3 | 4 | 'use strict'; 5 | 6 | const secp256k1 = require('bcrypto/lib/secp256k1'); 7 | const bio = require('bufio'); 8 | const assert = require('bsert'); 9 | const sig0 = require('../lib/sig0'); 10 | const hsig = require('../lib/hsig'); 11 | const wire = require('../lib/wire'); 12 | const encoding = require('../lib/encoding'); 13 | 14 | const { 15 | types, 16 | algs, 17 | Message, 18 | Question, 19 | Record 20 | } = wire; 21 | 22 | const { 23 | readNameBR 24 | } = encoding; 25 | 26 | describe('SIG(0)', function() { 27 | it('should sign and verify message', () => { 28 | const msg = new Message(); 29 | 30 | msg.qr = true; 31 | msg.ad = true; 32 | 33 | msg.question.push(Question.fromString('example.com. IN A')); 34 | msg.answer.push(Record.fromString('example.com. 300 IN A 172.217.0.46')); 35 | 36 | const priv = sig0.createPrivate(algs.RSASHA256, 1024); 37 | const pub = sig0.createPublic(algs.RSASHA256, priv); 38 | const key = sig0.createKey(algs.RSASHA256, pub); 39 | 40 | const msgRaw = msg.compress(); 41 | const signedRaw = sig0.sign(msgRaw, key, priv); 42 | 43 | const signed = Message.decode(signedRaw); 44 | assert(signed.sig0 instanceof Record); 45 | assert(signed.sig0.type === types.SIG); 46 | assert(signed.sig0.data.typeCovered === 0); 47 | assert(signed.sig0.data.algorithm === algs.RSASHA256); 48 | assert(signed.sig0.data.signature.length === 1024 / 8); 49 | 50 | assert(sig0.verify(signedRaw, key)); 51 | }); 52 | 53 | it('should sign and verify message w/ privatedns', () => { 54 | const msg = new Message(); 55 | 56 | msg.qr = true; 57 | msg.ad = true; 58 | 59 | msg.question.push(Question.fromString('example.com. IN A')); 60 | msg.answer.push(Record.fromString('example.com. 300 IN A 172.217.0.46')); 61 | 62 | const priv = hsig.createPrivate(); 63 | const pub = hsig.createPublic(priv); 64 | const key = hsig.createKey(pub); 65 | 66 | const signer = (priv, data) => { 67 | return secp256k1.sign(data, priv); 68 | }; 69 | 70 | const msgRaw = msg.compress(); 71 | let signedRaw = sig0.sign(msgRaw, key, priv, null, signer); 72 | 73 | let signed = Message.decode(signedRaw); 74 | assert(signed.sig0 instanceof Record); 75 | assert(signed.sig0.type === types.SIG); 76 | assert(signed.sig0.data.typeCovered === 0); 77 | assert(signed.sig0.data.algorithm === algs.PRIVATEDNS); 78 | 79 | const verifier = (rr, key, data) => { 80 | const br = new bio.BufferReader(key.data.publicKey); 81 | const name = readNameBR(br); 82 | 83 | assert(name === hsig.SIG0_ALGO_NAME); 84 | 85 | const size = br.data.length - br.offset; 86 | const publicKey = br.readBytes(size); 87 | return secp256k1.verify(data, rr.data.signature, publicKey);; 88 | }; 89 | 90 | assert(sig0.verify(signedRaw, key, verifier)); 91 | 92 | signedRaw = hsig.sign(msgRaw, priv); 93 | 94 | signed = Message.decode(signedRaw); 95 | assert(signed.sig0 instanceof Record); 96 | assert(signed.sig0.type === types.SIG); 97 | assert(signed.sig0.data.typeCovered === 0); 98 | assert(signed.sig0.data.algorithm === algs.PRIVATEDNS); 99 | 100 | assert(hsig.verify(signedRaw, pub)); 101 | }); 102 | }); 103 | -------------------------------------------------------------------------------- /test/smimea-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* eslint prefer-arrow-callback: "off" */ 3 | 4 | 'use strict'; 5 | 6 | const assert = require('bsert'); 7 | const smimea = require('../lib/smimea'); 8 | const {usages, selectors, matchingTypes} = smimea; 9 | 10 | function fromBase64(str) { 11 | return Buffer.from(str.replace(/\s+/g, ''), 'base64'); 12 | } 13 | 14 | describe('S/MIMEA', function() { 15 | it('should serialize email', () => { 16 | const name1 = smimea.encodeEmail('slippinjimmy@example.com.', 256); 17 | 18 | assert.strictEqual(name1, 19 | 'ae91629c1142f97683521f4b70cade48e95202aff15c16f0fdf34779' 20 | + '._smimecert.example.com.'); 21 | 22 | const name2 = smimea.encodeEmail('slippinjimmy@example.com.', 224); 23 | 24 | assert.strictEqual(name2, 25 | 'b33f5890ccb3ea7d9c91a6459d75a8a27eb9e894ab25fd2b5fc26aef' 26 | + '._smimecert.example.com.'); 27 | 28 | assert(smimea.isName(name1)); 29 | assert(smimea.isName(name2)); 30 | 31 | const data1 = smimea.decodeName(name1); 32 | assert.strictEqual(data1.name, 'example.com.'); 33 | assert.strictEqual(data1.hash.length, 28); 34 | 35 | const data2 = smimea.decodeName(name1); 36 | assert.strictEqual(data2.name, 'example.com.'); 37 | assert.strictEqual(data2.hash.length, 28); 38 | }); 39 | 40 | it('should create SMIMEA record', () => { 41 | const cert = fromBase64(` 42 | MIIFUTCCBDmgAwIBAgIIITAshaEP0OswDQYJKoZIhvcNAQELBQAwgcYxCzAJBgNV 43 | BAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUw 44 | IwYDVQQKExxTdGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTMwMQYDVQQLEypo 45 | dHRwOi8vY2VydHMuc3RhcmZpZWxkdGVjaC5jb20vcmVwb3NpdG9yeS8xNDAyBgNV 46 | BAMTK1N0YXJmaWVsZCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IC0gRzIw 47 | HhcNMTcwNjEyMTAxMjAwWhcNMTgwODExMjMxMjUwWjA4MSEwHwYDVQQLExhEb21h 48 | aW4gQ29udHJvbCBWYWxpZGF0ZWQxEzARBgNVBAMMCiouaWV0Zi5vcmcwggEiMA0G 49 | CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2eMubW2zWELh8023dSdAP3LvdsNeC 50 | KhPJZhIjdxr8o1+5PJ2MVMRgCaqe4asE5R+BuYfc9FDQCamqWOBZNvd3crwfhQW8 51 | NZBM9JLbUgyObyip3X2cTkbFaKsa7SgNHOFYsd7VFntmuiEI+D/U5yzLjtBm4raV 52 | oUHSsSatFYGYRhsOXf/DF/ld+oiqk7KckHTa2FetMJxMztHPUWoIW39lVkHmEpjZ 53 | L4JN0T04hUqWvhYcx+69Rh46PToaTAsUkc2/a1T62i8jeZhHFS5jhS6mRLcwL461 54 | 7LtcqbU/4g2NZah6CbqIIC3dW6ylXP7qlTbGCXeesBUxAcHh9F5A8fSlAgMBAAGj 55 | ggHOMIIByjAMBgNVHRMBAf8EAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEF 56 | BQcDAjAOBgNVHQ8BAf8EBAMCBaAwPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2Ny 57 | bC5zdGFyZmllbGR0ZWNoLmNvbS9zZmlnMnMxLTU2LmNybDBjBgNVHSAEXDBaME4G 58 | C2CGSAGG/W4BBxcBMD8wPQYIKwYBBQUHAgEWMWh0dHA6Ly9jZXJ0aWZpY2F0ZXMu 59 | c3RhcmZpZWxkdGVjaC5jb20vcmVwb3NpdG9yeS8wCAYGZ4EMAQIBMIGGBggrBgEF 60 | BQcBAQR6MHgwKgYIKwYBBQUHMAGGHmh0dHA6Ly9vY3NwLnN0YXJmaWVsZHRlY2gu 61 | Y29tLzBKBggrBgEFBQcwAoY+aHR0cDovL2NlcnRpZmljYXRlcy5zdGFyZmllbGR0 62 | ZWNoLmNvbS9yZXBvc2l0b3J5L3NmaWcyLmNydC5kZXIwHwYDVR0jBBgwFoAUJUWB 63 | aFAmOD07LSy+zWrZtj2zZmMwHwYDVR0RBBgwFoIKKi5pZXRmLm9yZ4IIaWV0Zi5v 64 | cmcwHQYDVR0OBBYEFAb+C6vY5nRu/MRzAoX3qUh+0TRPMA0GCSqGSIb3DQEBCwUA 65 | A4IBAQDkjdd7Mz2F83bfBNjAS0uN0mGIn2Z67dcWP+klzp7JzGb+qdbPZsI0aHKZ 66 | UEh0Pl71hcn8LlhYl+n7GJUGhW7CaOVqhzHkxfyfIls6BJ+pL6mIx5be8xqSV04b 67 | zyPBZcPnuFdi/dXAgjE9iSFHfNH8gthiXgzgiIPIjQp2xuJDeQHWT5ZQ5gUxF8qP 68 | ecO5L6IwMzZFRuE6SYzFynsOMOGjsPYJkYLm3JYwUulDz7OtRABwN5wegc5tTgq5 69 | 9HaFOULLCdMakLIRmMC0PzSI+m3+cYoZ6ue/8q9my7HgekcVMYQ5lRKncrs3GMxo 70 | WNyYOpbGqBfooA8nwwE20fpacX2i 71 | `); 72 | 73 | const rr = smimea.create(cert, 'slippinjimmy@example.com.', { 74 | ttl: 3600, 75 | usage: usages.DIC, 76 | selector: selectors.SPKI, 77 | matchingType: matchingTypes.SHA256 78 | }); 79 | 80 | assert(smimea.verify(rr, cert)); 81 | 82 | const rr2 = smimea.create(cert, 'slippinjimmy@example.com.', { 83 | ttl: 3600, 84 | usage: usages.DIC, 85 | selector: selectors.FULL, 86 | matchingType: matchingTypes.SHA256 87 | }); 88 | 89 | assert(smimea.verify(rr2, cert)); 90 | 91 | cert[30] ^= 1; 92 | 93 | assert(!smimea.verify(rr2, cert)); 94 | }); 95 | }); 96 | -------------------------------------------------------------------------------- /test/srv-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* eslint prefer-arrow-callback: "off" */ 3 | 4 | 'use strict'; 5 | 6 | const assert = require('bsert'); 7 | const srv = require('../lib/srv'); 8 | const wire = require('../lib/wire'); 9 | 10 | // $ dig.js _xmpp-server._tcp.gmail.com SRV 11 | const xmpp = ` 12 | _xmpp-server._tcp.gmail.com. 900 IN SRV 20 0 5269 alt1.xmpp-server.l.google.com. 13 | _xmpp-server._tcp.gmail.com. 900 IN SRV 20 0 5269 alt3.xmpp-server.l.google.com. 14 | _xmpp-server._tcp.gmail.com. 900 IN SRV 20 0 5269 alt2.xmpp-server.l.google.com. 15 | _xmpp-server._tcp.gmail.com. 900 IN SRV 5 0 5269 xmpp-server.l.google.com. 16 | _xmpp-server._tcp.gmail.com. 900 IN SRV 20 0 5269 alt4.xmpp-server.l.google.com. 17 | `; 18 | 19 | describe('SRV', function() { 20 | it('should serialize name', () => { 21 | const name = srv.encodeName('example.com', 'tcp', 'smtp'); 22 | 23 | assert.strictEqual(name, '_smtp._tcp.example.com.'); 24 | assert(srv.isName(name)); 25 | assert(!srv.isName('example.com.')); 26 | 27 | const data = srv.decodeName(name); 28 | 29 | assert.strictEqual(data.name, 'example.com.'); 30 | assert.strictEqual(data.protocol, 'tcp'); 31 | assert.strictEqual(data.service, 'smtp'); 32 | }); 33 | 34 | it('should parse SRV records', () => { 35 | wire.fromZone(xmpp); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /test/sshfp-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* eslint prefer-arrow-callback: "off" */ 3 | 4 | 'use strict'; 5 | 6 | const assert = require('bsert'); 7 | const sshfp = require('../lib/sshfp'); 8 | const {algs, hashes} = sshfp; 9 | 10 | describe('SSHFP', function() { 11 | it('should create SSHFP and verify', () => { 12 | const key = Buffer.alloc(32, 0x01); 13 | const rr = sshfp.create(key, 'example.com.', algs.ED25519, hashes.SHA256); 14 | 15 | assert(sshfp.verify(rr, key)); 16 | 17 | key[0] ^= 1; 18 | 19 | assert(!sshfp.verify(rr, key)); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /test/stub-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* eslint prefer-arrow-callback: "off" */ 3 | 4 | 'use strict'; 5 | 6 | const assert = require('bsert'); 7 | const dns = require('dns').promises; 8 | const api = require('../lib/dns'); 9 | const util = require('../lib/util'); 10 | 11 | const servers = [ 12 | '8.8.8.8', 13 | '8.8.4.4' 14 | ]; 15 | 16 | function sort(items) { 17 | if (!Array.isArray(items)) 18 | return items; 19 | 20 | return items.slice().sort((a, b) => { 21 | const x = JSON.stringify(a); 22 | const y = JSON.stringify(b); 23 | return util.compare(x, y); 24 | }); 25 | } 26 | 27 | if (process.browser) 28 | return; 29 | 30 | describe('Stub', function() { 31 | this.timeout(20000); 32 | 33 | const bns = new api.Resolver(); 34 | 35 | it('should resolve', async () => { 36 | dns.setServers(servers); 37 | bns.setServers(servers); 38 | 39 | const test = async (method, name, ...args) => { 40 | const x = await dns[method](name, ...args); 41 | const y = await bns[method](name, ...args); 42 | assert.deepStrictEqual(sort(x), sort(y), `dns.${method}('${name}')`); 43 | }; 44 | 45 | // await test('lookup', 'icanhazip.com'); 46 | // await test('lookupService', '172.217.0.46', 80); 47 | // await test('resolveAny', 'google.com'); 48 | await test('resolve4', 'icanhazip.com'); 49 | await test('resolve6', 'icanhazip.com'); 50 | await test('resolveCname', 'mail.google.com'); 51 | await test('resolveMx', 'google.com'); 52 | await test('resolveNaptr', 'apple.com'); 53 | // await test('resolvePtr', '46.0.217.172.in-addr.arpa.'); 54 | // await test('resolveSoa', 'google.com'); 55 | await test('resolveSrv', '_xmpp-server._tcp.gmail.com'); 56 | await test('resolveTxt', 'google.com'); 57 | // await test('reverse', '172.217.0.46'); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /test/tsig-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* eslint prefer-arrow-callback: "off" */ 3 | 4 | 'use strict'; 5 | 6 | const assert = require('bsert'); 7 | const tsig = require('../lib/tsig'); 8 | const wire = require('../lib/wire'); 9 | const {algs} = tsig; 10 | 11 | const { 12 | types, 13 | Message, 14 | Question, 15 | Record 16 | } = wire; 17 | 18 | describe('TSIG', function() { 19 | it('should sign and verify message', () => { 20 | const msg = new Message(); 21 | 22 | msg.qr = true; 23 | msg.ad = true; 24 | 25 | msg.question.push(Question.fromString('example.com. IN A')); 26 | msg.answer.push(Record.fromString('example.com. 300 IN A 172.217.0.46')); 27 | 28 | const secret = Buffer.alloc(32, 0x01); 29 | 30 | const msgRaw = msg.compress(); 31 | const signedRaw = tsig.sign(msgRaw, algs.SHA256, secret, null, false); 32 | 33 | const signed = Message.decode(signedRaw); 34 | assert(signed.tsig instanceof Record); 35 | assert(signed.tsig.type === types.TSIG); 36 | assert(signed.tsig.data.algorithm === algs.SHA256); 37 | assert(signed.tsig.data.mac.length === 32); 38 | 39 | assert(tsig.verify(signedRaw, secret, null, false)); 40 | 41 | secret[0] ^= 1; 42 | assert(!tsig.verify(signedRaw, secret, null, false)); 43 | }); 44 | 45 | it('should sign and verify message (timersOnly)', () => { 46 | const msg = new Message(); 47 | 48 | msg.qr = true; 49 | msg.ad = true; 50 | 51 | msg.question.push(Question.fromString('example.com. IN A')); 52 | msg.answer.push(Record.fromString('example.com. 300 IN A 172.217.0.46')); 53 | 54 | const secret = Buffer.alloc(32, 0x01); 55 | 56 | const msgRaw = msg.compress(); 57 | const signedRaw = tsig.sign(msgRaw, algs.SHA256, secret, null, true); 58 | 59 | const signed = Message.decode(signedRaw); 60 | assert(signed.tsig instanceof Record); 61 | assert(signed.tsig.type === types.TSIG); 62 | assert(signed.tsig.data.algorithm === algs.SHA256); 63 | assert(signed.tsig.data.mac.length === 32); 64 | 65 | assert(tsig.verify(signedRaw, secret, null, true)); 66 | 67 | secret[0] ^= 1; 68 | assert(!tsig.verify(signedRaw, secret, null, true)); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /test/wire-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* eslint prefer-arrow-callback: "off" */ 3 | 4 | 'use strict'; 5 | 6 | const assert = require('bsert'); 7 | const Path = require('path'); 8 | const fs = require('bfile'); 9 | const wire = require('../lib/wire'); 10 | const {Message, Record} = wire; 11 | 12 | const ROOT_ZONE = Path.resolve(__dirname, 'data', 'root.zone'); 13 | const ROOT_JSON = Path.resolve(__dirname, 'data', 'root.json'); 14 | const MSG_RAW = Path.resolve(__dirname, 'data', 'msg-raw.json'); 15 | const MSG_JSON = Path.resolve(__dirname, 'data', 'msg-json.json'); 16 | 17 | describe('Wire', function() { 18 | this.timeout(20000); 19 | 20 | describe('Root Zone File', function() { 21 | let rootZone = null; 22 | let rootJson = null; 23 | let rootRaw = null; 24 | 25 | it('should parse root zone file', () => { 26 | rootZone = wire.fromZone(fs.readFileSync(ROOT_ZONE, 'utf8')); 27 | rootJson = JSON.parse(fs.readFileSync(ROOT_JSON, 'utf8')); 28 | rootRaw = rootJson.map(hex => Buffer.from(hex, 'hex')); 29 | 30 | assert.strictEqual(rootZone.length, 22540); 31 | assert.strictEqual(rootJson.length, 22540); 32 | assert.strictEqual(rootRaw.length, 22540); 33 | 34 | assert.strictEqual(rootZone.length, rootJson.length); 35 | assert.strictEqual(rootZone.length, rootRaw.length); 36 | }); 37 | 38 | it('should compare zone to raw', () => { 39 | for (let i = 0; i < rootZone.length; i++) 40 | assert.bufferEqual(rootZone[i].encode(), rootRaw[i]); 41 | }); 42 | 43 | it('should compare raw to zone', () => { 44 | for (let i = 0; i < rootZone.length; i++) 45 | assert.deepStrictEqual(Record.decode(rootRaw[i]), rootZone[i]); 46 | }); 47 | 48 | it('should reserialize', () => { 49 | for (let i = 0; i < rootZone.length; i++) { 50 | const rr = rootZone[i]; 51 | assert.deepStrictEqual(Record.decode(rr.encode()), rr); 52 | } 53 | }); 54 | 55 | it('should reencode', () => { 56 | for (let i = 0; i < rootZone.length; i++) { 57 | const rr = rootZone[i]; 58 | 59 | assert.deepStrictEqual(Record.fromString(rr.toString()), rr); 60 | } 61 | }); 62 | 63 | it('should rejson', () => { 64 | for (let i = 0; i < rootZone.length; i++) { 65 | const rr = rootZone[i]; 66 | assert.deepStrictEqual(Record.fromJSON(rr.toJSON()), rr); 67 | } 68 | }); 69 | }); 70 | 71 | describe('Messages', function() { 72 | const msgJson_ = JSON.parse(fs.readFileSync(MSG_JSON, 'utf8')); 73 | const msgRaw_ = JSON.parse(fs.readFileSync(MSG_RAW, 'utf8')); 74 | 75 | let msgJson = null; 76 | let msgRaw = null; 77 | 78 | const clean = (msg) => { 79 | assert(msg instanceof Message); 80 | msg.size = 0; 81 | msg.malformed = false; 82 | msg.trailing = Buffer.alloc(0); 83 | }; 84 | 85 | const deepStrictEqual = (x, y) => { 86 | assert.deepStrictEqual(clean(x), clean(y)); 87 | }; 88 | 89 | it('should parse messages', () => { 90 | msgJson = msgJson_.map(json => Message.fromJSON(json)); 91 | msgRaw = msgRaw_.map(hex => Buffer.from(hex, 'hex')); 92 | 93 | assert.strictEqual(msgJson_.length, 280); 94 | assert.strictEqual(msgRaw_.length, 280); 95 | assert.strictEqual(msgJson.length, 280); 96 | assert.strictEqual(msgRaw.length, 280); 97 | assert.strictEqual(msgJson.length, msgRaw.length); 98 | }); 99 | 100 | it('should compare raw to json', () => { 101 | for (let i = 0; i < msgJson.length; i++) 102 | deepStrictEqual(Message.decode(msgRaw[i]), msgJson[i]); 103 | }); 104 | 105 | it('should reserialize', () => { 106 | for (let i = 0; i < msgJson.length; i++) { 107 | const msg = msgJson[i]; 108 | deepStrictEqual(Message.decode(msg.encode()), msg); 109 | } 110 | }); 111 | 112 | it('should compress', () => { 113 | for (let i = 0; i < msgJson.length; i++) { 114 | const msg = msgJson[i]; 115 | deepStrictEqual(Message.decode(msg.compress()), msg); 116 | } 117 | }); 118 | 119 | it('should not compress next domain in NSEC records', () => { 120 | const msg = msgJson[279]; 121 | const raw = msgRaw[279]; 122 | assert(msg instanceof Message); 123 | assert(raw instanceof Buffer); 124 | assert.strictEqual(msg.id, 63591); 125 | assert(raw.equals(msg.compress())); 126 | }); 127 | 128 | it('should reencode', () => { 129 | for (let i = 0; i < msgJson.length; i++) { 130 | const msg = msgJson[i]; 131 | deepStrictEqual(Message.fromString(msg.toString()), msg); 132 | } 133 | }); 134 | 135 | it('should rejson', () => { 136 | for (let i = 0; i < msgJson.length; i++) { 137 | const msg = msgJson[i]; 138 | deepStrictEqual(Message.fromJSON(msg.toJSON()), msg); 139 | } 140 | }); 141 | }); 142 | }); 143 | -------------------------------------------------------------------------------- /test/zone-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* eslint prefer-arrow-callback: "off" */ 3 | 4 | 'use strict'; 5 | 6 | const assert = require('bsert'); 7 | const Path = require('path'); 8 | const fs = require('bfile'); 9 | const wire = require('../lib/wire'); 10 | const Zone = require('../lib/zone'); 11 | const {types, codes} = wire; 12 | 13 | const ROOT_ZONE = Path.resolve(__dirname, 'data', 'root.zone'); 14 | const COM_RESPONSE = Path.resolve(__dirname, 'data', 'com-response.zone'); 15 | const COM_GLUE = Path.resolve(__dirname, 'data', 'com-glue.zone'); 16 | const NX_RESPONSE = Path.resolve(__dirname, 'data', 'nx-response.zone'); 17 | 18 | const comResponse = fs.readFileSync(COM_RESPONSE, 'utf8'); 19 | const comGlue = fs.readFileSync(COM_GLUE, 'utf8'); 20 | const nxResponse = fs.readFileSync(NX_RESPONSE, 'utf8'); 21 | 22 | describe('Zone', function() { 23 | this.timeout(10000); 24 | 25 | it('should serve root zone', () => { 26 | const zone = Zone.fromFile('.', ROOT_ZONE); 27 | 28 | assert.strictEqual(zone.names.size, 5717); 29 | 30 | { 31 | const msg = zone.resolve('com.', types.NS); 32 | assert(msg.code === codes.NOERROR); 33 | assert(!msg.aa); 34 | 35 | const expect = wire.fromZone(comResponse); 36 | 37 | assert.deepStrictEqual(msg.authority, expect); 38 | 39 | const glue = wire.fromZone(comGlue); 40 | 41 | assert.deepStrictEqual(msg.additional, glue); 42 | } 43 | 44 | { 45 | const msg = zone.resolve('idontexist.', types.A); 46 | assert(msg.code === codes.NXDOMAIN); 47 | assert(!msg.aa); 48 | assert(msg.answer.length === 0); 49 | 50 | const expect = wire.fromZone(nxResponse); 51 | 52 | assert.deepStrictEqual(msg.authority, expect); 53 | } 54 | }); 55 | }); 56 | --------------------------------------------------------------------------------