├── test ├── __init__.py ├── parser │ ├── __init__.py │ └── test_parser.py ├── formatter │ ├── __init__.py │ └── test_formatter.py └── generator │ ├── __init__.py │ └── test_generator.py ├── dnsstamps ├── parser │ ├── __init__.py │ ├── state.py │ └── parser.py ├── formatter │ ├── __init__.py │ └── formatter.py ├── option.py ├── protocol.py ├── generator │ ├── __init__.py │ └── generator.py ├── __init__.py └── parameter.py ├── .gitignore ├── setup.py ├── LICENSE ├── README.md └── bin └── dnsstamp.py /test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/parser/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/formatter/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/generator/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dnsstamps/parser/__init__.py: -------------------------------------------------------------------------------- 1 | from .parser import parse 2 | -------------------------------------------------------------------------------- /dnsstamps/formatter/__init__.py: -------------------------------------------------------------------------------- 1 | from .formatter import format 2 | -------------------------------------------------------------------------------- /dnsstamps/option.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class Option(Enum): 5 | DNSSEC = 1 6 | NO_LOGS = 2 7 | NO_FILTERS = 3 8 | -------------------------------------------------------------------------------- /dnsstamps/protocol.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class Protocol(Enum): 5 | PLAIN = 0 6 | DNSCRYPT = 1 7 | DOH = 2 8 | DOT = 3 9 | DOQ = 4 10 | DOH_TARGET = 5 11 | DNSCRYPT_RELAY = 129 12 | DOH_RELAY = 133 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | ### STS ### 4 | .classpath 5 | .factorypath 6 | .project 7 | .settings 8 | .springBeans 9 | 10 | ### IntelliJ IDEA ### 11 | .idea 12 | *.iws 13 | *.iml 14 | *.ipr 15 | 16 | ### Other ### 17 | build 18 | dist 19 | dnsstamps.egg-info 20 | -------------------------------------------------------------------------------- /dnsstamps/parser/state.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | 4 | class State: 5 | 6 | def __init__(self): 7 | self._data = b'' 8 | 9 | @property 10 | def data(self): 11 | return self._data 12 | 13 | @data.setter 14 | def data(self, data): 15 | self._data = data 16 | -------------------------------------------------------------------------------- /dnsstamps/generator/__init__.py: -------------------------------------------------------------------------------- 1 | from .generator import build 2 | from .generator import create_dnscrypt 3 | from .generator import create_dnscrypt_relay 4 | from .generator import create_doh 5 | from .generator import create_doh_relay 6 | from .generator import create_doh_target 7 | from .generator import create_dot 8 | from .generator import create_doq 9 | from .generator import create_plain 10 | from .generator import prepare_dnscrypt 11 | from .generator import prepare_dnscrypt_relay 12 | from .generator import prepare_doh 13 | from .generator import prepare_doh_relay 14 | from .generator import prepare_doh_target 15 | from .generator import prepare_dot 16 | from .generator import prepare_doq 17 | from .generator import prepare_plain 18 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | with open("README.md", "r") as fh: 4 | long_description = fh.read() 5 | 6 | setuptools.setup( 7 | name="dnsstamps", 8 | version="1.4.1", 9 | author="Christian Hofer", 10 | author_email="chrisss404@gmail.com", 11 | description="Create and parse DNS stamps with ease.", 12 | long_description=long_description, 13 | long_description_content_type="text/markdown", 14 | url="https://github.com/chrisss404/python-dnsstamps", 15 | packages=setuptools.find_packages(), 16 | classifiers=[ 17 | "Programming Language :: Python :: 3", 18 | "License :: OSI Approved :: MIT License", 19 | "Operating System :: OS Independent", 20 | ], 21 | scripts=['bin/dnsstamp.py'], 22 | ) 23 | -------------------------------------------------------------------------------- /dnsstamps/__init__.py: -------------------------------------------------------------------------------- 1 | from .option import Option 2 | from .protocol import Protocol 3 | 4 | from .parameter import Parameter 5 | 6 | from .generator import build 7 | from .generator import create_dnscrypt 8 | from .generator import create_dnscrypt_relay 9 | from .generator import create_doh 10 | from .generator import create_doh_relay 11 | from .generator import create_doh_target 12 | from .generator import create_dot 13 | from .generator import create_doq 14 | from .generator import create_plain 15 | from .generator import prepare_dnscrypt 16 | from .generator import prepare_dnscrypt_relay 17 | from .generator import prepare_doh 18 | from .generator import prepare_doh_relay 19 | from .generator import prepare_doh_target 20 | from .generator import prepare_dot 21 | from .generator import prepare_doq 22 | from .generator import prepare_plain 23 | 24 | from .parser import parse 25 | 26 | from .formatter import format 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 The Python Packaging Authority 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /dnsstamps/parameter.py: -------------------------------------------------------------------------------- 1 | from dnsstamps import Protocol 2 | 3 | 4 | class Parameter: 5 | 6 | def __init__(self): 7 | self._protocol = Protocol.PLAIN 8 | self._options = [] 9 | self._address = '127.0.0.1' 10 | self._public_key = b'' 11 | self._provider_name = '' 12 | self._hashes = [] 13 | self._hostname = '' 14 | self._path = '' 15 | self._bootstrap_ips = [] 16 | 17 | @property 18 | def protocol(self): 19 | return self._protocol 20 | 21 | @protocol.setter 22 | def protocol(self, protocol): 23 | if isinstance(protocol, Protocol): 24 | self._protocol = protocol 25 | else: 26 | raise ValueError('Unrecognized protocol <%s>' % protocol) 27 | 28 | @property 29 | def options(self): 30 | return self._options 31 | 32 | @options.setter 33 | def options(self, options): 34 | self._options = options 35 | 36 | @property 37 | def address(self): 38 | return self._address 39 | 40 | @address.setter 41 | def address(self, address): 42 | self._address = address 43 | 44 | @property 45 | def public_key(self): 46 | return self._public_key 47 | 48 | @public_key.setter 49 | def public_key(self, public_key): 50 | self._public_key = public_key 51 | 52 | @property 53 | def provider_name(self): 54 | return self._provider_name 55 | 56 | @provider_name.setter 57 | def provider_name(self, provider_name): 58 | self._provider_name = provider_name 59 | 60 | @property 61 | def hashes(self): 62 | return self._hashes 63 | 64 | @hashes.setter 65 | def hashes(self, hashes): 66 | self._hashes = hashes 67 | 68 | @property 69 | def hostname(self): 70 | return self._hostname 71 | 72 | @hostname.setter 73 | def hostname(self, hostname): 74 | self._hostname = hostname 75 | 76 | @property 77 | def path(self): 78 | return self._path 79 | 80 | @path.setter 81 | def path(self, path): 82 | self._path = path 83 | 84 | @property 85 | def bootstrap_ips(self): 86 | return self._bootstrap_ips 87 | 88 | @bootstrap_ips.setter 89 | def bootstrap_ips(self, bootstrap_ips): 90 | self._bootstrap_ips = bootstrap_ips 91 | -------------------------------------------------------------------------------- /test/formatter/test_formatter.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import dnsstamps 4 | 5 | 6 | class TestPrinter(unittest.TestCase): 7 | 8 | def test_format_with_invalid_parameter_type(self): 9 | with self.assertRaises(Exception) as context: 10 | dnsstamps.format(None) 11 | self.assertEqual( 12 | "Invalid parameter type ", 13 | str(context.exception), 14 | "Invalid parameter type") 15 | 16 | def test_format_plain_stamp(self): 17 | address = "[fe80::6d6d:f72c:3ad:60b8]" 18 | parameter = dnsstamps.prepare_plain(address) 19 | dnsstamps.format(parameter) 20 | 21 | def test_format_dnscrypt_stamp(self): 22 | address = "[fe80::6d6d:f72c:3ad:60b8]" 23 | public_key = "CB6A:DC5C:29F9:5510:0B65:BF12:94FE:5684:579A:B349:9CC9:798F:00D0:1BB5:C1A9:A2C7" 24 | provider_name = "2.dnscrypt-cert.example.com" 25 | parameter = dnsstamps.prepare_dnscrypt(address, public_key, provider_name) 26 | dnsstamps.format(parameter) 27 | 28 | def test_format_doh_stamp(self): 29 | address = "[fe80::6d6d:f72c:3ad:60b8]" 30 | hashes = ["3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838"] 31 | hostname = "doh.example.com" 32 | path = "/dns-query" 33 | parameter = dnsstamps.prepare_doh(address, hashes, hostname, path) 34 | dnsstamps.format(parameter) 35 | 36 | def test_format_dot_stamp(self): 37 | address = "[fe80::6d6d:f72c:3ad:60b8]" 38 | hashes = ["3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838"] 39 | hostname = "dot.example.com" 40 | parameter = dnsstamps.prepare_dot(address, hashes, hostname) 41 | dnsstamps.format(parameter) 42 | 43 | def test_format_doq_stamp(self): 44 | address = "[fe80::6d6d:f72c:3ad:60b8]" 45 | hashes = ["3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838"] 46 | hostname = "doq.example.com" 47 | parameter = dnsstamps.prepare_doq(address, hashes, hostname) 48 | dnsstamps.format(parameter) 49 | 50 | def test_format_doh_target_stamp(self): 51 | hostname = "doh-target.example.com" 52 | path = "/dns-query" 53 | parameter = dnsstamps.prepare_doh_target(hostname, path) 54 | dnsstamps.format(parameter) 55 | 56 | def test_format_dnscrypt_relay_stamp(self): 57 | address = "[fe80::6d6d:f72c:3ad:60b8]:433" 58 | parameter = dnsstamps.prepare_dnscrypt_relay(address) 59 | dnsstamps.format(parameter) 60 | 61 | def test_format_doh_relay_stamp(self): 62 | address = "[fe80::6d6d:f72c:3ad:60b8]" 63 | hashes = ["3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838"] 64 | hostname = "doh-relay.example.com" 65 | path = "/dns-query" 66 | parameter = dnsstamps.prepare_doh_relay(address, hashes, hostname, path) 67 | dnsstamps.format(parameter) 68 | -------------------------------------------------------------------------------- /dnsstamps/formatter/formatter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from dnsstamps import Option 4 | from dnsstamps import Parameter 5 | from dnsstamps import Protocol 6 | from dnsstamps import build 7 | 8 | 9 | def print_options(parameter): 10 | print('DNSSEC: %s' % ('yes' if Option.DNSSEC in parameter.options else 'no')) 11 | print('No logs: %s' % ('yes' if Option.NO_LOGS in parameter.options else 'no')) 12 | print('No filter: %s' % ('yes' if Option.NO_FILTERS in parameter.options else 'no')) 13 | 14 | 15 | def print_plain(parameter): 16 | print('Plain DNS stamp') 17 | print('===============') 18 | print('') 19 | print_options(parameter) 20 | print('IP Address: %s' % parameter.address) 21 | print('') 22 | print(build(parameter)) 23 | 24 | 25 | def print_dnscrypt(parameter): 26 | print('DNSCrypt DNS stamp') 27 | print('==================') 28 | print('') 29 | print_options(parameter) 30 | print('IP Address: %s' % parameter.address) 31 | print('Public key: %s' % parameter.public_key) 32 | print('Provider name: %s' % parameter.provider_name) 33 | print('') 34 | print(build(parameter)) 35 | 36 | 37 | def print_doh(parameter): 38 | print('DoH DNS stamp') 39 | print('=============') 40 | print('') 41 | print_options(parameter) 42 | print('IP Address: %s' % parameter.address) 43 | print('Hashes: %s' % parameter.hashes) 44 | print('Hostname: %s' % parameter.hostname) 45 | print('Path: %s' % parameter.path) 46 | print('Bootstrap IPs: %s' % parameter.bootstrap_ips) 47 | print('') 48 | print(build(parameter)) 49 | 50 | 51 | def print_dot(parameter): 52 | print('DoT DNS stamp') 53 | print('=============') 54 | print('') 55 | print_options(parameter) 56 | print('IP Address: %s' % parameter.address) 57 | print('Hostname: %s' % parameter.hostname) 58 | print('Hashes: %s' % parameter.hashes) 59 | print('Bootstrap IPs: %s' % parameter.bootstrap_ips) 60 | print('') 61 | print(build(parameter)) 62 | 63 | 64 | def print_doq(parameter): 65 | print('DoQ DNS stamp') 66 | print('=============') 67 | print('') 68 | print_options(parameter) 69 | print('IP Address: %s' % parameter.address) 70 | print('Hostname: %s' % parameter.hostname) 71 | print('Hashes: %s' % parameter.hashes) 72 | print('Bootstrap IPs: %s' % parameter.bootstrap_ips) 73 | print('') 74 | print(build(parameter)) 75 | 76 | 77 | def print_doh_target(parameter): 78 | print('DoH Target DNS stamp') 79 | print('====================') 80 | print('') 81 | print_options(parameter) 82 | print('Hostname: %s' % parameter.hostname) 83 | print('Path: %s' % parameter.path) 84 | print('') 85 | print(build(parameter)) 86 | 87 | 88 | def print_dnscrypt_relay(parameter): 89 | print('DNSCrypt DNS Relay Stamp') 90 | print('========================') 91 | print('') 92 | print('IP Address: %s' % parameter.address) 93 | print('') 94 | print(build(parameter)) 95 | 96 | 97 | def print_doh_relay(parameter): 98 | print('DoH Relay DNS stamp') 99 | print('===================') 100 | print('') 101 | print_options(parameter) 102 | print('IP Address: %s' % parameter.address) 103 | print('Hashes: %s' % parameter.hashes) 104 | print('Hostname: %s' % parameter.hostname) 105 | print('Path: %s' % parameter.path) 106 | print('Bootstrap IPs: %s' % parameter.bootstrap_ips) 107 | print('') 108 | print(build(parameter)) 109 | 110 | 111 | def format(parameter): 112 | if not isinstance(parameter, Parameter): 113 | raise ValueError('Invalid parameter type %s' % type(parameter)) 114 | 115 | if parameter.protocol == Protocol.PLAIN: 116 | return print_plain(parameter) 117 | elif parameter.protocol == Protocol.DNSCRYPT: 118 | return print_dnscrypt(parameter) 119 | elif parameter.protocol == Protocol.DOH: 120 | return print_doh(parameter) 121 | elif parameter.protocol == Protocol.DOT: 122 | return print_dot(parameter) 123 | elif parameter.protocol == Protocol.DOQ: 124 | return print_doq(parameter) 125 | elif parameter.protocol == Protocol.DOH_TARGET: 126 | return print_doh_target(parameter) 127 | elif parameter.protocol == Protocol.DNSCRYPT_RELAY: 128 | return print_dnscrypt_relay(parameter) 129 | elif parameter.protocol == Protocol.DOH_RELAY: 130 | return print_doh_relay(parameter) 131 | -------------------------------------------------------------------------------- /dnsstamps/parser/parser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import base64 4 | import binascii 5 | import struct 6 | 7 | from dnsstamps import Option 8 | from dnsstamps import Parameter 9 | from dnsstamps import Protocol 10 | from dnsstamps.parser.state import State 11 | 12 | 13 | def create_state_for_stamp(stamp): 14 | try: 15 | state = State() 16 | state.data = base64.urlsafe_b64decode(stamp.replace('sdns://', '') + '===') 17 | return state 18 | except Exception as e: 19 | raise Exception('Unable to unpack stamp', e) 20 | 21 | 22 | def consume_protocol(state): 23 | try: 24 | raw_protocol = struct.unpack(' printDNSCryptProviderFingerprint("/path/to/your/providerPublic.key") 16 | Provider fingerprint is: CB6A:DC5C:29F9:5510:0B65:BF12:94FE:5684:579A:B349:9CC9:798F:00D0:1BB5:C1A9:A2C7 17 | 18 | Then run 19 | 20 | $ dnsstamp.py dnscrypt -s -a 127.0.0.1 -n 2.dnscrypt-cert.example.com -k CB6A:DC5C:29F9:5510:0B65:BF12:94FE:5684:579A:B349:9CC9:798F:00D0:1BB5:C1A9:A2C7 21 | DNSCrypt DNS stamp 22 | ================== 23 | 24 | DNSSEC: yes 25 | No logs: no 26 | No filter: no 27 | IP Address: 127.0.0.1 28 | Public key: CB6A:DC5C:29F9:5510:0B65:BF12:94FE:5684:579A:B349:9CC9:798F:00D0:1BB5:C1A9:A2C7 29 | Provider name: 2.dnscrypt-cert.example.com 30 | 31 | sdns://AQEAAAAAAAAACTEyNy4wLjAuMSDLatxcKflVEAtlvxKU_laEV5qzSZzJeY8A0Bu1wamixxsyLmRuc2NyeXB0LWNlcnQuZXhhbXBsZS5jb20 32 | 33 | 34 | ### DNS-over-HTTPS 35 | 36 | First get your certificate's signed data hash (tbsCertificate) 37 | 38 | $ openssl asn1parse -in doh.example.com.chain.pem -out /dev/stdout -noout -strparse 4 | openssl dgst -sha256 39 | (stdin)= 3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838 40 | 41 | Then run 42 | 43 | $ dnsstamp.py doh -s -a 127.0.0.1 -n doh.example.com -p /dns-query -t 3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838 44 | DoH DNS stamp 45 | ============= 46 | 47 | DNSSEC: yes 48 | No logs: no 49 | No filter: no 50 | IP Address: 127.0.0.1 51 | Hashes: ['3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838'] 52 | Hostname: doh.example.com 53 | Path: /dns-query 54 | Bootstrap IPs: [] 55 | 56 | sdns://AgEAAAAAAAAACTEyNy4wLjAuMSA-GhoPbFPz6XpJLVcIS1uYBwWe4FerFQWHb9g_2j24OA9kb2guZXhhbXBsZS5jb20KL2Rucy1xdWVyeQ 57 | 58 | 59 | ### DNS-over-TLS 60 | 61 | First get your certificate's signed data hash (tbsCertificate) 62 | 63 | $ openssl asn1parse -in dot.example.com.chain.pem -out /dev/stdout -noout -strparse 4 | openssl dgst -sha256 64 | (stdin)= 2f1af500a66d4b83760766e41cb1123ebd6b95853afaef3bcdf39cbde3ab30b6 65 | 66 | Then run 67 | 68 | $ dnsstamp.py dot -s -a 127.0.0.1 -n dot.example.com -t 2f1af500a66d4b83760766e41cb1123ebd6b95853afaef3bcdf39cbde3ab30b6 69 | DoT DNS stamp 70 | ============= 71 | 72 | DNSSEC: yes 73 | No logs: no 74 | No filter: no 75 | IP Address: 127.0.0.1 76 | Hostname: dot.example.com 77 | Hashes: ['2f1af500a66d4b83760766e41cb1123ebd6b95853afaef3bcdf39cbde3ab30b6'] 78 | Bootstrap IPs: [] 79 | 80 | sdns://AwEAAAAAAAAACTEyNy4wLjAuMSAvGvUApm1Lg3YHZuQcsRI-vWuVhTr67zvN85y946swtg9kb3QuZXhhbXBsZS5jb20 81 | 82 | 83 | ### DNS-over-QUIC 84 | 85 | First get your certificate's signed data hash (tbsCertificate) 86 | 87 | $ openssl asn1parse -in doq.example.com.chain.pem -out /dev/stdout -noout -strparse 4 | openssl dgst -sha256 88 | (stdin)= 2449d49a44b91e80e0c71eeab76627579b6d4e53b4445cd5479004cc93d23263 89 | 90 | Then run 91 | 92 | $ dnsstamp.py doq -s -a 127.0.0.1 -n doq.example.com -t 2449d49a44b91e80e0c71eeab76627579b6d4e53b4445cd5479004cc93d23263 93 | DoQ DNS stamp 94 | ============= 95 | 96 | DNSSEC: yes 97 | No logs: no 98 | No filter: no 99 | IP Address: 127.0.0.1 100 | Hostname: doq.example.com 101 | Hashes: ['2449d49a44b91e80e0c71eeab76627579b6d4e53b4445cd5479004cc93d23263'] 102 | Bootstrap IPs: [] 103 | 104 | sdns://BAEAAAAAAAAACTEyNy4wLjAuMSAkSdSaRLkegODHHuq3ZidXm21OU7REXNVHkATMk9IyYw9kb3EuZXhhbXBsZS5jb20 105 | 106 | 107 | ### Oblivious DoH target 108 | 109 | $ dnsstamp.py doh_target -s -a 127.0.0.1 -n doh-target.example.com -p /dns-query 110 | DoH Target DNS stamp 111 | ==================== 112 | 113 | DNSSEC: yes 114 | No logs: no 115 | No filter: no 116 | Hostname: doh-target.example.com 117 | Path: /dns-query 118 | 119 | sdns://BQEAAAAAAAAAFmRvaC10YXJnZXQuZXhhbXBsZS5jb20KL2Rucy1xdWVyeQ 120 | 121 | 122 | ### Anonymized DNSCrypt relay 123 | 124 | $ dnsstamp.py dnscrypt_relay -a 127.0.0.1 125 | DNSCrypt DNS Relay Stamp 126 | ======================== 127 | 128 | IP Address: 127.0.0.1 129 | 130 | sdns://gQkxMjcuMC4wLjE 131 | 132 | 133 | ### Oblivious DoH relay 134 | 135 | First get your certificate's signed data hash (tbsCertificate) 136 | 137 | $ openssl asn1parse -in doh.example.com.chain.pem -out /dev/stdout -noout -strparse 4 | openssl dgst -sha256 138 | (stdin)= 3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838 139 | 140 | Then run 141 | 142 | $ dnsstamp.py doh_relay -a 127.0.0.1 -n doh-relay.example.com -p /dns-query -t 3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838 143 | DoH Relay DNS stamp 144 | =================== 145 | 146 | DNSSEC: no 147 | No logs: no 148 | No filter: no 149 | IP Address: 127.0.0.1 150 | Hashes: ['3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838'] 151 | Hostname: doh-relay.example.com 152 | Path: /dns-query 153 | Bootstrap IPs: [] 154 | 155 | sdns://hQAAAAAAAAAACTEyNy4wLjAuMSA-GhoPbFPz6XpJLVcIS1uYBwWe4FerFQWHb9g_2j24OBVkb2gtcmVsYXkuZXhhbXBsZS5jb20KL2Rucy1xdWVyeQ 156 | 157 | 158 | ### Plain DNS 159 | 160 | $ dnsstamp.py plain -s -a 127.0.0.1 161 | Plain DNS stamp 162 | =============== 163 | 164 | DNSSEC: yes 165 | No logs: no 166 | No filter: no 167 | IP Address: 127.0.0.1 168 | 169 | sdns://AAEAAAAAAAAACTEyNy4wLjAuMQ 170 | 171 | 172 | ## Parsing DNS stamps 173 | 174 | $ dnsstamp.py parse sdns://AAEAAAAAAAAACTEyNy4wLjAuMQ 175 | Plain DNS stamp 176 | =============== 177 | 178 | DNSSEC: yes 179 | No logs: no 180 | No filter: no 181 | IP Address: 127.0.0.1 182 | 183 | sdns://AAEAAAAAAAAACTEyNy4wLjAuMQ 184 | 185 | 186 | ## Using the library 187 | 188 | import dnsstamps 189 | from dnsstamps import Option 190 | 191 | # DNSCrypt 192 | stamp = dnsstamps.create_dnscrypt("127.0.0.1", "CB6A:DC5C", "provider-name", [Option.DNSSEC]) 193 | 194 | # DNS-over-HTTPS 195 | stamp = dnsstamps.create_doh("127.0.0.1", ["3e1a1a0f"], "hostname", "path", [Option.NO_LOGS]) 196 | 197 | # DNS-over-TLS 198 | stamp = dnsstamps.create_dot("127.0.0.1", ["d0b24377"], "hostname", [Option.NO_FILTERS]) 199 | 200 | # DNS-over-QUIC 201 | stamp = dnsstamps.create_doq("127.0.0.1", ["e50ff421"], "hostname", [Option.NO_FILTERS]) 202 | 203 | # Oblivious DoH target 204 | stamp = dnsstamps.create_doh_target("hostname", "path", [Option.NO_LOGS]) 205 | 206 | # Anonymized DNSCrypt relay 207 | stamp = dnsstamps.create_dnscrypt_relay("127.0.0.1") 208 | 209 | # Oblivious DoH relay 210 | stamp = dnsstamps.create_doh_relay("127.0.0.1", ["3e1a1a0f"], "hostname", "path", [Option.NO_LOGS]) 211 | 212 | # Plain DNS 213 | stamp = dnsstamps.create_plain("127.0.0.1", [Option.DNSSEC, Option.NO_LOGS, Option.NO_FILTERS]) 214 | 215 | # Parse 216 | parameter = dnsstamps.parse("sdns://AAEAAAAAAAAACTEyNy4wLjAuMQ") 217 | dnsstamps.format(parameter) 218 | stamp = dnsstamps.build(parameter) 219 | 220 | 221 | ## Running tests 222 | 223 | python3 -m unittest discover 224 | 225 | 226 | ## Setting up your own DNS server 227 | 228 | * [Unbound](https://github.com/jedisct1/dnscrypt-proxy/wiki/How-to-setup-your-own-DNSCrypt-server-in-less-than-10-minutes) (DNSSEC, DNSCrypt) 229 | * [PowerDNS](https://github.com/chrisss404/powerdns#private-recursor) (DNSSEC, DNSCrypt, DoH, DoT, Authoritative Server) 230 | 231 | 232 | ## Updating PyPI package 233 | 234 | python3 setup.py sdist bdist_wheel 235 | python3 -m twine upload dist/* 236 | 237 | 238 | -------------------------------------------------------------------------------- /dnsstamps/generator/generator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import base64 4 | import binascii 5 | import struct 6 | 7 | from dnsstamps import Option 8 | from dnsstamps import Parameter 9 | from dnsstamps import Protocol 10 | 11 | 12 | def pack_protocol(protocol): 13 | return struct.pack(" []') 38 | parser.add_argument('command', 39 | choices=['parse', 'plain', 'dnscrypt', 'doh', 'dot', 'doq', 'doh_target', 'dnscrypt_relay', 40 | 'doh_relay'], 41 | help='The command to execute.') 42 | 43 | args = parser.parse_args(sys.argv[1:2]) 44 | getattr(self, args.command)() 45 | 46 | def parse(self): 47 | parser = argparse.ArgumentParser(description='Parse DNS stamp.') 48 | 49 | parser.add_argument('stamp', type=str, help='The stamp to parse.') 50 | 51 | args = parser.parse_args(sys.argv[2:]) 52 | 53 | try: 54 | parameter = dnsstamps.parse(args.stamp) 55 | dnsstamps.format(parameter) 56 | except: 57 | print("Unable to parse DNS stamp <%s>" % args.stamp) 58 | 59 | def plain(self): 60 | parser = argparse.ArgumentParser(description='Create plain stamp') 61 | self.append_common_arguments(parser) 62 | 63 | args = parser.parse_args(sys.argv[2:]) 64 | 65 | options = [] 66 | if args.dnssec: 67 | options.append(Option.DNSSEC) 68 | if args.logs: 69 | options.append(Option.NO_LOGS) 70 | if args.filter: 71 | options.append(Option.NO_FILTERS) 72 | parameter = dnsstamps.prepare_plain("" if args.address is None else args.address, options) 73 | dnsstamps.format(parameter) 74 | 75 | def dnscrypt(self): 76 | parser = argparse.ArgumentParser(description='Create DNSCrypt stamp') 77 | self.append_common_arguments(parser) 78 | 79 | parser.add_argument('-k', '--public_key', 80 | required=True, 81 | type=str, 82 | help="the DNSCrypt public key (e.g.: CB6A:DC5C:29F9:5510:0B65:BF12:94FE:5684:579A:B349:9CC9:798F:00D0:1BB5:C1A9:A2C7)") 83 | parser.add_argument('-n', '--provider_name', 84 | required=True, 85 | type=str, 86 | help="the DNSCrypt provider name (e.g.: 2.dnscrypt-cert.example.com)") 87 | 88 | args = parser.parse_args(sys.argv[2:]) 89 | 90 | options = [] 91 | if args.dnssec: 92 | options.append(Option.DNSSEC) 93 | if args.logs: 94 | options.append(Option.NO_LOGS) 95 | if args.filter: 96 | options.append(Option.NO_FILTERS) 97 | parameter = dnsstamps.prepare_dnscrypt("" if args.address is None else args.address, args.public_key, 98 | args.provider_name, options) 99 | dnsstamps.format(parameter) 100 | 101 | def doh(self): 102 | parser = argparse.ArgumentParser(description='Create DNS-over-HTTPS stamp') 103 | self.append_common_arguments(parser) 104 | 105 | parser.add_argument('-t', '--hashes', 106 | type=str, 107 | help="a comma-separated list of tbs certificate hashes (e.g.: 3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838)") 108 | parser.add_argument('-n', '--hostname', 109 | required=True, 110 | type=str, 111 | help="the server hostname which will also be used as a SNI name (e.g.: doh.example.com)") 112 | parser.add_argument('-p', '--path', 113 | required=True, 114 | type=str, 115 | help="the absolute URI path (e.g.: /dns-query)") 116 | parser.add_argument('-b', '--bootstrap_ips', 117 | type=str, 118 | help="a comma-separated list of bootstrap ips (e.g.: 1.1.1.1,1.0.0.1)") 119 | 120 | args = parser.parse_args(sys.argv[2:]) 121 | 122 | options = [] 123 | if args.dnssec: 124 | options.append(Option.DNSSEC) 125 | if args.logs: 126 | options.append(Option.NO_LOGS) 127 | if args.filter: 128 | options.append(Option.NO_FILTERS) 129 | parameter = dnsstamps.prepare_doh("" if args.address is None else args.address, 130 | [] if args.hashes is None else args.hashes.split(','), args.hostname, 131 | args.path, options, 132 | [] if args.bootstrap_ips is None else args.bootstrap_ips.split(',')) 133 | dnsstamps.format(parameter) 134 | 135 | def dot(self): 136 | parser = argparse.ArgumentParser(description='Create DNS-over-TLS stamp') 137 | self.append_common_arguments(parser) 138 | 139 | parser.add_argument('-t', '--hashes', 140 | type=str, 141 | help="a comma-separated list of tbs certificate hashes (e.g.: 3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838)") 142 | parser.add_argument('-n', '--hostname', 143 | required=True, 144 | type=str, 145 | help="the server hostname which will also be used as a SNI name (e.g.: dot.example.com)") 146 | parser.add_argument('-b', '--bootstrap_ips', 147 | type=str, 148 | help="a comma-separated list of bootstrap ips (e.g.: 1.1.1.1,1.0.0.1)") 149 | 150 | args = parser.parse_args(sys.argv[2:]) 151 | 152 | options = [] 153 | if args.dnssec: 154 | options.append(Option.DNSSEC) 155 | if args.logs: 156 | options.append(Option.NO_LOGS) 157 | if args.filter: 158 | options.append(Option.NO_FILTERS) 159 | parameter = dnsstamps.prepare_dot("" if args.address is None else args.address, 160 | [] if args.hashes is None else args.hashes.split(','), args.hostname, options, 161 | [] if args.bootstrap_ips is None else args.bootstrap_ips.split(',')) 162 | dnsstamps.format(parameter) 163 | 164 | def doq(self): 165 | parser = argparse.ArgumentParser(description='Create DNS-over-QUIC stamp') 166 | self.append_common_arguments(parser) 167 | 168 | parser.add_argument('-t', '--hashes', 169 | type=str, 170 | help="a comma-separated list of tbs certificate hashes (e.g.: 3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838)") 171 | parser.add_argument('-n', '--hostname', 172 | required=True, 173 | type=str, 174 | help="the server hostname which will also be used as a SNI name (e.g.: doq.example.com)") 175 | parser.add_argument('-b', '--bootstrap_ips', 176 | type=str, 177 | help="a comma-separated list of bootstrap ips (e.g.: 1.1.1.1,1.0.0.1)") 178 | 179 | args = parser.parse_args(sys.argv[2:]) 180 | 181 | options = [] 182 | if args.dnssec: 183 | options.append(Option.DNSSEC) 184 | if args.logs: 185 | options.append(Option.NO_LOGS) 186 | if args.filter: 187 | options.append(Option.NO_FILTERS) 188 | parameter = dnsstamps.prepare_doq("" if args.address is None else args.address, 189 | [] if args.hashes is None else args.hashes.split(','), args.hostname, options, 190 | [] if args.bootstrap_ips is None else args.bootstrap_ips.split(',')) 191 | dnsstamps.format(parameter) 192 | 193 | def doh_target(self): 194 | parser = argparse.ArgumentParser(description='Create DoH target stamp') 195 | self.append_common_arguments(parser) 196 | 197 | parser.add_argument('-n', '--hostname', 198 | required=True, 199 | type=str, 200 | help="the server hostname which will also be used as a SNI name (e.g.: doh-target.example.com)") 201 | parser.add_argument('-p', '--path', 202 | required=True, 203 | type=str, 204 | help="the absolute URI path (e.g.: /dns-query)") 205 | 206 | args = parser.parse_args(sys.argv[2:]) 207 | 208 | options = [] 209 | if args.dnssec: 210 | options.append(Option.DNSSEC) 211 | if args.logs: 212 | options.append(Option.NO_LOGS) 213 | if args.filter: 214 | options.append(Option.NO_FILTERS) 215 | parameter = dnsstamps.prepare_doh_target(args.hostname, args.path, options) 216 | dnsstamps.format(parameter) 217 | 218 | def dnscrypt_relay(self): 219 | parser = argparse.ArgumentParser(description='Create DNSCrypt relay stamp') 220 | self.append_common_arguments(parser) 221 | 222 | args = parser.parse_args(sys.argv[2:]) 223 | 224 | parameter = dnsstamps.prepare_dnscrypt_relay("" if args.address is None else args.address) 225 | dnsstamps.format(parameter) 226 | 227 | def doh_relay(self): 228 | parser = argparse.ArgumentParser(description='Create DoH relay stamp') 229 | self.append_common_arguments(parser) 230 | 231 | parser.add_argument('-t', '--hashes', 232 | type=str, 233 | help="a comma-separated list of tbs certificate hashes (e.g.: 3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838)") 234 | parser.add_argument('-n', '--hostname', 235 | required=True, 236 | type=str, 237 | help="the server hostname which will also be used as a SNI name (e.g.: doh-relay.example.com)") 238 | parser.add_argument('-p', '--path', 239 | required=True, 240 | type=str, 241 | help="the absolute URI path (e.g.: /dns-query)") 242 | parser.add_argument('-b', '--bootstrap_ips', 243 | type=str, 244 | help="a comma-separated list of bootstrap ips (e.g.: 1.1.1.1,1.0.0.1)") 245 | 246 | args = parser.parse_args(sys.argv[2:]) 247 | 248 | options = [] 249 | if args.dnssec: 250 | options.append(Option.DNSSEC) 251 | if args.logs: 252 | options.append(Option.NO_LOGS) 253 | if args.filter: 254 | options.append(Option.NO_FILTERS) 255 | parameter = dnsstamps.prepare_doh_relay("" if args.address is None else args.address, 256 | [] if args.hashes is None else args.hashes.split(','), args.hostname, 257 | args.path, options, 258 | [] if args.bootstrap_ips is None else args.bootstrap_ips.split(',')) 259 | dnsstamps.format(parameter) 260 | 261 | 262 | if __name__ == '__main__': 263 | DnsStampCli() 264 | -------------------------------------------------------------------------------- /test/generator/test_generator.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import dnsstamps 4 | from dnsstamps import Option 5 | from dnsstamps import Parameter 6 | 7 | 8 | class TestGenerator(unittest.TestCase): 9 | 10 | def test_build_with_invalid_parameter_type(self): 11 | with self.assertRaises(Exception) as context: 12 | dnsstamps.build(None) 13 | self.assertEqual( 14 | "Invalid parameter type ", 15 | str(context.exception), 16 | "Invalid parameter type") 17 | 18 | def test_build_with_empty_parameters(self): 19 | self.assertEqual( 20 | "sdns://AAAAAAAAAAAACTEyNy4wLjAuMQ", 21 | dnsstamps.build(Parameter()), 22 | "Invalid stamp") 23 | 24 | def test_generate_plain_stamp(self): 25 | address = "[fe80::6d6d:f72c:3ad:60b8]" 26 | self.assertEqual( 27 | "sdns://AAAAAAAAAAAAGltmZTgwOjo2ZDZkOmY3MmM6M2FkOjYwYjhd", 28 | dnsstamps.create_plain(address), 29 | "Invalid stamp") 30 | 31 | def test_generate_plain_stamp_with_options(self): 32 | address = "127.0.0.1" 33 | options = [Option.DNSSEC, Option.NO_LOGS, Option.NO_FILTERS] 34 | self.assertEqual( 35 | "sdns://AAcAAAAAAAAACTEyNy4wLjAuMQ", 36 | dnsstamps.create_plain(address, options), 37 | "Invalid stamp") 38 | 39 | def test_generate_dnscrypt_stamp(self): 40 | address = "[fe80::6d6d:f72c:3ad:60b8]" 41 | public_key = "CB6A:DC5C:29F9:5510:0B65:BF12:94FE:5684:579A:B349:9CC9:798F:00D0:1BB5:C1A9:A2C7" 42 | provider_name = "2.dnscrypt-cert.example.com" 43 | 44 | self.assertEqual( 45 | "sdns://AQAAAAAAAAAAGltmZTgwOjo2ZDZkOmY3MmM6M2FkOjYwYjhdIMtq3Fwp-VUQC2W_EpT-VoRXmrNJnMl5jwDQG7XBqaLHGzIuZG5zY3J5cHQtY2VydC5leGFtcGxlLmNvbQ", 46 | dnsstamps.create_dnscrypt(address, public_key, provider_name), 47 | "Invalid stamp") 48 | 49 | def test_generate_dnscrypt_stamp_with_options(self): 50 | address = "127.0.0.1" 51 | public_key = "CB6A:DC5C:29F9:5510:0B65:BF12:94FE:5684:579A:B349:9CC9:798F:00D0:1BB5:C1A9:A2C7" 52 | provider_name = "2.dnscrypt-cert.example.com" 53 | options = [Option.DNSSEC, Option.NO_FILTERS] 54 | 55 | self.assertEqual( 56 | "sdns://AQUAAAAAAAAACTEyNy4wLjAuMSDLatxcKflVEAtlvxKU_laEV5qzSZzJeY8A0Bu1wamixxsyLmRuc2NyeXB0LWNlcnQuZXhhbXBsZS5jb20", 57 | dnsstamps.create_dnscrypt(address, public_key, provider_name, options), 58 | "Invalid stamp") 59 | 60 | def test_generate_doh_stamp(self): 61 | address = "[fe80::6d6d:f72c:3ad:60b8]" 62 | hashes = ["3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838"] 63 | hostname = "doh.example.com" 64 | path = "/dns-query" 65 | 66 | self.assertEqual( 67 | "sdns://AgAAAAAAAAAAGltmZTgwOjo2ZDZkOmY3MmM6M2FkOjYwYjhdID4aGg9sU_PpekktVwhLW5gHBZ7gV6sVBYdv2D_aPbg4D2RvaC5leGFtcGxlLmNvbQovZG5zLXF1ZXJ5", 68 | dnsstamps.create_doh(address, hashes, hostname, path), 69 | "Invalid stamp") 70 | 71 | def test_generate_doh_stamp_with_options(self): 72 | address = "127.0.0.1" 73 | hashes = ["3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838"] 74 | hostname = "doh.example.com" 75 | path = "/dns-query" 76 | options = [Option.NO_LOGS, Option.NO_FILTERS] 77 | 78 | self.assertEqual( 79 | "sdns://AgYAAAAAAAAACTEyNy4wLjAuMSA-GhoPbFPz6XpJLVcIS1uYBwWe4FerFQWHb9g_2j24OA9kb2guZXhhbXBsZS5jb20KL2Rucy1xdWVyeQ", 80 | dnsstamps.create_doh(address, hashes, hostname, path, options), 81 | "Invalid stamp") 82 | 83 | def test_generate_doh_stamp_with_multiple_hashes(self): 84 | address = "127.0.0.1" 85 | hashes = ["3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838", 86 | "d0b243776a6c10e4485b34ea3e3b3a063f3089770e04a78c8087b7c49d4f98d6"] 87 | hostname = "doh.example.com" 88 | path = "/dns-query" 89 | 90 | self.assertEqual( 91 | "sdns://AgAAAAAAAAAACTEyNy4wLjAuMaA-GhoPbFPz6XpJLVcIS1uYBwWe4FerFQWHb9g_2j24OCDQskN3amwQ5EhbNOo-OzoGPzCJdw4Ep4yAh7fEnU-Y1g9kb2guZXhhbXBsZS5jb20KL2Rucy1xdWVyeQ", 92 | dnsstamps.create_doh(address, hashes, hostname, path), 93 | "Invalid stamp") 94 | 95 | def test_generate_doh_stamp_with_bootstrap_ips(self): 96 | address = "127.0.0.1" 97 | hashes = ["3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838"] 98 | hostname = "doh.example.com" 99 | path = "/dns-query" 100 | bootstrap_ips = ["1.1.1.1"] 101 | 102 | self.assertEqual( 103 | "sdns://AgAAAAAAAAAACTEyNy4wLjAuMSA-GhoPbFPz6XpJLVcIS1uYBwWe4FerFQWHb9g_2j24OA9kb2guZXhhbXBsZS5jb20KL2Rucy1xdWVyeQcxLjEuMS4x", 104 | dnsstamps.create_doh(address, hashes, hostname, path, bootstrap_ips=bootstrap_ips), 105 | "Invalid stamp") 106 | 107 | def test_generate_doh_stamp_without_hashes(self): 108 | address = "" 109 | hashes = [] 110 | hostname = "doh.example.com" 111 | path = "/dns-query" 112 | options = [Option.DNSSEC, Option.NO_FILTERS] 113 | 114 | self.assertEqual( 115 | "sdns://AgUAAAAAAAAAAAAPZG9oLmV4YW1wbGUuY29tCi9kbnMtcXVlcnk", 116 | dnsstamps.create_doh(address, hashes, hostname, path, options), 117 | "Invalid stamp") 118 | 119 | def test_generate_dot_stamp(self): 120 | address = "[fe80::6d6d:f72c:3ad:60b8]" 121 | hashes = ["3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838"] 122 | hostname = "dot.example.com" 123 | 124 | self.assertEqual( 125 | "sdns://AwAAAAAAAAAAGltmZTgwOjo2ZDZkOmY3MmM6M2FkOjYwYjhdID4aGg9sU_PpekktVwhLW5gHBZ7gV6sVBYdv2D_aPbg4D2RvdC5leGFtcGxlLmNvbQ", 126 | dnsstamps.create_dot(address, hashes, hostname), 127 | "Invalid stamp") 128 | 129 | def test_generate_dot_stamp_with_options(self): 130 | address = "127.0.0.1" 131 | hashes = ["3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838"] 132 | hostname = "dot.example.com" 133 | options = [Option.DNSSEC] 134 | 135 | self.assertEqual( 136 | "sdns://AwEAAAAAAAAACTEyNy4wLjAuMSA-GhoPbFPz6XpJLVcIS1uYBwWe4FerFQWHb9g_2j24OA9kb3QuZXhhbXBsZS5jb20", 137 | dnsstamps.create_dot(address, hashes, hostname, options), 138 | "Invalid stamp") 139 | 140 | def test_generate_dot_stamp_with_multiple_hashes(self): 141 | address = "127.0.0.1" 142 | hashes = ["3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838", 143 | "d0b243776a6c10e4485b34ea3e3b3a063f3089770e04a78c8087b7c49d4f98d6"] 144 | hostname = "dot.example.com" 145 | 146 | self.assertEqual( 147 | "sdns://AwAAAAAAAAAACTEyNy4wLjAuMaA-GhoPbFPz6XpJLVcIS1uYBwWe4FerFQWHb9g_2j24OCDQskN3amwQ5EhbNOo-OzoGPzCJdw4Ep4yAh7fEnU-Y1g9kb3QuZXhhbXBsZS5jb20", 148 | dnsstamps.create_dot(address, hashes, hostname), 149 | "Invalid stamp") 150 | 151 | def test_generate_dot_stamp_with_bootstrap_ips(self): 152 | address = "127.0.0.1" 153 | hashes = ["3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838"] 154 | hostname = "dot.example.com" 155 | bootstrap_ips = ["1.1.1.1"] 156 | 157 | self.assertEqual( 158 | "sdns://AwAAAAAAAAAACTEyNy4wLjAuMSA-GhoPbFPz6XpJLVcIS1uYBwWe4FerFQWHb9g_2j24OA9kb3QuZXhhbXBsZS5jb20HMS4xLjEuMQ", 159 | dnsstamps.create_dot(address, hashes, hostname, bootstrap_ips=bootstrap_ips), 160 | "Invalid stamp") 161 | 162 | def test_generate_dot_stamp_without_hashes(self): 163 | address = "" 164 | hashes = [] 165 | hostname = "dot.example.com" 166 | options = [Option.DNSSEC, Option.NO_FILTERS] 167 | 168 | self.assertEqual( 169 | "sdns://AwUAAAAAAAAAAAAPZG90LmV4YW1wbGUuY29t", 170 | dnsstamps.create_dot(address, hashes, hostname, options), 171 | "Invalid stamp") 172 | 173 | def test_generate_doq_stamp(self): 174 | address = "[fe80::6d6d:f72c:3ad:60b8]" 175 | hashes = ["3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838"] 176 | hostname = "doq.example.com" 177 | 178 | self.assertEqual( 179 | "sdns://BAAAAAAAAAAAGltmZTgwOjo2ZDZkOmY3MmM6M2FkOjYwYjhdID4aGg9sU_PpekktVwhLW5gHBZ7gV6sVBYdv2D_aPbg4D2RvcS5leGFtcGxlLmNvbQ", 180 | dnsstamps.create_doq(address, hashes, hostname), 181 | "Invalid stamp") 182 | 183 | def test_generate_doq_stamp_with_options(self): 184 | address = "127.0.0.1" 185 | hashes = ["3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838"] 186 | hostname = "doq.example.com" 187 | options = [Option.DNSSEC] 188 | 189 | self.assertEqual( 190 | "sdns://BAEAAAAAAAAACTEyNy4wLjAuMSA-GhoPbFPz6XpJLVcIS1uYBwWe4FerFQWHb9g_2j24OA9kb3EuZXhhbXBsZS5jb20", 191 | dnsstamps.create_doq(address, hashes, hostname, options), 192 | "Invalid stamp") 193 | 194 | def test_generate_doq_stamp_with_multiple_hashes(self): 195 | address = "127.0.0.1" 196 | hashes = ["3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838", 197 | "d0b243776a6c10e4485b34ea3e3b3a063f3089770e04a78c8087b7c49d4f98d6"] 198 | hostname = "doq.example.com" 199 | 200 | self.assertEqual( 201 | "sdns://BAAAAAAAAAAACTEyNy4wLjAuMaA-GhoPbFPz6XpJLVcIS1uYBwWe4FerFQWHb9g_2j24OCDQskN3amwQ5EhbNOo-OzoGPzCJdw4Ep4yAh7fEnU-Y1g9kb3EuZXhhbXBsZS5jb20", 202 | dnsstamps.create_doq(address, hashes, hostname), 203 | "Invalid stamp") 204 | 205 | def test_generate_doq_stamp_with_bootstrap_ips(self): 206 | address = "127.0.0.1" 207 | hashes = ["3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838"] 208 | hostname = "doq.example.com" 209 | bootstrap_ips = ["1.1.1.1"] 210 | 211 | self.assertEqual( 212 | "sdns://BAAAAAAAAAAACTEyNy4wLjAuMSA-GhoPbFPz6XpJLVcIS1uYBwWe4FerFQWHb9g_2j24OA9kb3EuZXhhbXBsZS5jb20HMS4xLjEuMQ", 213 | dnsstamps.create_doq(address, hashes, hostname, bootstrap_ips=bootstrap_ips), 214 | "Invalid stamp") 215 | 216 | def test_generate_doq_stamp_without_hashes(self): 217 | address = "" 218 | hashes = [] 219 | hostname = "doq.example.com" 220 | options = [Option.DNSSEC, Option.NO_FILTERS] 221 | 222 | self.assertEqual( 223 | "sdns://BAUAAAAAAAAAAAAPZG9xLmV4YW1wbGUuY29t", 224 | dnsstamps.create_doq(address, hashes, hostname, options), 225 | "Invalid stamp") 226 | 227 | def test_generate_doh_target_stamp(self): 228 | hostname = "doh-target.example.com" 229 | path = "/dns-query" 230 | 231 | self.assertEqual( 232 | "sdns://BQAAAAAAAAAAFmRvaC10YXJnZXQuZXhhbXBsZS5jb20KL2Rucy1xdWVyeQ", 233 | dnsstamps.create_doh_target(hostname, path), 234 | "Invalid stamp") 235 | 236 | def test_generate_doh_target_stamp_with_options(self): 237 | hostname = "doh-target.example.com" 238 | path = "/dns-query" 239 | options = [Option.NO_LOGS, Option.NO_FILTERS] 240 | 241 | self.assertEqual( 242 | "sdns://BQYAAAAAAAAAFmRvaC10YXJnZXQuZXhhbXBsZS5jb20KL2Rucy1xdWVyeQ", 243 | dnsstamps.create_doh_target(hostname, path, options), 244 | "Invalid stamp") 245 | 246 | def test_generate_doh_target_stamp_with_umlauts_in_hostname(self): 247 | hostname = "döh-tärget.example.cöm" 248 | path = "/dns-query" 249 | options = [Option.NO_LOGS, Option.NO_FILTERS] 250 | 251 | self.assertEqual( 252 | "sdns://BQYAAAAAAAAAFmT2aC105HJnZXQuZXhhbXBsZS5j9m0KL2Rucy1xdWVyeQ", 253 | dnsstamps.create_doh_target(hostname, path, options), 254 | "Invalid stamp") 255 | 256 | def test_generate_dnscrypt_relay_stamp(self): 257 | address = "127.0.0.1:443" 258 | self.assertEqual( 259 | "sdns://gQ0xMjcuMC4wLjE6NDQz", 260 | dnsstamps.create_dnscrypt_relay(address), 261 | "Invalid stamp") 262 | 263 | def test_generate_doh_relay_stamp(self): 264 | address = "[fe80::6d6d:f72c:3ad:60b8]" 265 | hashes = ["3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838"] 266 | hostname = "doh-relay.example.com" 267 | path = "/dns-query" 268 | 269 | self.assertEqual( 270 | "sdns://hQAAAAAAAAAAGltmZTgwOjo2ZDZkOmY3MmM6M2FkOjYwYjhdID4aGg9sU_PpekktVwhLW5gHBZ7gV6sVBYdv2D_aPbg4FWRvaC1yZWxheS5leGFtcGxlLmNvbQovZG5zLXF1ZXJ5", 271 | dnsstamps.create_doh_relay(address, hashes, hostname, path), 272 | "Invalid stamp") 273 | 274 | def test_generate_doh_relay_stamp_with_options(self): 275 | address = "127.0.0.1" 276 | hashes = ["3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838"] 277 | hostname = "doh-relay.example.com" 278 | path = "/dns-query" 279 | options = [Option.NO_LOGS] 280 | 281 | self.assertEqual( 282 | "sdns://hQIAAAAAAAAACTEyNy4wLjAuMSA-GhoPbFPz6XpJLVcIS1uYBwWe4FerFQWHb9g_2j24OBVkb2gtcmVsYXkuZXhhbXBsZS5jb20KL2Rucy1xdWVyeQ", 283 | dnsstamps.create_doh_relay(address, hashes, hostname, path, options), 284 | "Invalid stamp") 285 | 286 | def test_generate_doh_relay_stamp_with_multiple_hashes(self): 287 | address = "127.0.0.1" 288 | hashes = ["3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838", 289 | "d0b243776a6c10e4485b34ea3e3b3a063f3089770e04a78c8087b7c49d4f98d6"] 290 | hostname = "doh-relay.example.com" 291 | path = "/dns-query" 292 | 293 | self.assertEqual( 294 | "sdns://hQAAAAAAAAAACTEyNy4wLjAuMaA-GhoPbFPz6XpJLVcIS1uYBwWe4FerFQWHb9g_2j24OCDQskN3amwQ5EhbNOo-OzoGPzCJdw4Ep4yAh7fEnU-Y1hVkb2gtcmVsYXkuZXhhbXBsZS5jb20KL2Rucy1xdWVyeQ", 295 | dnsstamps.create_doh_relay(address, hashes, hostname, path), 296 | "Invalid stamp") 297 | 298 | def test_generate_doh_relay_stamp_with_bootstrap_ips(self): 299 | address = "127.0.0.1" 300 | hashes = ["3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838"] 301 | hostname = "doh-relay.example.com" 302 | path = "/dns-query" 303 | bootstrap_ips = ["1.1.1.1"] 304 | 305 | self.assertEqual( 306 | "sdns://hQAAAAAAAAAACTEyNy4wLjAuMSA-GhoPbFPz6XpJLVcIS1uYBwWe4FerFQWHb9g_2j24OBVkb2gtcmVsYXkuZXhhbXBsZS5jb20KL2Rucy1xdWVyeQcxLjEuMS4x", 307 | dnsstamps.create_doh_relay(address, hashes, hostname, path, bootstrap_ips=bootstrap_ips), 308 | "Invalid stamp") 309 | 310 | def test_generate_doh_relay_stamp_without_hashes(self): 311 | address = "" 312 | hashes = [] 313 | hostname = "doh-relay.example.com" 314 | path = "/dns-query" 315 | options = [Option.NO_LOGS] 316 | 317 | self.assertEqual( 318 | "sdns://hQIAAAAAAAAAAAAVZG9oLXJlbGF5LmV4YW1wbGUuY29tCi9kbnMtcXVlcnk", 319 | dnsstamps.create_doh_relay(address, hashes, hostname, path, options), 320 | "Invalid stamp") 321 | -------------------------------------------------------------------------------- /test/parser/test_parser.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import dnsstamps 4 | from dnsstamps import Option 5 | from dnsstamps import Protocol 6 | 7 | 8 | class TestParser(unittest.TestCase): 9 | 10 | def test_parse_stamp_with_invalid_format(self): 11 | with self.assertRaises(Exception) as context: 12 | dnsstamps.parse("sdns://abc123xyz") 13 | self.assertEqual( 14 | "('Unable to unpack stamp', Error('Invalid base64-encoded string: number of data characters (9) cannot be 1 more than a multiple of 4'))", 15 | str(context.exception), 16 | "Invalid exception") 17 | 18 | def test_parse_stamp_with_invalid_protocol(self): 19 | with self.assertRaises(Exception) as context: 20 | dnsstamps.parse("sdns://abc123") 21 | self.assertEqual( 22 | "('Unable to consume protocol', ValueError('105 is not a valid Protocol'))", 23 | str(context.exception), 24 | "Invalid exception") 25 | 26 | def test_parse_plain_stamp(self): 27 | parameter = dnsstamps.parse("sdns://AAAAAAAAAAAAGltmZTgwOjo2ZDZkOmY3MmM6M2FkOjYwYjhd") 28 | 29 | self.assertEqual(Protocol.PLAIN, parameter.protocol, "Invalid protocol") 30 | self.assertEqual([], parameter.options, "Invalid options") 31 | self.assertEqual("[fe80::6d6d:f72c:3ad:60b8]", parameter.address, "Invalid address") 32 | 33 | def test_parse_plain_stamp_with_options(self): 34 | parameter = dnsstamps.parse("sdns://AAcAAAAAAAAACTEyNy4wLjAuMQ") 35 | 36 | self.assertEqual(Protocol.PLAIN, parameter.protocol, "Invalid protocol") 37 | self.assertEqual([Option.DNSSEC, Option.NO_LOGS, Option.NO_FILTERS], parameter.options, "Invalid options") 38 | self.assertEqual("127.0.0.1", parameter.address, "Invalid address") 39 | 40 | def test_parse_dnscrypt_stamp(self): 41 | parameter = dnsstamps.parse( 42 | "sdns://AQAAAAAAAAAAGltmZTgwOjo2ZDZkOmY3MmM6M2FkOjYwYjhdIMtq3Fwp-VUQC2W_EpT-VoRXmrNJnMl5jwDQG7XBqaLHGzIuZG5zY3J5cHQtY2VydC5leGFtcGxlLmNvbQ") 43 | 44 | self.assertEqual(Protocol.DNSCRYPT, parameter.protocol, "Invalid protocol") 45 | self.assertEqual([], parameter.options, "Invalid options") 46 | self.assertEqual("[fe80::6d6d:f72c:3ad:60b8]", parameter.address, "Invalid address") 47 | self.assertEqual(b"cb6adc5c29f955100b65bf1294fe5684579ab3499cc9798f00d01bb5c1a9a2c7", parameter.public_key, 48 | "Invalid public_key") 49 | self.assertEqual("2.dnscrypt-cert.example.com", parameter.provider_name, "Invalid provider_name") 50 | 51 | def test_parse_dnscrypt_stamp_with_options(self): 52 | parameter = dnsstamps.parse( 53 | "sdns://AQUAAAAAAAAACTEyNy4wLjAuMSDLatxcKflVEAtlvxKU_laEV5qzSZzJeY8A0Bu1wamixxsyLmRuc2NyeXB0LWNlcnQuZXhhbXBsZS5jb20") 54 | 55 | self.assertEqual(Protocol.DNSCRYPT, parameter.protocol, "Invalid protocol") 56 | self.assertEqual([Option.DNSSEC, Option.NO_FILTERS], parameter.options, "Invalid options") 57 | self.assertEqual("127.0.0.1", parameter.address, "Invalid address") 58 | self.assertEqual(b"cb6adc5c29f955100b65bf1294fe5684579ab3499cc9798f00d01bb5c1a9a2c7", parameter.public_key, 59 | "Invalid public_key") 60 | self.assertEqual("2.dnscrypt-cert.example.com", parameter.provider_name, "Invalid provider_name") 61 | 62 | def test_parse_doh_stamp(self): 63 | parameter = dnsstamps.parse( 64 | "sdns://AgAAAAAAAAAAGltmZTgwOjo2ZDZkOmY3MmM6M2FkOjYwYjhdID4aGg9sU_PpekktVwhLW5gHBZ7gV6sVBYdv2D_aPbg4D2RvaC5leGFtcGxlLmNvbQovZG5zLXF1ZXJ5") 65 | 66 | self.assertEqual(Protocol.DOH, parameter.protocol, "Invalid protocol") 67 | self.assertEqual([], parameter.options, "Invalid options") 68 | self.assertEqual("[fe80::6d6d:f72c:3ad:60b8]", parameter.address, "Invalid address") 69 | self.assertEqual([b"3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838"], parameter.hashes, 70 | "Invalid hashes") 71 | self.assertEqual("doh.example.com", parameter.hostname, "Invalid hostname") 72 | self.assertEqual("/dns-query", parameter.path, "Invalid path") 73 | self.assertEqual([], parameter.bootstrap_ips, "Invalid bootstrap_ips") 74 | 75 | def test_parse_doh_stamp_with_options(self): 76 | parameter = dnsstamps.parse( 77 | "sdns://AgYAAAAAAAAACTEyNy4wLjAuMSA-GhoPbFPz6XpJLVcIS1uYBwWe4FerFQWHb9g_2j24OA9kb2guZXhhbXBsZS5jb20KL2Rucy1xdWVyeQ") 78 | 79 | self.assertEqual(Protocol.DOH, parameter.protocol, "Invalid protocol") 80 | self.assertEqual([Option.NO_LOGS, Option.NO_FILTERS], parameter.options, "Invalid options") 81 | self.assertEqual("127.0.0.1", parameter.address, "Invalid address") 82 | self.assertEqual([b"3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838"], parameter.hashes, 83 | "Invalid hashes") 84 | self.assertEqual("doh.example.com", parameter.hostname, "Invalid hostname") 85 | self.assertEqual("/dns-query", parameter.path, "Invalid path") 86 | self.assertEqual([], parameter.bootstrap_ips, "Invalid bootstrap_ips") 87 | 88 | def test_parse_doh_stamp_with_multiple_hashes(self): 89 | parameter = dnsstamps.parse( 90 | "sdns://AgAAAAAAAAAACTEyNy4wLjAuMaA-GhoPbFPz6XpJLVcIS1uYBwWe4FerFQWHb9g_2j24OCDQskN3amwQ5EhbNOo-OzoGPzCJdw4Ep4yAh7fEnU-Y1g9kb2guZXhhbXBsZS5jb20KL2Rucy1xdWVyeQ") 91 | 92 | self.assertEqual(Protocol.DOH, parameter.protocol, "Invalid protocol") 93 | self.assertEqual([], parameter.options, "Invalid options") 94 | self.assertEqual("127.0.0.1", parameter.address, "Invalid address") 95 | self.assertEqual([b"3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838", 96 | b"d0b243776a6c10e4485b34ea3e3b3a063f3089770e04a78c8087b7c49d4f98d6"], parameter.hashes, 97 | "Invalid hashes") 98 | self.assertEqual("doh.example.com", parameter.hostname, "Invalid hostname") 99 | self.assertEqual("/dns-query", parameter.path, "Invalid path") 100 | self.assertEqual([], parameter.bootstrap_ips, "Invalid bootstrap_ips") 101 | 102 | def test_parse_doh_stamp_with_bootstrap_ips(self): 103 | parameter = dnsstamps.parse( 104 | "sdns://AgAAAAAAAAAACTEyNy4wLjAuMSA-GhoPbFPz6XpJLVcIS1uYBwWe4FerFQWHb9g_2j24OA9kb2guZXhhbXBsZS5jb20KL2Rucy1xdWVyeQcxLjEuMS4x") 105 | 106 | self.assertEqual(Protocol.DOH, parameter.protocol, "Invalid protocol") 107 | self.assertEqual([], parameter.options, "Invalid options") 108 | self.assertEqual("127.0.0.1", parameter.address, "Invalid address") 109 | self.assertEqual([b"3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838"], parameter.hashes, 110 | "Invalid hashes") 111 | self.assertEqual("doh.example.com", parameter.hostname, "Invalid hostname") 112 | self.assertEqual("/dns-query", parameter.path, "Invalid path") 113 | self.assertEqual(["1.1.1.1"], parameter.bootstrap_ips, "Invalid bootstrap_ips") 114 | 115 | def test_parse_doh_stamp_without_hashes(self): 116 | parameter = dnsstamps.parse("sdns://AgUAAAAAAAAAAAAPZG9oLmV4YW1wbGUuY29tCi9kbnMtcXVlcnk") 117 | 118 | self.assertEqual(Protocol.DOH, parameter.protocol, "Invalid protocol") 119 | self.assertEqual([Option.DNSSEC, Option.NO_FILTERS], parameter.options, "Invalid options") 120 | self.assertEqual("", parameter.address, "Invalid address") 121 | self.assertEqual([], parameter.hashes, "Invalid hashes") 122 | self.assertEqual("doh.example.com", parameter.hostname, "Invalid hostname") 123 | self.assertEqual("/dns-query", parameter.path, "Invalid path") 124 | self.assertEqual([], parameter.bootstrap_ips, "Invalid bootstrap_ips") 125 | 126 | def test_parse_dot_stamp(self): 127 | parameter = dnsstamps.parse( 128 | "sdns://AwAAAAAAAAAAGltmZTgwOjo2ZDZkOmY3MmM6M2FkOjYwYjhdID4aGg9sU_PpekktVwhLW5gHBZ7gV6sVBYdv2D_aPbg4D2RvdC5leGFtcGxlLmNvbQ") 129 | 130 | self.assertEqual(Protocol.DOT, parameter.protocol, "Invalid protocol") 131 | self.assertEqual([], parameter.options, "Invalid options") 132 | self.assertEqual("[fe80::6d6d:f72c:3ad:60b8]", parameter.address, "Invalid address") 133 | self.assertEqual([b"3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838"], parameter.hashes, 134 | "Invalid hashes") 135 | self.assertEqual("dot.example.com", parameter.hostname, "Invalid hostname") 136 | self.assertEqual([], parameter.bootstrap_ips, "Invalid bootstrap_ips") 137 | 138 | def test_parse_dot_stamp_with_options(self): 139 | parameter = dnsstamps.parse( 140 | "sdns://AwEAAAAAAAAACTEyNy4wLjAuMSA-GhoPbFPz6XpJLVcIS1uYBwWe4FerFQWHb9g_2j24OA9kb3QuZXhhbXBsZS5jb20") 141 | 142 | self.assertEqual(Protocol.DOT, parameter.protocol, "Invalid protocol") 143 | self.assertEqual([Option.DNSSEC], parameter.options, "Invalid options") 144 | self.assertEqual("127.0.0.1", parameter.address, "Invalid address") 145 | self.assertEqual([b"3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838"], parameter.hashes, 146 | "Invalid hashes") 147 | self.assertEqual("dot.example.com", parameter.hostname, "Invalid hostname") 148 | self.assertEqual([], parameter.bootstrap_ips, "Invalid bootstrap_ips") 149 | 150 | def test_parse_dot_stamp_with_multiple_hashes(self): 151 | parameter = dnsstamps.parse( 152 | "sdns://AwAAAAAAAAAACTEyNy4wLjAuMaA-GhoPbFPz6XpJLVcIS1uYBwWe4FerFQWHb9g_2j24OCDQskN3amwQ5EhbNOo-OzoGPzCJdw4Ep4yAh7fEnU-Y1g9kb3QuZXhhbXBsZS5jb20") 153 | 154 | self.assertEqual(Protocol.DOT, parameter.protocol, "Invalid protocol") 155 | self.assertEqual([], parameter.options, "Invalid options") 156 | self.assertEqual("127.0.0.1", parameter.address, "Invalid address") 157 | self.assertEqual([b"3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838", 158 | b"d0b243776a6c10e4485b34ea3e3b3a063f3089770e04a78c8087b7c49d4f98d6"], parameter.hashes, 159 | "Invalid hashes") 160 | self.assertEqual("dot.example.com", parameter.hostname, "Invalid hostname") 161 | self.assertEqual([], parameter.bootstrap_ips, "Invalid bootstrap_ips") 162 | 163 | def test_parse_dot_stamp_with_bootstrap_ips(self): 164 | parameter = dnsstamps.parse( 165 | "sdns://AwAAAAAAAAAACTEyNy4wLjAuMSA-GhoPbFPz6XpJLVcIS1uYBwWe4FerFQWHb9g_2j24OA9kb3QuZXhhbXBsZS5jb20HMS4xLjEuMQ") 166 | 167 | self.assertEqual(Protocol.DOT, parameter.protocol, "Invalid protocol") 168 | self.assertEqual([], parameter.options, "Invalid options") 169 | self.assertEqual("127.0.0.1", parameter.address, "Invalid address") 170 | self.assertEqual([b"3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838"], parameter.hashes, 171 | "Invalid hashes") 172 | self.assertEqual("dot.example.com", parameter.hostname, "Invalid hostname") 173 | self.assertEqual(["1.1.1.1"], parameter.bootstrap_ips, "Invalid bootstrap_ips") 174 | 175 | def test_parse_dot_stamp_without_hashes(self): 176 | parameter = dnsstamps.parse("sdns://AwUAAAAAAAAAAAAPZG90LmV4YW1wbGUuY29t") 177 | 178 | self.assertEqual(Protocol.DOT, parameter.protocol, "Invalid protocol") 179 | self.assertEqual([Option.DNSSEC, Option.NO_FILTERS], parameter.options, "Invalid options") 180 | self.assertEqual("", parameter.address, "Invalid address") 181 | self.assertEqual([], parameter.hashes, "Invalid hashes") 182 | self.assertEqual("dot.example.com", parameter.hostname, "Invalid hostname") 183 | self.assertEqual([], parameter.bootstrap_ips, "Invalid bootstrap_ips") 184 | 185 | def test_parse_doq_stamp(self): 186 | parameter = dnsstamps.parse( 187 | "sdns://BAAAAAAAAAAAGltmZTgwOjo2ZDZkOmY3MmM6M2FkOjYwYjhdID4aGg9sU_PpekktVwhLW5gHBZ7gV6sVBYdv2D_aPbg4D2RvcS5leGFtcGxlLmNvbQ") 188 | 189 | self.assertEqual(Protocol.DOQ, parameter.protocol, "Invalid protocol") 190 | self.assertEqual([], parameter.options, "Invalid options") 191 | self.assertEqual("[fe80::6d6d:f72c:3ad:60b8]", parameter.address, "Invalid address") 192 | self.assertEqual([b"3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838"], parameter.hashes, 193 | "Invalid hashes") 194 | self.assertEqual("doq.example.com", parameter.hostname, "Invalid hostname") 195 | self.assertEqual([], parameter.bootstrap_ips, "Invalid bootstrap_ips") 196 | 197 | def test_parse_doq_stamp_with_options(self): 198 | parameter = dnsstamps.parse( 199 | "sdns://BAEAAAAAAAAACTEyNy4wLjAuMSA-GhoPbFPz6XpJLVcIS1uYBwWe4FerFQWHb9g_2j24OA9kb3EuZXhhbXBsZS5jb20") 200 | 201 | self.assertEqual(Protocol.DOQ, parameter.protocol, "Invalid protocol") 202 | self.assertEqual([Option.DNSSEC], parameter.options, "Invalid options") 203 | self.assertEqual("127.0.0.1", parameter.address, "Invalid address") 204 | self.assertEqual([b"3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838"], parameter.hashes, 205 | "Invalid hashes") 206 | self.assertEqual("doq.example.com", parameter.hostname, "Invalid hostname") 207 | self.assertEqual([], parameter.bootstrap_ips, "Invalid bootstrap_ips") 208 | 209 | def test_parse_doq_stamp_with_multiple_hashes(self): 210 | parameter = dnsstamps.parse( 211 | "sdns://BAAAAAAAAAAACTEyNy4wLjAuMaA-GhoPbFPz6XpJLVcIS1uYBwWe4FerFQWHb9g_2j24OCDQskN3amwQ5EhbNOo-OzoGPzCJdw4Ep4yAh7fEnU-Y1g9kb3EuZXhhbXBsZS5jb20") 212 | 213 | self.assertEqual(Protocol.DOQ, parameter.protocol, "Invalid protocol") 214 | self.assertEqual([], parameter.options, "Invalid options") 215 | self.assertEqual("127.0.0.1", parameter.address, "Invalid address") 216 | self.assertEqual([b"3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838", 217 | b"d0b243776a6c10e4485b34ea3e3b3a063f3089770e04a78c8087b7c49d4f98d6"], parameter.hashes, 218 | "Invalid hashes") 219 | self.assertEqual("doq.example.com", parameter.hostname, "Invalid hostname") 220 | self.assertEqual([], parameter.bootstrap_ips, "Invalid bootstrap_ips") 221 | 222 | def test_parse_doq_stamp_with_bootstrap_ips(self): 223 | parameter = dnsstamps.parse( 224 | "sdns://BAAAAAAAAAAACTEyNy4wLjAuMSA-GhoPbFPz6XpJLVcIS1uYBwWe4FerFQWHb9g_2j24OA9kb3EuZXhhbXBsZS5jb20HMS4xLjEuMQ") 225 | 226 | self.assertEqual(Protocol.DOQ, parameter.protocol, "Invalid protocol") 227 | self.assertEqual([], parameter.options, "Invalid options") 228 | self.assertEqual("127.0.0.1", parameter.address, "Invalid address") 229 | self.assertEqual([b"3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838"], parameter.hashes, 230 | "Invalid hashes") 231 | self.assertEqual("doq.example.com", parameter.hostname, "Invalid hostname") 232 | self.assertEqual(["1.1.1.1"], parameter.bootstrap_ips, "Invalid bootstrap_ips") 233 | 234 | def test_parse_doq_stamp_without_hashes(self): 235 | parameter = dnsstamps.parse("sdns://BAUAAAAAAAAAAAAPZG9xLmV4YW1wbGUuY29t") 236 | 237 | self.assertEqual(Protocol.DOQ, parameter.protocol, "Invalid protocol") 238 | self.assertEqual([Option.DNSSEC, Option.NO_FILTERS], parameter.options, "Invalid options") 239 | self.assertEqual("", parameter.address, "Invalid address") 240 | self.assertEqual([], parameter.hashes, "Invalid hashes") 241 | self.assertEqual("doq.example.com", parameter.hostname, "Invalid hostname") 242 | self.assertEqual([], parameter.bootstrap_ips, "Invalid bootstrap_ips") 243 | 244 | def test_parse_doh_target_stamp(self): 245 | parameter = dnsstamps.parse("sdns://BQAAAAAAAAAAFmRvaC10YXJnZXQuZXhhbXBsZS5jb20KL2Rucy1xdWVyeQ") 246 | 247 | self.assertEqual(Protocol.DOH_TARGET, parameter.protocol, "Invalid protocol") 248 | self.assertEqual([], parameter.options, "Invalid options") 249 | self.assertEqual("doh-target.example.com", parameter.hostname, "Invalid hostname") 250 | self.assertEqual("/dns-query", parameter.path, "Invalid path") 251 | 252 | def test_parse_doh_target_stamp_with_options(self): 253 | parameter = dnsstamps.parse("sdns://BQYAAAAAAAAAFmRvaC10YXJnZXQuZXhhbXBsZS5jb20KL2Rucy1xdWVyeQ") 254 | 255 | self.assertEqual(Protocol.DOH_TARGET, parameter.protocol, "Invalid protocol") 256 | self.assertEqual([Option.NO_LOGS, Option.NO_FILTERS], parameter.options, "Invalid options") 257 | self.assertEqual("doh-target.example.com", parameter.hostname, "Invalid hostname") 258 | self.assertEqual("/dns-query", parameter.path, "Invalid path") 259 | 260 | def test_parse_doh_target_stamp_with_umlauts_in_hostname(self): 261 | parameter = dnsstamps.parse("sdns://BQYAAAAAAAAAFmT2aC105HJnZXQuZXhhbXBsZS5j9m0KL2Rucy1xdWVyeQ") 262 | 263 | self.assertEqual(Protocol.DOH_TARGET, parameter.protocol, "Invalid protocol") 264 | self.assertEqual([Option.NO_LOGS, Option.NO_FILTERS], parameter.options, "Invalid options") 265 | self.assertEqual("döh-tärget.example.cöm", parameter.hostname, "Invalid hostname") 266 | self.assertEqual("/dns-query", parameter.path, "Invalid path") 267 | 268 | def test_parse_dnscrypt_relay_stamp(self): 269 | parameter = dnsstamps.parse("sdns://gQ0xMjcuMC4wLjE6NDQz") 270 | 271 | self.assertEqual(Protocol.DNSCRYPT_RELAY, parameter.protocol, "Invalid protocol") 272 | self.assertEqual("127.0.0.1:443", parameter.address, "Invalid address") 273 | 274 | def test_parse_doh_relay_stamp(self): 275 | parameter = dnsstamps.parse( 276 | "sdns://hQAAAAAAAAAAGltmZTgwOjo2ZDZkOmY3MmM6M2FkOjYwYjhdID4aGg9sU_PpekktVwhLW5gHBZ7gV6sVBYdv2D_aPbg4FWRvaC1yZWxheS5leGFtcGxlLmNvbQovZG5zLXF1ZXJ5") 277 | 278 | self.assertEqual(Protocol.DOH_RELAY, parameter.protocol, "Invalid protocol") 279 | self.assertEqual([], parameter.options, "Invalid options") 280 | self.assertEqual("[fe80::6d6d:f72c:3ad:60b8]", parameter.address, "Invalid address") 281 | self.assertEqual([b"3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838"], parameter.hashes, 282 | "Invalid hashes") 283 | self.assertEqual("doh-relay.example.com", parameter.hostname, "Invalid hostname") 284 | self.assertEqual("/dns-query", parameter.path, "Invalid path") 285 | self.assertEqual([], parameter.bootstrap_ips, "Invalid bootstrap_ips") 286 | 287 | def test_parse_doh_relay_stamp_with_options(self): 288 | parameter = dnsstamps.parse( 289 | "sdns://hQIAAAAAAAAACTEyNy4wLjAuMSA-GhoPbFPz6XpJLVcIS1uYBwWe4FerFQWHb9g_2j24OBVkb2gtcmVsYXkuZXhhbXBsZS5jb20KL2Rucy1xdWVyeQ") 290 | 291 | self.assertEqual(Protocol.DOH_RELAY, parameter.protocol, "Invalid protocol") 292 | self.assertEqual([Option.NO_LOGS], parameter.options, "Invalid options") 293 | self.assertEqual("127.0.0.1", parameter.address, "Invalid address") 294 | self.assertEqual([b"3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838"], parameter.hashes, 295 | "Invalid hashes") 296 | self.assertEqual("doh-relay.example.com", parameter.hostname, "Invalid hostname") 297 | self.assertEqual("/dns-query", parameter.path, "Invalid path") 298 | self.assertEqual([], parameter.bootstrap_ips, "Invalid bootstrap_ips") 299 | 300 | def test_parse_doh_relay_stamp_with_multiple_hashes(self): 301 | parameter = dnsstamps.parse( 302 | "sdns://hQAAAAAAAAAACTEyNy4wLjAuMaA-GhoPbFPz6XpJLVcIS1uYBwWe4FerFQWHb9g_2j24OCDQskN3amwQ5EhbNOo-OzoGPzCJdw4Ep4yAh7fEnU-Y1hVkb2gtcmVsYXkuZXhhbXBsZS5jb20KL2Rucy1xdWVyeQ") 303 | 304 | self.assertEqual(Protocol.DOH_RELAY, parameter.protocol, "Invalid protocol") 305 | self.assertEqual([], parameter.options, "Invalid options") 306 | self.assertEqual("127.0.0.1", parameter.address, "Invalid address") 307 | self.assertEqual([b"3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838", 308 | b"d0b243776a6c10e4485b34ea3e3b3a063f3089770e04a78c8087b7c49d4f98d6"], parameter.hashes, 309 | "Invalid hashes") 310 | self.assertEqual("doh-relay.example.com", parameter.hostname, "Invalid hostname") 311 | self.assertEqual("/dns-query", parameter.path, "Invalid path") 312 | self.assertEqual([], parameter.bootstrap_ips, "Invalid bootstrap_ips") 313 | 314 | def test_parse_doh_relay_stamp_with_bootstrap_ips(self): 315 | parameter = dnsstamps.parse( 316 | "sdns://hQAAAAAAAAAACTEyNy4wLjAuMSA-GhoPbFPz6XpJLVcIS1uYBwWe4FerFQWHb9g_2j24OBVkb2gtcmVsYXkuZXhhbXBsZS5jb20KL2Rucy1xdWVyeQcxLjEuMS4x") 317 | 318 | self.assertEqual(Protocol.DOH_RELAY, parameter.protocol, "Invalid protocol") 319 | self.assertEqual([], parameter.options, "Invalid options") 320 | self.assertEqual("127.0.0.1", parameter.address, "Invalid address") 321 | self.assertEqual([b"3e1a1a0f6c53f3e97a492d57084b5b9807059ee057ab1505876fd83fda3db838"], parameter.hashes, 322 | "Invalid hashes") 323 | self.assertEqual("doh-relay.example.com", parameter.hostname, "Invalid hostname") 324 | self.assertEqual("/dns-query", parameter.path, "Invalid path") 325 | self.assertEqual(["1.1.1.1"], parameter.bootstrap_ips, "Invalid bootstrap_ips") 326 | 327 | def test_parse_doh_relay_stamp_without_hashes(self): 328 | parameter = dnsstamps.parse("sdns://hQIAAAAAAAAAAAAVZG9oLXJlbGF5LmV4YW1wbGUuY29tCi9kbnMtcXVlcnk") 329 | 330 | self.assertEqual(Protocol.DOH_RELAY, parameter.protocol, "Invalid protocol") 331 | self.assertEqual([Option.NO_LOGS], parameter.options, "Invalid options") 332 | self.assertEqual("", parameter.address, "Invalid address") 333 | self.assertEqual([], parameter.hashes, "Invalid hashes") 334 | self.assertEqual("doh-relay.example.com", parameter.hostname, "Invalid hostname") 335 | self.assertEqual("/dns-query", parameter.path, "Invalid path") 336 | self.assertEqual([], parameter.bootstrap_ips, "Invalid bootstrap_ips") 337 | --------------------------------------------------------------------------------