DNSSEC Short-term Key Rotation

Overview

BIND9/Design/DNSSEC describes very basic functionality where DNS zone is signed with provided signing keys. Those keys need to be generated and periodically rotated.

This document describes short-term solution for automatic key generation/rotation feature. See also DNSSEC Longterm Key Rotation. We decided to go this way because we think that short-term solution is much easier to achieve so we will be able to test our solution before we decide to invest in long-term effort.

References:

Use Cases

  • Signing keys need to be generated when user decides to enable DNSSEC for particular DNS zone.
  • Signing keys should be rotated as described in documents referenced above.

Design

../../../../_images/dnssec-key-rotation-shortterm.png
  • Black boxes in the diagram already exist.
  • “Dashed” blue boxes already exist but need some patching.
  • We need to write solid blue boxes from scratch.

On sigle node

  • Run OpenDNSSEC Enforcer daemon on a single node as a key generator.
  • Use hardcoded/pre-defined key rotation policy (aka KASP).
  • Replace OpenDNSSEC Signer daemon with own script which will export (wrapped) keys from SoftHSM to LDAP DB. This script also needs to store necessary DNSSEC-metadata (timestamps, key flags etc.) to LDAP DB.
  • Run own little daemon which will import list of DNS zones from LDAP to OpenDNSSEC daemon/configuration.

On all nodes

  • Use the SoftHSM as the PKCS#11 provider for OpenDNSSEC and BIND.
  • Use own little daemon for key import from LDAP DB to SoftHSM:
  • LDAP schema should be forward-compatible so migration from this short-term to DNSSEC Longterm Key Rotation will not require changes in LDAP schema and data migration.

Key distribution

The basic idea is that DNSSEC private keys will never be sent or stored to LDAP DB unencrypted. All cryptographic operations will be done inside SoftHSM.

There are three levels of keys, see related discussion in mailing list thread: https://www.redhat.com/archives/freeipa-devel/2014-April/msg00565.html

If mailing list contradicts statements in this document contradicts then this design document is authoritative “source of truth” and takes precedence.

This design page summarizes workflows related to key management. Results of key generation and unwrapping (raw keys) are always stored in SoftHSM. Wrapped keys can be stored in LDAP or somewhere else.

First server installation (replica1)

This operation needs to be detected and guarded so only one replica will become DNSSEC master. It can be done by writing who is DNSSEC master to cn=masters.

  1. Generate symmetric “master key” for DNSSEC
  2. Generate replica key pair:
    • Store public key to LDAP: cn=DNS,cn=<replica FQDN>,cn=masters,cn=ipa,cn=etc,dc=example sub-tree in LDAP.
    • Store private key to SoftHSM (on disk).
  3. Wrap master key with replica key:
    • Store resulting blob in cn=master, cn=keys, cn=sec, cn=dns, dc=example sub-tree in LDAP.

Replica installation (replica2)

This is done by ipa-replica-install:

  1. Generate replica key pair:
  2. Store public key to LDAP: cn=DNS,cn=<replica FQDN>,cn=masters,cn=ipa,cn=etc,dc=example sub-tree in LDAP.
  3. Store private key in SoftHSM (on disk).

This is done by little daemon running on replica 1:

  1. DNSSEC master-replica will detect (SyncRepl) a new public key in cn=masters sub-tree and will use own private key (replica1‘s) to re-encrypt master key using public key of the new replica (replica2)
  2. Store resulting master-key-blob to cn=master, cn=keys, cn=sec, cn=dns, dc=example sub-tree in LDAP.`

This is done by little daemon running on replica 2:

  1. Download blob with master key from LDAP.
  2. Unwrap blob with replica2‘s private key.
  3. Store raw master key to local SoftHSM.

Replica uninstallation (replica2)

NOTE: replica1 cannot be uninstalled at the moment. Support for this scenario requires more work.

NOTE: Old master keys remain in LDAP and old DNSSEC keys are not touched at all. After all, all keys used before replica removal are on replica2‘s disk anyway.

This step is done by ipa-replica-manage:

  1. Delete replica entry in cn=masters.

All operations with keys are done by little daemon running on replica1:

  1. Detect that replica2‘s public key was deleted and initiate master key rotation.
  2. Generate a new master key.
  3. Wrap new master key with all currently published replica-public keys (from cn=masters) and store it to LDAP

DNSSEC key generation (replica1)

Key wrapping is done only by replica1 which is running OpenDNSSEC Enforcer daemon.

  1. Let OpenDNSSEC Enforcer to generate a key according to time schedule. (OpenDNSSEC Enforcer calls OpenDNSSEC Signer daemon which is replaced by our script.)
  2. Wrap DNSSEC key with newest master key (stored in local SoftHSM) and store resulting blob to cn=keys, cn=sec, cn=dns.
  3. Store key metadata to cn=keys, idnsname=example.net, cn=dns

DNSSEC key unwrapping (replica2)

This is done by little daemons running on all replicas with DNS.

  1. Detect a new key in LDAP (SyncRepl).
  2. Download blob from LDAP.
  3. Unwrap blob with master key described by ipaWrappingKey attribute.
  4. Store raw DNSSEC key in local SoftHSM.

DNSSEC key usage (replica1 + replica2)

Prerequisites:

  • Unwrapped DNSSEC private keys are available in local SoftHSM.
  1. Generate BIND key-file with key metadata (cn=keys, idnsname=example.net, cn=dns).
  2. Ping BIND to reload keys for given zone.

LDAP schema

DNSSEC data are logically separated and stored in different parts of LDAP DIT:

dc=example
├── cn=dns
│   ├── cn=sec
│   │   └── cn=keys
│   │       ├── ipk11UniqueId=00555c3d4e5f
│   │       │   ^ DNS zone public + private key + PKCS#11 metadata
│   │       ├── ipk11UniqueId=00abcdef1234
│   │       │   ^ DNS zone public + private key + PKCS#11 metadata
│   │       ├── ipk11UniqueId=111b2c3d4e90
│   │       │   ^ replica1 public key + PKCS#11 metadata
│   │       ├── ipk11UniqueId=1234567890ab
│   │       │   ^ replica2 public key + PKCS#11 metadata
│   │       ├── ipk11UniqueId=9876543210fe
│   │       │   ^ master secret key - PKCS#11 metadata + pointers to key data
│   │       ├── ipk11UniqueId=abcdef012345
│   │       │   ^ master secret key data for replica1
│   │       └── ipk11UniqueId=fedcba987654
│   │           ^ master secret key data for replica2
│   │
│   ├── idnsname=example.net
│   │   └── cn=keys
│   │       └── cn=ksk-20140716010203
│   │           ^ DNSSEC metadata only + PKCS#11 URI of the key
│   └── idnsname=example.org
│       └── cn=keys
│           └── cn=ksk-20141009010203
│               ^ DNSSEC metadata only + PKCS#11 URI of the key
└── cn=etc
    └── cn=ipa
        └── cn=masters
            ├── cn=replica1.example
            │   ├── cn=DNS
            │   ├── cn=DNSKeyExporter
            │   ├── cn=DNSKeySync
            │   └── cn=OpenDNSSEC
            └── cn=replica2.example
                ├── cn=DNS
                └── cn=DNSKeySync

This page is authoritative source of truth. Some details of the schema were changed many times and mailing list threads become incomprehensible.

Key material

Key is stored in objects desribed by LDAP schema for PKCS#11.

Discussion: https://www.redhat.com/archives/freeipa-devel/2014-April/msg00565.html

DNSSEC metadata

Metadata are stored in separate LDAP objects. The object contains PKCS#11 URI which points to the key material.

We have to have enough metadata to generate files which are usually produced by dnssec-keyfromlabel utility. The metadata have to contain timestamps which are necessary for key management according to RFC 6781.

Discussion: https://www.redhat.com/archives/freeipa-devel/2014-June/msg00305.html

Attributes
cn

Arbitrary name. It will be used as part of object DN.

  • Format: <DNSSEC key type>-<timestamp of key creation>-<PKCS#11 ID in hex>
  • Example: KSK-20140619125038Z-bba18ce06079720a33940ec5dee077ef
idnsSecAlgorithm
( 2.16.840.1.113730.3.8.5.27
  NAME 'idnsSecAlgorithm'
  DESC 'DNSKEY algorithm: string used as mnemonic'
  EQUALITY caseIgnoreIA5Match
  SUBSTR caseIgnoreIA5SubstringsMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
  SINGLE-VALUE
  X-ORIGIN 'IPA v4' )
idnsSecKeyCreated
( 2.16.840.1.113730.3.8.5.19
  NAME 'idnsSecKeyCreated'
  DESC 'DNSSEC key creation timestamp'
  EQUALITY generalizedTimeMatch
  ORDERING generalizedTimeOrderingMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
  SINGLE-VALUE
  X-ORIGIN 'IPA v4' )
idnsSecKeyRef
( 2.16.840.1.113730.3.8.5.28
  NAME 'idnsSecKeyRef'
  DESC 'PKCS#11 URI of the key'
  EQUALITY caseExactMatch
  SINGLE-VALUE
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
idnsSecKeyActivate
( 2.16.840.1.113730.3.8.5.21
  NAME 'idnsSecKeyActivate'
  DESC 'DNSSEC key (planned) activation time'
  EQUALITY generalizedTimeMatch
  ORDERING generalizedTimeOrderingMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
  SINGLE-VALUE
  X-ORIGIN 'IPA v4' )
idnsSecKeyDelete
( 2.16.840.1.113730.3.8.5.23
  NAME 'idnsSecKeyDelete'
  DESC 'DNSSEC key (planned) deletion timestamp'
  EQUALITY generalizedTimeMatch
  ORDERING generalizedTimeOrderingMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
  SINGLE-VALUE
  X-ORIGIN 'IPA v4' )
idnsSecKeyInactive
( 2.16.840.1.113730.3.8.5.22
  NAME 'idnsSecKeyInactive'
  DESC 'DNSSEC key (planned) inactivation time'
  EQUALITY generalizedTimeMatch
  ORDERING generalizedTimeOrderingMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
  SINGLE-VALUE
  X-ORIGIN 'IPA v4' )
idnsSecKeyPublish
( 2.16.840.1.113730.3.8.5.20
  NAME 'idnsSecKeyPublish'
  DESC 'DNSSEC key (planned) publication time'
  EQUALITY generalizedTimeMatch
  ORDERING generalizedTimeOrderingMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
  SINGLE-VALUE
  X-ORIGIN 'IPA v4' )
idnsSecKeyRevoke
( 2.16.840.1.113730.3.8.5.25
  NAME 'idnsSecKeyRevoke'
  DESC 'DNSKEY REVOKE flag (equivalent to bit 8): RFC 5011'
  EQUALITY booleanMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
  SINGLE-VALUE
  X-ORIGIN 'IPA v4' )
idnsSecKeySep
( 2.16.840.1.113730.3.8.5.26
  NAME 'idnsSecKeySep'
  DESC 'DNSKEY SEP flag (equivalent to bit 15): RFC 4035'
  EQUALITY booleanMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
  SINGLE-VALUE
  X-ORIGIN 'IPA v4' )
idnsSecKeyZone
( 2.16.840.1.113730.3.8.5.24
  NAME 'idnsSecKeyZone'
  DESC 'DNSKEY ZONE flag (equivalent to bit 7): RFC 4035'
  EQUALITY booleanMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
  SINGLE-VALUE
  X-ORIGIN 'IPA v4' )
Object class

Represents metadata for single DNSSEC key pair.

( 2.16.840.1.113730.3.8.6.4
  NAME 'idnsSecKey'
  DESC 'DNSSEC key metadata' STRUCTURAL
  MUST ( idnsSecKeyRef $ idnsSecKeyCreated $ idnsSecAlgorithm )
  MAY ( idnsSecKeyPublish $ idnsSecKeyActivate $ idnsSecKeyInactive $ idnsSecKeyDelete $ idnsSecKeyZone $ idnsSecKeyRevoke $ idnsSecKeySep $ cn )
  X-ORIGIN 'IPA v4' )

Implementation

Any additional requirements or changes discovered during the implementation phase:

SoftHSM v2

We have to use version 2 because SoftHSM v1 depends on Botan which we don’t like.

SoftHSM v2 itself needs some enhancements:

  • Add support for key wrapping with asymmetric keys - PKCS_RSA method
  • CKA_SENSITIVE flag handling is somehow broken:
  • Add support for key wrapping with asymmetric keys - PKCS_RSA_OAEP method
  • AES key wrapping with padding did not use standard OpenSSL API:

OpenSSL

SoftHSM’s support for key wrapping with symmetric keys is present in the code base but it depends on support in OpenSSL. This support was not available in upstream without patch.

BIND

It is not that easy to export raw keys from SoftHSM v2 so we should use PKCS#11 interface to interconnect BIND and SoftHSM directly.

This interface have to be backported from BIND 9.10 to BIND 9.9 which is a non-trivial task.

OpenDNSSEC

  • Package was missing in RHEL: RHEL bug 1117174
  • CKA_EXTRACTABLE flag for private keys is not configurable:
  • Key state from OpenDNSSEC has to be carefully transformed to key state understood by BIND: See separate design page

python-ldap

Feature Management

For simplicity, this short-term solution will not provide any explicit user interface for key management. In emergency cases user can use OpenDNSSEC command line tools to enforce key rotation.

FreeIPA will provide only minimal user interface for installation/upgrade and DNSSEC enablement for particular DNS zone.

Commands to enable/disable key generation and zone signing:

$ ipa dnszone-mod example.com. --dnssec=TRUE
$ ipa dnszone-mod example.com. --dnssec=FALSE

New command ipa-dnssec-install will “elect” one replica to be DNSSEC-key-master.

Replication

No expected impact. Key data will be replicated from one node to others, no replication conflicts are expected. New data will be written approximatelly once per month per DNS zone.

Updates and Upgrades

Upgrade has to install and configure all new depedencies. One replica has to be manually selected as DNSSEC key master after upgrade/installation: See https://www.redhat.com/archives/freeipa-devel/2014-July/msg00182.html for discussion.

Dependencies

New depedencies:

  • OpenDNSSEC suite
  • SoftHSM v2
  • sufficiently new version of OpenSSL
  • Scripts or other mechanism pro bidirectional synchronization with LDAP
  • Bunch of single-purpose tools for PKCS#11 operations from command line (only used internally by FreeIPA)

External Impact

No impact was expected at the beginning but chain of consequences starting with SoftHSM v1->SoftHSM v2 transition led to depedencies.

Backup and Restore

  • Signing keys can be re-generated after restore. This will require cooperation with operator of parent DNS zone because hashes of new keys have to be uploaded to the parent zone. For security reasons, I still prefer not to backup DNS signing keys.
  • KASP will be hardcoded so there is nothing to restore.

Test Plan

Test scenarios that will be transformed to test cases for FreeIPA Continuous Integration during implementation or review phase.

  • General idea: Muck with system clock on OpenDNSSEC node and check if new keys are generated as necessary.

  • I have got suggestion from Siôn Lloyd:

    We have a feature here that may help you... When you configure/build the enforcer you can include a flag “–enable-timeshift” which allows you to overwrite the time that the enforcer uses... With this flag set the enforcerd process looks for an environment variable set via, e.g.:

    export ENFORCER_TIMESHIFT=‘01-01-2010 12:00:00’

    If this exists it will run once as if it is that time then exit. We use this feature in some of our tests as it means we can have deterministic results. Maybe this would be useful for you too?

    —Siôn Lloyd

RFE Author

Petr Spacek <pspacek@…>