Compare commits
10 commits
05875b3bee
...
6d7f439642
Author | SHA1 | Date | |
---|---|---|---|
6d7f439642 | |||
f34820f25b | |||
26ccbd1c51 | |||
cacd3aa7e1 | |||
b9cf5e0406 | |||
fdea135856 | |||
41566033e3 | |||
fb1a8ab604 | |||
e644e09277 | |||
04a271a1e5 |
4 changed files with 195 additions and 52 deletions
12
.envrc
12
.envrc
|
@ -13,9 +13,17 @@ PATH_add bin
|
|||
python3 -m pip --require-virtualenv --quiet install --upgrade pip wheel
|
||||
python3 -m pip --require-virtualenv --quiet install --upgrade -r requirements.txt
|
||||
|
||||
rm -rf .cache/bw/git_deploy
|
||||
export BW_GIT_DEPLOY_CACHE=.cache/bw/git_deploy
|
||||
# git deploy cache
|
||||
export BW_GIT_DEPLOY_CACHE=".cache/bw/git_deploy"
|
||||
CREATED_AT=$(stat -f "%Sa" "$BW_GIT_DEPLOY_CACHE" | xargs -I{} date -f '%b %d %H:%M:%S %Y' -j "{}" '+%s')
|
||||
NOW_AT=$(date +%s)
|
||||
SECONDS_SINCE=$(expr "$NOW_AT" - "$CREATED_AT")
|
||||
if [[ "$SECONDS_SINCE" > 86400 ]]
|
||||
then
|
||||
rm -rf "$BW_GIT_DEPLOY_CACHE"
|
||||
fi
|
||||
mkdir -p "$BW_GIT_DEPLOY_CACHE"
|
||||
|
||||
export EXPERIMENTAL_UPLOAD_VIA_CAT=1
|
||||
export BW_ITEM_WORKERS=32
|
||||
export BW_NODE_WORKERS=12
|
||||
|
|
185
bin/dnssec
185
bin/dnssec
|
@ -1,5 +1,10 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# https://medium.com/iocscan/how-dnssec-works-9c652257be0
|
||||
# https://de.wikipedia.org/wiki/RRSIG_Resource_Record
|
||||
# https://metebalci.com/blog/a-minimum-complete-tutorial-of-dnssec/
|
||||
# https://bind9.readthedocs.io/en/latest/dnssec-guide.html
|
||||
|
||||
from sys import argv
|
||||
from os.path import realpath, dirname
|
||||
from bundlewrap.repo import Repository
|
||||
|
@ -8,77 +13,77 @@ from cryptography.utils import int_to_bytes
|
|||
from cryptography.hazmat.primitives import serialization as crypto_serialization
|
||||
from struct import pack, unpack
|
||||
from hashlib import sha1, sha256
|
||||
from json import dumps
|
||||
from cache_to_disk import cache_to_disk
|
||||
|
||||
|
||||
def long_to_base64(n):
|
||||
return urlsafe_b64encode(int_to_bytes(n, None)).decode()
|
||||
|
||||
domain = argv[1]
|
||||
zone = argv[1]
|
||||
repo = Repository(dirname(dirname(realpath(__file__))))
|
||||
#repo = Repository('.')
|
||||
|
||||
flags = 256
|
||||
protocol = 3
|
||||
algorithm = 8
|
||||
algorithm_name = 'RSASHA256'
|
||||
|
||||
# Private Key
|
||||
|
||||
priv = repo.libs.rsa.generate_deterministic_rsa_private_key(
|
||||
b64decode(str(repo.vault.random_bytes_as_base64_for('dnssec' + domain)))
|
||||
)
|
||||
|
||||
# ZSK/KSK DNSKEY
|
||||
#
|
||||
# https://cryptography.io/en/latest/hazmat/primitives/asymmetric/rsa/#cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateNumbers
|
||||
# https://crypto.stackexchange.com/a/21104
|
||||
public_exponent = priv.private_numbers().public_numbers.e
|
||||
modulo = priv.private_numbers().public_numbers.n
|
||||
private_exponent = priv.private_numbers().d
|
||||
prime1 = priv.private_numbers().p
|
||||
prime2 = priv.private_numbers().q
|
||||
exponent1 = priv.private_numbers().dmp1
|
||||
exponent2 = priv.private_numbers().dmq1
|
||||
coefficient = priv.private_numbers().iqmp
|
||||
|
||||
print(f"""
|
||||
Private-key-format: v1.3
|
||||
Algorithm: {algorithm} ({algorithm_name})
|
||||
Modulus: {long_to_base64(modulo)}
|
||||
PublicExponent: {long_to_base64(public_exponent)}
|
||||
PrivateExponent: {long_to_base64(private_exponent)}
|
||||
Prime1: {long_to_base64(prime1)}
|
||||
Prime2: {long_to_base64(prime2)}
|
||||
Exponent1: {long_to_base64(exponent1)}
|
||||
Exponent2: {long_to_base64(exponent2)}
|
||||
Coefficient: {long_to_base64(coefficient)}
|
||||
Created: 20230428110109
|
||||
Publish: 20230428110109
|
||||
Activate: 20230428110109
|
||||
""")
|
||||
def generate_signing_key_pair(zone, salt):
|
||||
privkey = repo.libs.rsa.generate_deterministic_rsa_private_key(
|
||||
b64decode(str(repo.vault.random_bytes_as_base64_for(f'dnssec {salt} ' + zone)))
|
||||
)
|
||||
|
||||
# DNSKEY
|
||||
public_exponent = privkey.private_numbers().public_numbers.e
|
||||
modulo = privkey.private_numbers().public_numbers.n
|
||||
private_exponent = privkey.private_numbers().d
|
||||
prime1 = privkey.private_numbers().p
|
||||
prime2 = privkey.private_numbers().q
|
||||
exponent1 = privkey.private_numbers().dmp1
|
||||
exponent2 = privkey.private_numbers().dmq1
|
||||
coefficient = privkey.private_numbers().iqmp
|
||||
|
||||
pub = priv.public_key()
|
||||
dnskey = ''.join(privkey.public_key().public_bytes(
|
||||
crypto_serialization.Encoding.PEM,
|
||||
crypto_serialization.PublicFormat.SubjectPublicKeyInfo
|
||||
).decode().split('\n')[1:-2])
|
||||
|
||||
dnskey = ''.join(pub.public_bytes(
|
||||
crypto_serialization.Encoding.PEM,
|
||||
crypto_serialization.PublicFormat.SubjectPublicKeyInfo
|
||||
).decode().split('\n')[1:-2])
|
||||
return {
|
||||
'dnskey': dnskey,
|
||||
'dnskey_record': f'{zone}. IN DNSKEY {flags} {protocol} {algorithm} {dnskey}',
|
||||
'privkey': privkey,
|
||||
'privkey_file': {
|
||||
'Private-key-format': 'v1.3',
|
||||
'Algorithm': f'{algorithm} ({algorithm_name})',
|
||||
'Modulus': long_to_base64(modulo),
|
||||
'PublicExponent': long_to_base64(public_exponent),
|
||||
'PrivateExponent': long_to_base64(private_exponent),
|
||||
'Prime1': long_to_base64(prime1),
|
||||
'Prime2': long_to_base64(prime2),
|
||||
'Exponent1': long_to_base64(exponent1),
|
||||
'Exponent2': long_to_base64(exponent2),
|
||||
'Coefficient': long_to_base64(coefficient),
|
||||
'Created': 20230428110109,
|
||||
'Publish': 20230428110109,
|
||||
'Activate': 20230428110109,
|
||||
},
|
||||
}
|
||||
|
||||
value = f"{flags} {protocol} {algorithm} {dnskey}"
|
||||
print(f"""
|
||||
{domain}. IN DNSKEY {value}
|
||||
""")
|
||||
|
||||
# DS
|
||||
#
|
||||
# https://gist.github.com/wido/4c6288b2f5ba6d16fce37dca3fc2cb4a#file-dnskey_to_dsrecord-py-L40
|
||||
|
||||
|
||||
def _calc_ds(domain, flags, protocol, algorithm, dnskey):
|
||||
if domain.endswith('.') is False:
|
||||
domain += '.'
|
||||
def _calc_ds(zone, flags, protocol, algorithm, dnskey):
|
||||
if zone.endswith('.') is False:
|
||||
zone += '.'
|
||||
|
||||
signature = bytes()
|
||||
for i in domain.split('.'):
|
||||
for i in zone.split('.'):
|
||||
signature += pack('B', len(i)) + i.encode()
|
||||
|
||||
signature += pack('!HBB', int(flags), int(protocol), int(algorithm))
|
||||
|
@ -103,8 +108,88 @@ def _calc_keyid(flags, protocol, algorithm, dnskey):
|
|||
|
||||
return ((cnt & 0xFFFF) + (cnt >> 16)) & 0xFFFF
|
||||
|
||||
keyid = _calc_keyid(flags, protocol, algorithm, dnskey)
|
||||
ds = _calc_ds(domain, flags, protocol, algorithm, dnskey)
|
||||
def dnskey_to_ds(zone, flags, protocol, algorithm, dnskey):
|
||||
keyid = _calc_keyid(flags, protocol, algorithm, dnskey)
|
||||
ds = _calc_ds(zone, flags, protocol, algorithm, dnskey)
|
||||
|
||||
print(f"{domain}. IN DS {str(keyid)} {str(algorithm)} 1 {ds['sha1'].lower()}")
|
||||
print(f"{domain}. IN DS {str(keyid)} {str(algorithm)} 2 {ds['sha256'].lower()}")
|
||||
return[
|
||||
f"{zone}. IN DS {str(keyid)} {str(algorithm)} 1 {ds['sha1'].lower()}",
|
||||
f"{zone}. IN DS {str(keyid)} {str(algorithm)} 2 {ds['sha256'].lower()}",
|
||||
]
|
||||
|
||||
# Result
|
||||
|
||||
#@cache_to_disk(30)
|
||||
def generate_dnssec_for_zone(zone):
|
||||
zsk_data = generate_signing_key_pair(zone, salt='zsk')
|
||||
ksk_data = generate_signing_key_pair(zone, salt='ksk')
|
||||
ds_records = dnskey_to_ds(zone, flags, protocol, algorithm, ksk_data['dnskey'])
|
||||
|
||||
return {
|
||||
'zsk_data': zsk_data,
|
||||
'ksk_data': ksk_data,
|
||||
'ds_records': ds_records,
|
||||
}
|
||||
|
||||
print(
|
||||
generate_dnssec_for_zone(zone),
|
||||
)
|
||||
|
||||
|
||||
# #########################
|
||||
|
||||
# from dns import rrset, rdatatype, rdata
|
||||
# from dns.rdataclass import IN
|
||||
# from dns.dnssec import sign, make_dnskey
|
||||
# from dns.name import Name
|
||||
# from dns.rdtypes.IN.A import A
|
||||
|
||||
# data = generate_dnssec_for_zone(zone)
|
||||
# zone_name = Name(f'{zone}.'.split('.'))
|
||||
# assert zone_name.is_absolute()
|
||||
|
||||
# # rrset = rrset.from_text_list(
|
||||
# # name=Name(['test']).derelativize(zone_name),
|
||||
# # origin=zone_name,
|
||||
# # relativize=False,
|
||||
# # ttl=60,
|
||||
# # rdclass=IN,
|
||||
# # rdtype=rdatatype.from_text('A'),
|
||||
# # text_rdatas=[
|
||||
# # '100.2.3.4',
|
||||
# # '10.0.0.55',
|
||||
# # ],
|
||||
# # )
|
||||
|
||||
# rrset = rrset.from_rdata_list(
|
||||
# name=Name(['test']).derelativize(zone_name),
|
||||
# ttl=60,
|
||||
# rdatas=[
|
||||
# rdata.from_text(
|
||||
# rdclass=IN,
|
||||
# rdtype=rdatatype.from_text('A'),
|
||||
# origin=zone_name,
|
||||
# tok='1.2.3.4',
|
||||
# relativize=False,
|
||||
# ),
|
||||
# A(IN, rdatatype.from_text('A'), '10.20.30.40')
|
||||
# ],
|
||||
# )
|
||||
|
||||
# # for e in rrset:
|
||||
# # print(e.is_absolute())
|
||||
|
||||
# dnskey = make_dnskey(
|
||||
# public_key=data['zsk_data']['privkey'].public_key(),
|
||||
# algorithm=algorithm,
|
||||
# flags=flags,
|
||||
# protocol=protocol,
|
||||
# )
|
||||
|
||||
# sign(
|
||||
# rrset=rrset,
|
||||
# private_key=data['zsk_data']['privkey'],
|
||||
# signer=Name(f'{zone}.'),
|
||||
# dnskey=dnskey,
|
||||
# lifetime=99999,
|
||||
# )
|
||||
|
|
47
bin/test
Executable file
47
bin/test
Executable file
|
@ -0,0 +1,47 @@
|
|||
import dns.zone
|
||||
import dns.rdatatype
|
||||
import dns.rdataclass
|
||||
import dns.dnssec
|
||||
|
||||
# Define the zone name and domain names
|
||||
zone_name = 'example.com.'
|
||||
a_name = 'www.example.com.'
|
||||
txt_name = 'example.com.'
|
||||
mx_name = 'example.com.'
|
||||
|
||||
# Define the DNSKEY algorithm and size
|
||||
algorithm = 8
|
||||
key_size = 2048
|
||||
|
||||
# Generate the DNSSEC key pair
|
||||
keypair = dns.dnssec.make_dnskey(algorithm, key_size)
|
||||
|
||||
# Create the zone
|
||||
zone = dns.zone.Zone(origin=zone_name)
|
||||
|
||||
# Add A record to zone
|
||||
a_rrset = zone.get_rdataset(a_name, rdtype=dns.rdatatype.A, create=True)
|
||||
a_rrset.add(dns.rdataclass.IN, dns.rdatatype.A, '192.0.2.1')
|
||||
|
||||
# Add TXT record to zone
|
||||
txt_rrset = zone.get_rdataset(txt_name, rdtype=dns.rdatatype.TXT, create=True)
|
||||
txt_rrset.add(dns.rdataclass.IN, dns.rdatatype.TXT, 'Hello, world!')
|
||||
|
||||
# Add MX record to zone
|
||||
mx_rrset = zone.get_rdataset(mx_name, rdtype=dns.rdatatype.MX, create=True)
|
||||
mx_rrset.add(dns.rdataclass.IN, dns.rdatatype.MX, '10 mail.example.com.')
|
||||
|
||||
# Create the DNSKEY record for the zone
|
||||
key_name = f'{keypair.name}-K{keypair.fingerprint()}'
|
||||
dnskey_rrset = dns.rrset.RRset(name=keypair.name, rdclass=dns.rdataclass.IN, rdtype=dns.rdatatype.DNSKEY)
|
||||
dnskey_rrset.ttl = 86400
|
||||
dnskey_rrset.add(dns.rdataclass.IN, dns.rdatatype.DNSKEY, keypair.key, key_name=key_name)
|
||||
|
||||
# Add the DNSKEY record to the zone
|
||||
zone.replace_rdataset(keypair.name, dnskey_rrset)
|
||||
|
||||
# Sign the zone with the DNSSEC key pair
|
||||
dns.dnssec.sign_zone(zone, keypair, inception=0, expiration=3600)
|
||||
|
||||
# Print the resulting zone with the RRSIG records
|
||||
print(zone.to_text())
|
|
@ -23,4 +23,7 @@ DNSKEY:
|
|||
|
||||
DS
|
||||
|
||||
### sub
|
||||
|
||||
ZSK/KSK:
|
||||
https://www.cloudflare.com/de-de/dns/dnssec/how-dnssec-works/
|
||||
|
|
Loading…
Reference in a new issue