dnsbase-1.0.0.0: src/Net/DNSBase/RData/NSEC.hs
{-|
Module : Net.DNSBase.RData.NSEC
Description : DNSSEC denial-of-existence records (NSEC, NSEC3, NSEC3PARAM, NXT)
Copyright : (c) Viktor Dukhovni, 2026
License : BSD-3-Clause
Maintainer : ietf-dane@dukhovni.org
Stability : unstable
DNSSEC denial-of-existence machinery. 'T_nsec' (RFC 4034) names
the next existing owner in canonical order alongside a bitmap of
present RR types at the proving name. 'T_nsec3' (RFC 5155) is
the hashed variant, where the next-name pointer is the hashed
owner. 'T_nsec3param' (RFC 5155) carries the zone-wide NSEC3
hashing parameters at the zone apex. 'T_nxt' (RFC 2535) is the
obsolete predecessor of NSEC, defined here for compatibility
with archival zone data.
-}
{-# LANGUAGE
NegativeLiterals
, RecordWildCards
#-}
module Net.DNSBase.RData.NSEC
( -- * NSEC, NSEC3, and NSEC Type Bitmap structures
T_nsec(..)
, T_nsec3(..)
, T_nsec3param(..)
, NsecTypes
, nsecTypesFromList
, nsecTypesToList
, hasRRtype
-- * Obsolete NXT structure
, T_nxt(..)
, NxtTypes
, NxtRRtype
, toNxtTypes
, nxtTypesFromNE
, nxtTypesToNE
, hasNxtRRtype
, module Net.DNSBase.NonEmpty
) where
import qualified Data.ByteString.Short as SB
import Net.DNSBase.Internal.Util
import Net.DNSBase.Bytes
import Net.DNSBase.Decode.Domain
import Net.DNSBase.Decode.State
import Net.DNSBase.Domain
import Net.DNSBase.Encode.State
import Net.DNSBase.NonEmpty
import Net.DNSBase.NsecTypes
import Net.DNSBase.Present
import Net.DNSBase.RData
import Net.DNSBase.RRTYPE
import Net.DNSBase.Secalgs
import Net.DNSBase.Text
-----------------
-- | The @NSEC@ resource record
-- ([RFC 4034 section 4](https://datatracker.ietf.org/doc/html/rfc4034#section-4))
-- — the building block of authenticated denial of existence: a
-- 'Domain' naming the next existing owner in the zone's canonical
-- order, plus an 'NsecTypes' bitmap of RR types present at the
-- proving name.
--
-- The next-owner-name field is not subject to wire-form name
-- compression
-- ([RFC 3597 section 4](https://datatracker.ietf.org/doc/html/rfc3597#section-4))
-- and is not lower-cased when computing canonical wire form
-- ([RFC 6840 section 5.1](https://datatracker.ietf.org/doc/html/rfc6840#section-5.1)).
--
-- See 'T_nsec3' for the hashed-name variant.
data T_nsec = T_NSEC
{ nsecNext :: Domain
, nsecTypes :: NsecTypes
} deriving (Eq, Show)
instance Ord T_nsec where
a `compare` b = nsecNext a `compare` nsecNext b
<> nsecTypes a `compare` nsecTypes b
instance Presentable T_nsec where
present T_NSEC{..} =
present nsecNext
. presentSp nsecTypes
instance KnownRData T_nsec where
rdType _ = NSEC
{-# INLINE rdType #-}
rdEncode T_NSEC{..} = do
putSizedBuilder $ mbWireForm nsecNext
putNsecTypes nsecTypes
rdDecode _ _ len = do
pos0 <- getPosition
nsecNext <- getDomainNC
used <- subtract pos0 <$> getPosition
nsecTypes <- getNsecTypes (len - used)
pure $ RData T_NSEC{..}
-- | The @NSEC3@ resource record
-- ([RFC 5155 section 3.2](https://tools.ietf.org/html/rfc5155#section-3.2))
-- — the hashed denial-of-existence variant. The next-owner-name
-- field carries the hashed equivalent rather than the plain name,
-- and the record itself includes the hashing parameters
-- (algorithm, flags, iteration count, salt) needed to reproduce
-- the hash. The trailing 'NsecTypes' bitmap names the RR types
-- present at the proving (un-hashed) name.
--
-- > 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
-- > 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
-- > +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-- > | Hash Alg. | Flags | Iterations |
-- > +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-- > | Salt Length | Salt /
-- > +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-- > | Hash Length | Next Hashed Owner Name /
-- > +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-- > / Type Bit Maps /
-- > +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
--
-- The 'Ord' instance compares the fields in wire-encoding order,
-- using 'dnsTextCmp' on the length-prefixed salt and hashed-name
-- bytes, so it agrees with the canonical RR-content ordering of
-- [RFC 4034 section 6.2](https://datatracker.ietf.org/doc/html/rfc4034#section-6.2).
--
-- See 'T_nsec' for the un-hashed variant and 'T_nsec3param' for the
-- zone-apex parameter record.
data T_nsec3 = T_NSEC3
{ nsec3Alg :: NSEC3HashAlg
, nsec3Flags :: Word8
, nsec3Iters :: Word16
, nsec3Salt :: ShortByteString
, nsec3Next :: ShortByteString
, nsec3Types :: NsecTypes
} deriving (Eq)
instance Ord T_nsec3 where
a `compare` b = nsec3Alg a `compare` nsec3Alg b
<> nsec3Flags a `compare` nsec3Flags b
<> nsec3Iters a `compare` nsec3Iters b
<> nsec3Salt a `dnsTextCmp` nsec3Salt b
<> nsec3Next a `dnsTextCmp` nsec3Next b
<> nsec3Types a `compare` nsec3Types b
instance Show T_nsec3 where
showsPrec p T_NSEC3{..} = showsP p $
showString "T_NSEC3 "
. shows' nsec3Alg . showChar ' '
. shows' nsec3Flags . showChar ' '
. shows' nsec3Iters . showChar ' '
. showSalt nsec3Salt . showChar ' '
. showNext nsec3Next . showChar ' '
. shows' nsec3Types
where
showNext s = shows @Bytes32 (coerce s)
showSalt s | SB.null s = showChar '-'
| otherwise = shows @Bytes16 (coerce s)
instance Presentable T_nsec3 where
present T_NSEC3{..} =
present nsec3Alg
. presentSp nsec3Flags
. presentSp nsec3Iters
. presentSalt nsec3Salt
. presentNext nsec3Next
. presentSp nsec3Types
where
presentNext s = presentSp @Bytes32 (coerce s)
presentSalt s | SB.null s = presentSp '-'
| otherwise = presentSp @Bytes16 (coerce s)
instance KnownRData T_nsec3 where
rdType _ = NSEC3
{-# INLINE rdType #-}
rdEncode T_NSEC3{..} = do
putSizedBuilder $
coerce mbWord8 nsec3Alg
<> mbWord8 nsec3Flags
<> mbWord16 nsec3Iters
<> mbShortByteStringLen8 nsec3Salt
<> mbShortByteStringLen8 nsec3Next
putNsecTypes nsec3Types
rdDecode _ _ len = do
pos0 <- getPosition
nsec3Alg <- NSEC3HashAlg <$> get8
nsec3Flags <- get8
nsec3Iters <- get16
nsec3Salt <- getShortByteStringLen8
nsec3Next <- getShortByteStringLen8
used <- subtract pos0 <$> getPosition
nsec3Types <- getNsecTypes (len - used)
pure $ RData T_NSEC3{..}
-- | The @NSEC3PARAM@ resource record
-- ([RFC 5155 section 4.2](https://tools.ietf.org/html/rfc5155#section-4.2))
-- — a zone-apex record describing the NSEC3 hashing parameters
-- (algorithm, iteration count, salt) in use across the zone's
-- NSEC3 chain. Validating resolvers do not consult this record
-- (each 'T_nsec3' carries its own parameters in the RDATA); it
-- exists for authoritative-server tooling.
--
-- > 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
-- > 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
-- > +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-- > | Hash Alg. | Flags | Iterations |
-- > +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-- > | Salt Length | Salt /
-- > +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
--
-- (Editorial: the salt and iteration count were largely a bad
-- idea in retrospect; best practice for zone signers is to set
-- the salt empty and the iteration count to zero.)
--
-- See 'T_nsec3' for the records produced under these parameters.
data T_nsec3param = T_NSEC3PARAM
{ nsec3paramAlg :: NSEC3HashAlg
, nsec3paramFlags :: Word8
, nsec3paramIters :: Word16
, nsec3paramSalt :: ShortByteString
} deriving (Eq, Show)
instance Ord T_nsec3param where
compare a b =
comparing nsec3paramAlg a b
<> comparing nsec3paramFlags a b
<> comparing nsec3paramIters a b
<> dnsTextCmp (nsec3paramSalt a) (nsec3paramSalt b)
instance Presentable T_nsec3param where
present T_NSEC3PARAM{..} =
present nsec3paramAlg
. presentSp nsec3paramFlags
. presentSp nsec3paramIters
. presentSalt nsec3paramSalt
where
presentSalt s | SB.null s = presentSp '-'
| otherwise = presentSp @Bytes16 (coerce s)
instance KnownRData T_nsec3param where
rdType _ = NSEC3PARAM
{-# INLINE rdType #-}
rdEncode T_NSEC3PARAM{..} = putSizedBuilder $
mbWord8 (coerce nsec3paramAlg)
<> mbWord8 nsec3paramFlags
<> mbWord16 nsec3paramIters
<> mbShortByteStringLen8 nsec3paramSalt
rdDecode _ _ = const do
nsec3paramAlg <- NSEC3HashAlg <$> get8
nsec3paramFlags <- get8
nsec3paramIters <- get16
nsec3paramSalt <- getShortByteStringLen8
pure $ RData T_NSEC3PARAM{..}
-- | The @NXT@ resource record
-- ([RFC 2535 section 5.2](https://www.rfc-editor.org/rfc/rfc2535.html#section-5.2))
-- — the obsolete predecessor of 'T_nsec', defined here for
-- compatibility with archival DNSSEC zone data. Same conceptual
-- shape as @NSEC@ (next owner name + type bitmap) but with a
-- different type-bitmap encoding.
--
-- > 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
-- > 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
-- > +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-- > | next domain name /
-- > +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-- > | type bit map /
-- > +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
--
-- The domain name is wire-form name-compressed on decode only
-- ([RFC 3597 section 4](https://datatracker.ietf.org/doc/html/rfc3597#section-4))
-- and canonicalises to lower case
-- ([RFC 4034 section 6.2](https://datatracker.ietf.org/doc/html/rfc4034#section-6.2)).
-- The 'Eq' and 'Ord' instances compare the domain field in
-- canonical wire form (via 'equalWireHost' / 'compareWireHost')
-- and the type bitmap byte-wise.
--
-- See 'T_nsec' for the modern replacement.
data T_nxt = T_NXT
{ nxtNext :: Domain
, nxtBits :: NxtTypes
}
instance Show T_nxt where
showsPrec p T_NXT{..} = showsP p $
showString "T_NXT "
. shows' nxtNext . showChar ' '
. shows' nxtBits
instance Eq T_nxt where
a == b = nxtNext a `equalWireHost` nxtNext b
&& nxtBits a == nxtBits b
instance Ord T_nxt where
a `compare` b = nxtNext a `compareWireHost` nxtNext b
<> nxtBits a `compare` nxtBits b
instance Presentable T_nxt where
present T_NXT{..} =
present nxtNext
. presentSp nxtBits
instance KnownRData T_nxt where
rdType _ = NXT
{-# INLINE rdType #-}
rdEncode T_NXT{..} = putSizedBuilder $
mbWireForm nxtNext
<> mbShortByteString (coerce nxtBits)
cnEncode rd@(T_NXT{nxtNext = d}) =
rdEncode rd {nxtNext = canonicalise d}
rdDecode _ _ len = do
pos0 <- getPosition
nxtNext <- getDomain
used <- subtract pos0 <$> getPosition
nxtBits <- getNxtTypes (len - used)
pure $ RData $ T_NXT{..}