From 4ae76aa2ddef8760bd25769832a662bf81a763bf Mon Sep 17 00:00:00 2001 From: Joshua Tauberer Date: Sat, 4 Oct 2014 17:29:42 +0000 Subject: [PATCH] dnssec: use RSASHA256 keys for .email domains --- management/dns_update.py | 12 +++++++++++- management/status_checks.py | 9 ++++++--- setup/dns.sh | 27 +++++++++++++++++++++------ setup/migrate.py | 7 +++++++ 4 files changed, 45 insertions(+), 10 deletions(-) diff --git a/management/dns_update.py b/management/dns_update.py index b9aa2612..43072f59 100755 --- a/management/dns_update.py +++ b/management/dns_update.py @@ -479,8 +479,18 @@ zone: ######################################################################## +def dnssec_choose_algo(domain, env): + if domain.endswith(".email"): + # At least at GoDaddy, this is the only algorithm supported. + return "RSASHA256" + + # For any domain we were able to sign before, don't change the algorithm + # on existing users. We'll probably want to migrate to SHA256 later. + return "RSASHA1-NSEC3-SHA1" + def sign_zone(domain, zonefile, env): - dnssec_keys = load_env_vars_from_file(os.path.join(env['STORAGE_ROOT'], 'dns/dnssec/keys.conf')) + algo = dnssec_choose_algo(domain, env) + dnssec_keys = load_env_vars_from_file(os.path.join(env['STORAGE_ROOT'], 'dns/dnssec/%s.conf' % algo)) # In order to use the same keys for all domains, we have to generate # a new .key file with a DNSSEC record for the specific domain. We diff --git a/management/status_checks.py b/management/status_checks.py index 0dd60125..6338b4ff 100755 --- a/management/status_checks.py +++ b/management/status_checks.py @@ -210,14 +210,15 @@ def check_dnssec(domain, env, dns_zonefiles, is_checking_primary=False): # Some registrars may want the public key so they can compute the digest. The DS # record that we suggest using is for the KSK (and that's how the DS records were generated). - dnssec_keys = load_env_vars_from_file(os.path.join(env['STORAGE_ROOT'], 'dns/dnssec/keys.conf')) + alg_name_map = { '7': 'RSASHA1-NSEC3-SHA1', '8': 'RSASHA256' } + dnssec_keys = load_env_vars_from_file(os.path.join(env['STORAGE_ROOT'], 'dns/dnssec/%s.conf' % alg_name_map[ds_alg])) dnsssec_pubkey = open(os.path.join(env['STORAGE_ROOT'], 'dns/dnssec/' + dnssec_keys['KSK'] + '.key')).read().split("\t")[3].split(" ")[3] # Query public DNS for the DS record at the registrar. ds = query_dns(domain, "DS", nxdomain=None) ds_looks_valid = ds and len(ds.split(" ")) == 4 if ds_looks_valid: ds = ds.split(" ") - if ds_looks_valid and ds[0] == ds_keytag and ds[1] == '7' and ds[3] == digests.get(ds[2]): + if ds_looks_valid and ds[0] == ds_keytag and ds[1] == ds_alg and ds[3] == digests.get(ds[2]): if is_checking_primary: return env['out'].print_ok("DNSSEC 'DS' record is set correctly at registrar.") else: @@ -236,7 +237,9 @@ def check_dnssec(domain, env, dns_zonefiles, is_checking_primary=False): env['out'].print_line("") env['out'].print_line("Key Tag: " + ds_keytag + ("" if not ds_looks_valid or ds[0] == ds_keytag else " (Got '%s')" % ds[0])) env['out'].print_line("Key Flags: KSK") - env['out'].print_line("Algorithm: 7 / RSASHA1-NSEC3-SHA1" + ("" if not ds_looks_valid or ds[1] == '7' else " (Got '%s')" % ds[1])) + env['out'].print_line( + ("Algorithm: %s / %s" % (ds_alg, alg_name_map[ds_alg])) + + ("" if not ds_looks_valid or ds[1] == ds_alg else " (Got '%s')" % ds[1])) # see http://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml env['out'].print_line("Digest Type: 2 / SHA-256") # http://www.ietf.org/assignments/ds-rr-types/ds-rr-types.xml diff --git a/setup/dns.sh b/setup/dns.sh index d1ca7304..faccd410 100755 --- a/setup/dns.sh +++ b/setup/dns.sh @@ -36,25 +36,39 @@ sudo mkdir -p /var/run/nsd # Create DNSSEC signing keys. mkdir -p "$STORAGE_ROOT/dns/dnssec"; -if [ ! -f "$STORAGE_ROOT/dns/dnssec/keys.conf" ]; then - echo "Generating DNSSEC signing keys. This may take a few minutes..." + +# TLDs don't all support the same algorithms, so we'll generate keys using a few +# different algorithms. +# +# Supports RSASHA1-NSEC3-SHA1 (didn't test with RSASHA256): +# .info and .me. +# +# Requires RSASHA256 +# .email +FIRST=1 +for algo in RSASHA1-NSEC3-SHA1 RSASHA256; do +if [ ! -f "$STORAGE_ROOT/dns/dnssec/$algo.conf" ]; then + if [ $FIRST == 1 ]; then + echo "Generating DNSSEC signing keys. This may take a few minutes..." + FIRST=0 + fi # Create the Key-Signing Key (KSK) (-k) which is the so-called # Secure Entry Point. Use a NSEC3-compatible algorithm (best # practice), and a nice and long keylength. The domain name we # provide ("_domain_") doesn't matter -- we'll use the same # keys for all our domains. - KSK=$(umask 077; cd $STORAGE_ROOT/dns/dnssec; ldns-keygen -a RSASHA1-NSEC3-SHA1 -b 2048 -k _domain_); + KSK=$(umask 077; cd $STORAGE_ROOT/dns/dnssec; ldns-keygen -a $algo -b 2048 -k _domain_); # Now create a Zone-Signing Key (ZSK) which is expected to be # rotated more often than a KSK, although we have no plans to # rotate it (and doing so would be difficult to do without # disturbing DNS availability.) Omit '-k' and use a shorter key. - ZSK=$(umask 077; cd $STORAGE_ROOT/dns/dnssec; ldns-keygen -a RSASHA1-NSEC3-SHA1 -b 1024 _domain_); + ZSK=$(umask 077; cd $STORAGE_ROOT/dns/dnssec; ldns-keygen -a $algo -b 1024 _domain_); # These generate two sets of files like: # - # * `K_domain_.+007+08882.ds`: DS record to provide to domain name registrar + # * `K_domain_.+007+08882.ds`: DS record normally provided to domain name registrar (but it's actually invalid with "_domain_") # * `K_domain_.+007+08882.key`: public key (goes into DS record & upstream DNS provider like your registrar) # * `K_domain_.+007+08882.private`: private key (secret!) @@ -62,11 +76,12 @@ if [ ! -f "$STORAGE_ROOT/dns/dnssec/keys.conf" ]; then # options. So we'll store the names of the files we just generated. # We might have multiple keys down the road. This will identify # what keys are the current keys. - cat > $STORAGE_ROOT/dns/dnssec/keys.conf << EOF; + cat > $STORAGE_ROOT/dns/dnssec/$algo.conf << EOF; KSK=$KSK ZSK=$ZSK EOF fi +done # Force the dns_update script to be run every day to re-sign zones for DNSSEC. cat > /etc/cron.daily/mailinabox-dnssec << EOF; diff --git a/setup/migrate.py b/setup/migrate.py index dac9bf84..cbc4b361 100755 --- a/setup/migrate.py +++ b/setup/migrate.py @@ -60,6 +60,13 @@ def migration_5(env): # The secret key for encrypting backups was world readable. Fix here. os.chmod(os.path.join(env["STORAGE_ROOT"], 'backup/secret_key.txt'), 0o600) +def migration_6(env): + # We now will generate multiple DNSSEC keys for different algorithms, since TLDs may + # not support them all. .email only supports RSA/SHA-256. Rename the keys.conf file + # to be algorithm-specific. + basepath = os.path.join(env["STORAGE_ROOT"], 'dns/dnssec') + shutil.move(os.path.join(basepath, 'keys.conf'), os.path.join(basepath, 'RSASHA1-NSEC3-SHA1.conf')) + def get_current_migration(): ver = 0 while True: