%% ------------------------------------------------------------------- %% %% Copyright (c) 2011 Andrew Tunnell-Jones. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. %% %% ------------------------------------------------------------------- -module(dnssec). -moduledoc """ The `dnssec` module exports functions used for generating NSEC/NSEC3 records, signing and verifying RRSIGs, and adding keytags to DNSKEY records. For example, the `sign_rr/6` function can be given a collection of resource records, the signer name, keytag, signing algorithm, private key, and a collection of options and it will return a list of RRSIG records. Supported signing algorithms include DSA, RSA (SHA1/SHA256/SHA512), ECDSA (P-256/P-384), Ed25519, and Ed448. """. %% API -export([gen_nsec/1, gen_nsec/3, gen_nsec/4]). -export([gen_nsec3/1, gen_nsec3/2]). -export([sign_rr/5, sign_rr/6]). -export([sign_rrset/5, sign_rrset/6]). -export([verify_rrsig/4]). -export([add_keytag_to_dnskey/1, add_keytag_to_cdnskey/1]). -export([canonical_rrdata_form/1]). -export([ih/4]). -include_lib("dns_erlang/include/dns.hrl"). -include_lib("public_key/include/public_key.hrl"). -export_type([ sigalg/0, nsec3_hashalg/0, nsec3_hashalg_fun/0, nsec3_salt/0, nsec3_iterations/0, gen_nsec_opts/0, gen_nsec3_opts/0, sign_rr_opts/0, verify_rrsig_opts/0, keytag/0, key/0, ds/0, dnskey/0, rrsig/0, nsec/0, nsec3/0 ]). -doc "DS (Delegation Signer) resource record data.". -type ds() :: #dns_rrdata_ds{}. -doc "DNSKEY resource record data.". -type dnskey() :: #dns_rrdata_dnskey{}. -doc "RRSIG resource record data.". -type rrsig() :: #dns_rrdata_rrsig{}. -doc "NSEC resource record data.". -type nsec() :: #dns_rrdata_nsec{}. -doc "NSEC3 resource record data.". -type nsec3() :: #dns_rrdata_nsec3{}. -doc """ DNSSEC signing algorithm identifier. Unlike `t:dns:alg/0`, this type is restricted to algorithms valid for zone signing. """. -type sigalg() :: ?DNS_ALG_DSA | ?DNS_ALG_NSEC3DSA | ?DNS_ALG_RSASHA1 | ?DNS_ALG_NSEC3RSASHA1 | ?DNS_ALG_RSASHA256 | ?DNS_ALG_RSASHA512 | ?DNS_ALG_ECDSAP256SHA256 | ?DNS_ALG_ECDSAP384SHA384 | ?DNS_ALG_ED25519 | ?DNS_ALG_ED448. -doc "NSEC3 hash algorithm identifier (currently only SHA-1).". -type nsec3_hashalg() :: ?DNSSEC_NSEC3_ALG_SHA1. -doc "Custom hash function for use with `ih/4`.". -type nsec3_hashalg_fun() :: fun((iodata()) -> binary()). -doc "NSEC3 salt value.". -type nsec3_salt() :: binary(). -doc "NSEC3 iteration count.". -type nsec3_iterations() :: non_neg_integer(). -doc "Options for `gen_nsec/4`.". -type gen_nsec_opts() :: #{base_types => [dns:type()]}. -doc "Options for `gen_nsec3/2`.". -type gen_nsec3_opts() :: gen_nsec_opts(). -doc "DNSKEY key tag (RFC 4034 Appendix B).". -type keytag() :: integer(). -doc "Cryptographic key material for signing or verification.". -type key() :: [binary()] | binary(). -doc "Options for `sign_rr/6` and `sign_rrset/6`.". -type sign_rr_opts() :: #{inception => dns:unix_time(), expiration => dns:unix_time()}. -doc "Options for `verify_rrsig/4`.". -type verify_rrsig_opts() :: #{now => dns:unix_time()}. -define(RSASHA1_PREFIX, <<16#30, 16#21, 16#30, 16#09, 16#06, 16#05, 16#2B, 16#0E, 16#03, 16#02, 16#1A, 16#05, 16#00, 16#04, 16#14>> ). -define(RSASHA256_PREFIX, <<16#30, 16#31, 16#30, 16#0d, 16#06, 16#09, 16#60, 16#86, 16#48, 16#01, 16#65, 16#03, 16#04, 16#02, 16#01, 16#05, 16#00, 16#04, 16#20>> ). -define(RSASHA512_PREFIX, <<16#30, 16#51, 16#30, 16#0d, 16#06, 16#09, 16#60, 16#86, 16#48, 16#01, 16#65, 16#03, 16#04, 16#02, 16#03, 16#05, 16#00, 16#04, 16#40>> ). -define(SECS_IN_YEAR, (365 * 24 * 60 * 60)). -doc """ Generate NSEC records from a list of `t:dns:rr/0`. The list must contain a SOA `t:dns:rr/0` which is used to determine zone name and TTL. """. -spec gen_nsec([dns:rr()]) -> [dns:rr()]. gen_nsec(RR) -> case lists:keyfind(?DNS_TYPE_SOA, #dns_rr.type, RR) of false -> erlang:error(badarg); #dns_rr{name = ZoneName} = Soa -> TTL = minimum_soa_ttl(Soa), gen_nsec(ZoneName, RR, TTL) end. -doc #{equiv => gen_nsec(ZoneName, RR, TTL, #{})}. -spec gen_nsec(dns:dname(), [dns:rr()], dns:ttl()) -> [dns:rr()]. gen_nsec(ZoneName, RR, TTL) -> gen_nsec(ZoneName, RR, TTL, #{}). -doc "Generate NSEC records.". -spec gen_nsec(dns:dname(), [dns:rr()], dns:ttl(), gen_nsec_opts()) -> [dns:rr()]. gen_nsec(ZoneNameM, RR, TTL, Opts) -> ZoneName = dns_domain:to_lower(ZoneNameM), BaseTypes = maps:get(base_types, Opts, [?DNS_TYPE_NSEC, ?DNS_TYPE_RRSIG]), Map = build_rrmap_gbt(RR, BaseTypes), Unsorted = [ #dns_rr{ name = Name, class = Class, type = ?DNS_TYPE_NSEC, ttl = TTL, data = #dns_rrdata_nsec{next_dname = Name, types = Types} } || {Name, Class} := Types <- Map ], Sorted = name_order(Unsorted), add_next_dname([], Sorted, ZoneName). -spec add_next_dname([dns:rr()], [dns:rr(), ...], binary()) -> [dns:rr(), ...]. add_next_dname(Added, [#dns_rr{data = Data} = RR | [#dns_rr{name = Next} | _] = ToAdd], ZoneName) -> NewRR = RR#dns_rr{data = Data#dns_rrdata_nsec{next_dname = Next}}, NewAdded = [NewRR | Added], add_next_dname(NewAdded, ToAdd, ZoneName); add_next_dname(Added, [#dns_rr{type = ?DNS_TYPE_NSEC, data = Data} = RR], ZoneName) -> NewRR = RR#dns_rr{data = Data#dns_rrdata_nsec{next_dname = ZoneName}}, lists:reverse([NewRR | Added]). -doc #{equiv => gen_nsec3(RRs, #{})}. -spec gen_nsec3([dns:rr()]) -> [dns:rr()]. gen_nsec3(RRs) -> gen_nsec3(RRs, #{}). -doc """ Generate NSEC3 records from a list of `t:dns:rr/0`. The list must contain a SOA `t:dns:rr/0` to source the zone name and TTL from as well as as an NSEC3Param `t:dns:rr/0` to source the hash algorithm, iterations and salt from. """. -spec gen_nsec3([dns:rr()], gen_nsec3_opts()) -> [dns:rr()]. gen_nsec3(RRs, Opts) -> case lists:keyfind(?DNS_TYPE_SOA, #dns_rr.type, RRs) of false -> erlang:error(badarg); #dns_rr{name = ZoneName} = Soa -> case lists:keyfind(?DNS_TYPE_NSEC3PARAM, #dns_rr.type, RRs) of false -> erlang:error(badarg); #dns_rr{ class = Class, data = #dns_rrdata_nsec3param{ hash_alg = HashAlg, iterations = Iter, salt = Salt } } -> TTL = minimum_soa_ttl(Soa), gen_nsec3(RRs, ZoneName, HashAlg, Salt, Iter, TTL, Class, Opts) end end. -spec gen_nsec3( [dns:rr()], dns:dname(), nsec3_hashalg(), nsec3_salt(), nsec3_iterations(), dns:ttl(), dns:class(), gen_nsec3_opts() ) -> [dns:rr()]. gen_nsec3(RRs, ZoneName, Alg, Salt, Iterations, TTL, Class, Opts) -> BaseTypes = maps:get(base_types, Opts, [?DNS_TYPE_RRSIG]), Map = build_rrmap(RRs, BaseTypes, ZoneName), Unsorted = maps:fold( fun ({Name, SClass}, Types, Acc) when SClass =:= Class -> DName = dns_domain:to_wire(Name), HashedName = ih(Alg, Salt, DName, Iterations), HexdHashName = base32:encode(HashedName, [hex, nopad]), NewName = <>, Data = #dns_rrdata_nsec3{ hash_alg = Alg, opt_out = false, iterations = Iterations, salt = Salt, hash = HashedName, types = Types }, NewRR = #dns_rr{ name = NewName, class = Class, type = ?DNS_TYPE_NSEC3, ttl = TTL, data = Data }, [{HashedName, NewRR} | Acc]; (_, _, Acc) -> Acc end, [], Map ), Sorted = [RR || {_, RR} <- lists:keysort(1, Unsorted)], add_next_hash(Sorted). -spec minimum_soa_ttl(dns:rr()) -> dns:ttl(). minimum_soa_ttl(#dns_rr{type = ?DNS_TYPE_SOA, ttl = Rec, data = #dns_rrdata_soa{minimum = Min}}) -> erlang:min(Min, Rec). -doc "NSEC3 iterative hash function.". -spec ih(nsec3_hashalg() | nsec3_hashalg_fun(), nsec3_salt(), binary(), nsec3_iterations()) -> binary(). ih(?DNSSEC_NSEC3_ALG_SHA1, Salt, X, I) when is_binary(Salt), is_binary(X), is_integer(I), 0 =< I -> ih_nsec3(Salt, X, I); ih(H, Salt, X, I) when is_function(H, 1), is_binary(Salt), is_binary(X), is_integer(I), 0 =< I -> ih_nsec3_custom(H, Salt, X, I). %% Optimise for the common case -spec ih_nsec3(nsec3_salt(), binary(), nsec3_iterations()) -> binary(). ih_nsec3(Salt, X, 0) -> crypto:hash(sha, [X, Salt]); ih_nsec3(Salt, X, I) -> ih_nsec3(Salt, crypto:hash(sha, [X, Salt]), I - 1). -spec ih_nsec3_custom(fun((iodata()) -> binary()), nsec3_salt(), binary(), nsec3_iterations()) -> binary(). ih_nsec3_custom(H, Salt, X, 0) -> H([X, Salt]); ih_nsec3_custom(H, Salt, X, I) -> ih_nsec3_custom(H, Salt, H([X, Salt]), I - 1). -spec add_next_hash([dns:rr(), ...]) -> [dns:rr(), ...]. add_next_hash([#dns_rr{data = #dns_rrdata_nsec3{hash = First}} | _] = Hashes) -> add_next_hash(Hashes, [], First). -spec add_next_hash([dns:rr(), ...], [dns:rr()], _) -> [dns:rr(), ...]. add_next_hash([#dns_rr{data = Data} = RR], RRs, FirstHash) -> NewRR = RR#dns_rr{data = Data#dns_rrdata_nsec3{hash = FirstHash}}, lists:reverse([NewRR | RRs]); add_next_hash( [ #dns_rr{data = Data} = RR | [#dns_rr{data = #dns_rrdata_nsec3{hash = NextHash}} | _] = Hashes ], RRs, FirstHash ) -> NewRR = RR#dns_rr{data = Data#dns_rrdata_nsec3{hash = NextHash}}, add_next_hash(Hashes, [NewRR | RRs], FirstHash). -spec normalise_rr(dns:rr()) -> dns:rr(). normalise_rr(#dns_rr{name = Name} = RR) -> RR#dns_rr{name = dns_domain:to_lower(Name)}. -type rrtype_map() :: #{{dns:dname(), dns:class()} => [integer()]}. -spec build_rrmap([dns:rr()], [integer()], dns:dname()) -> rrtype_map(). build_rrmap(RR, BaseTypes, ZoneName) -> Base = build_rrmap_gbt(RR, BaseTypes), build_rrmap_nonterm(ZoneName, maps:keys(Base), Base). -spec build_rrmap_nonterm(dns:dname(), [{dns:dname(), dns:class()} | binary()], rrtype_map()) -> rrtype_map(). build_rrmap_nonterm(_, [], Map) -> Map; build_rrmap_nonterm(ZoneName, [{Name, Class} | Rest], Map) when is_binary(ZoneName) -> NameAncs = name_ancestors(Name, ZoneName), NewMap = add_nonterm_ancestors(Class, NameAncs, Map), build_rrmap_nonterm(ZoneName, Rest, NewMap). -spec add_nonterm_ancestors(dns:class(), [dns:dname()], rrtype_map()) -> rrtype_map(). add_nonterm_ancestors(_, [], Map) -> Map; add_nonterm_ancestors(Class, [Name | Rest], Map) -> Key = {Name, Class}, NewMap = maps:update_with(Key, fun(V) -> V end, [], Map), add_nonterm_ancestors(Class, Rest, NewMap). -spec build_rrmap_gbt([dns:rr()], [dns:type()]) -> rrtype_map(). build_rrmap_gbt(RR, BaseTypes) -> build_rrmap_gbt(RR, BaseTypes, #{}). -spec build_rrmap_gbt([dns:rr()], [dns:type()], rrtype_map()) -> rrtype_map(). build_rrmap_gbt([], _BaseTypes, Map) -> Map; build_rrmap_gbt([#dns_rr{} = RR | Rest], BaseTypes, Map) -> #dns_rr{name = Name, class = Class, type = Type} = normalise_rr(RR), Key = {Name, Class}, NewMap = maps:update_with( Key, fun(Types) -> case lists:member(Type, Types) of true -> Types; false -> [Type | Types] end end, [Type | BaseTypes], Map ), build_rrmap_gbt(Rest, BaseTypes, NewMap). -type tree_key() :: {dns:dname(), dns:class(), dns:type()}. -spec rrs_to_rrsets([dns:rr()]) -> [[dns:rr()]]. rrs_to_rrsets(RR) when is_list(RR) -> rrs_to_rrsets(RR, #{}, #{}). -spec rrs_to_rrsets([dns:rr()], #{tree_key() => dns:ttl()}, #{tree_key() => [dns:rrdata()]}) -> [[dns:rr()]]. rrs_to_rrsets([], TTLMap, RRSets) -> [finalise_rrs_to_rrsets(TTLMap, Key, Datas) || Key := Datas <- RRSets]; rrs_to_rrsets([#dns_rr{} = RR | RRs], TTLMap, RRSets) -> #dns_rr{ name = Name, class = Class, type = Type, ttl = TTL, data = Data } = normalise_rr(RR), Key = {Name, Class, Type}, NewTTLMap = maps:update_with(Key, fun(OldTTL) -> max(OldTTL, TTL) end, TTL, TTLMap), NewRRSets = maps:update_with(Key, fun(OldData) -> [Data | OldData] end, [Data], RRSets), rrs_to_rrsets(RRs, NewTTLMap, NewRRSets). -spec finalise_rrs_to_rrsets(#{tree_key() => dns:ttl()}, tree_key(), [dns:rrdata()]) -> [dns:rr()]. finalise_rrs_to_rrsets(TTLMap, {Name, Class, Type} = Key, Datas) -> TTL = maps:get(Key, TTLMap), [ #dns_rr{ name = Name, class = Class, type = Type, ttl = TTL, data = Data } || Data <- Datas ]. -doc #{equiv => sign_rr(RR, SignerName, KeyTag, Alg, Key, [])}. -spec sign_rr([dns:rr()], dns:dname(), keytag(), sigalg(), key()) -> [dns:rr()]. sign_rr(RR, SignerName, KeyTag, Alg, Key) -> sign_rr(RR, SignerName, KeyTag, Alg, Key, #{}). -doc "Signs a list of `t:dns:rr/0`.". -spec sign_rr([dns:rr()], dns:dname(), keytag(), sigalg(), key(), sign_rr_opts()) -> [dns:rr()]. sign_rr(RR, SignerName, KeyTag, Alg, Key, Opts) when is_map(Opts) -> RRSets = rrs_to_rrsets(RR), [ sign_rrset(RRSet, SignerName, KeyTag, Alg, Key, Opts) || RRSet <- RRSets ]. -doc #{equiv => sign_rrset(RRSet, SignerName, KeyTag, Alg, Key, [])}. -spec sign_rrset([dns:rr(), ...], dns:dname(), keytag(), sigalg(), key()) -> dns:rr(). sign_rrset(RRSet, SignerName, KeyTag, Alg, Key) -> sign_rrset(RRSet, SignerName, KeyTag, Alg, Key, #{}). -doc "Signs a list of `t:dns:rr/0` of the same class and type.". -spec sign_rrset([dns:rr(), ...], dns:dname(), keytag(), sigalg(), key(), sign_rr_opts()) -> dns:rr(). sign_rrset( [#dns_rr{name = Name, class = Class, ttl = TTL} | _] = RRs, SignersName, KeyTag, Alg, Key, Opts ) when is_integer(Alg) -> Now = erlang:system_time(second), Incept = maps:get(inception, Opts, Now), %% 1 year Expire = maps:get(expiration, Opts, Now + ?SECS_IN_YEAR), {Data0, BaseSigInput} = build_sig_input(RRs, SignersName, KeyTag, Alg, Incept, Expire), Signature = sign(Alg, BaseSigInput, Key), Data = Data0#dns_rrdata_rrsig{signature = Signature}, #dns_rr{ name = Name, type = ?DNS_TYPE_RRSIG, class = Class, ttl = TTL, data = Data }. -spec sign(sigalg(), binary(), key()) -> binary(). sign(Alg, BaseSigInput, Key) when Alg =:= ?DNS_ALG_DSA orelse Alg =:= ?DNS_ALG_NSEC3DSA -> %% RFC2536: Key must be [P, Q, G, X] as integers (crypto key format). DerSig = crypto:sign(dss, sha, BaseSigInput, Key), #'Dss-Sig-Value'{r = R, s = S} = public_key:der_decode('Dss-Sig-Value', DerSig), [P, _Q, _G, _Y] = Key, M = byte_size(binary:encode_unsigned(P)), T = (M - 64) div 8, <>; sign(Alg, BaseSigInput, Key) when Alg =:= ?DNS_ALG_NSEC3RSASHA1 orelse Alg =:= ?DNS_ALG_RSASHA1 orelse Alg =:= ?DNS_ALG_RSASHA256 orelse Alg =:= ?DNS_ALG_RSASHA512 -> crypto:sign( rsa, none, BaseSigInput, Key, [{rsa_padding, rsa_pkcs1_padding}] ); sign(?DNS_ALG_ECDSAP256SHA256, BaseSigInput, Key) -> DerSig = crypto:sign(ecdsa, sha256, BaseSigInput, [Key, secp256r1]), %% RFC6605: keys must be r||s of 32 octets each ecdsa_der_to_wire(DerSig, 32); sign(?DNS_ALG_ECDSAP384SHA384, BaseSigInput, Key) -> DerSig = crypto:sign(ecdsa, sha384, BaseSigInput, [Key, secp384r1]), %% RFC6605: keys must be r||s of 48 octets each ecdsa_der_to_wire(DerSig, 48); sign(?DNS_ALG_ED25519, BaseSigInput, Key) -> crypto:sign(eddsa, none, BaseSigInput, [Key, ed25519]); sign(?DNS_ALG_ED448, BaseSigInput, Key) -> crypto:sign(eddsa, none, BaseSigInput, [Key, ed448]). -doc "Provides primitive verification of an RR set.". -spec verify_rrsig(dns:rr(), [dns:rr()], [dns:rr()], verify_rrsig_opts()) -> boolean(). verify_rrsig(#dns_rr{type = ?DNS_TYPE_RRSIG, data = Data}, RRs, RRDNSKey, Opts) -> Now = maps:get(now, Opts, erlang:system_time(second)), #dns_rrdata_rrsig{ original_ttl = OTTL, keytag = SigKeyTag, alg = SigAlg, inception = Incept, expiration = Expire, signers_name = SignersName, signature = Signature } = Data, Keys0 = [ {KeyTag, Alg, PubKey} || #dns_rr{ name = Name, type = ?DNS_TYPE_DNSKEY, data = #dns_rrdata_dnskey{ protocol = 3, alg = Alg, keytag = KeyTag, public_key = PubKey } } <- RRDNSKey, Alg =:= SigAlg, dns_domain:are_equal(Name, SignersName) ], Keys = case lists:keytake(SigKeyTag, 1, Keys0) of false -> Keys0; {value, Match, RemKeys} -> [Match | RemKeys] end, case Now of Now when Now < Incept; Expire < Now -> false; Now -> {_SigTuple, SigInput} = build_sig_input( RRs, SignersName, SigKeyTag, SigAlg, Incept, Expire, OTTL ), lists:any(fun({_KeyTag, Alg, Key}) -> verify(Alg, Key, Signature, SigInput) end, Keys) end. -spec verify(sigalg(), key(), binary(), binary()) -> boolean(). verify(Alg, Key, Signature, SigInput) when Alg =:= ?DNS_ALG_DSA orelse Alg =:= ?DNS_ALG_NSEC3DSA -> <<_T, R:20/unit:8, S:20/unit:8>> = Signature, AsnSig = public_key:der_encode('Dss-Sig-Value', #'Dss-Sig-Value'{r = R, s = S}), %% DNSKEY public_key from decode is [P, Q, G, Y] as binaries; normalize to integers for crypto. KeyInts = dsa_pubkey_to_integers(Key), crypto:verify(dss, sha, SigInput, AsnSig, KeyInts); verify(Alg, Key, Signature, SigInput) when Alg =:= ?DNS_ALG_NSEC3RSASHA1 orelse Alg =:= ?DNS_ALG_RSASHA1 orelse Alg =:= ?DNS_ALG_RSASHA256 orelse Alg =:= ?DNS_ALG_RSASHA512 -> try crypto:verify( rsa, none, SigInput, Signature, Key, [{rsa_padding, rsa_pkcs1_padding}] ) catch error:decrypt_failed -> false end; verify(?DNS_ALG_ECDSAP256SHA256, Key, Signature, SigInput) -> DerSig = ecdsa_wire_to_der(Signature, 32), crypto:verify(ecdsa, sha256, SigInput, DerSig, [<<4, Key/binary>>, secp256r1]); verify(?DNS_ALG_ECDSAP384SHA384, Key, Signature, SigInput) -> DerSig = ecdsa_wire_to_der(Signature, 48), crypto:verify(ecdsa, sha384, SigInput, DerSig, [<<4, Key/binary>>, secp384r1]); verify(?DNS_ALG_ED25519, Key, Signature, SigInput) -> crypto:verify(eddsa, none, SigInput, Signature, [Key, ed25519]); verify(?DNS_ALG_ED448, Key, Signature, SigInput) -> crypto:verify(eddsa, none, SigInput, Signature, [Key, ed448]). -spec build_sig_input([dns:rr(), ...], dns:dname(), keytag(), dns:alg(), integer(), integer()) -> {dns:rrdata_rrsig(), binary()}. build_sig_input( [#dns_rr{ttl = TTL} | _] = RRs, SignersName, KeyTag, Alg, Incept, Expire ) -> build_sig_input(RRs, SignersName, KeyTag, Alg, Incept, Expire, TTL). -spec build_sig_input( [dns:rr(), ...], dns:dname(), keytag(), dns:alg(), integer(), integer(), non_neg_integer() ) -> {dns:rrdata_rrsig(), binary()}. build_sig_input( [#dns_rr{name = Name, class = Class, type = Type, ttl = TTL} | _] = RRs, SignersName, KeyTag, Alg, Incept, Expire, TTL ) when is_integer(Alg) -> LName = dns_domain:to_lower(Name), Datas = lists:sort([canonical_rrdata_bin(RR) || RR <- RRs]), LLabels = dns_domain:split(LName), NameBin = dns_domain:to_wire(LName), RecordBase = <>, RRSetBin = [<> || Data <- Datas], RRSigData = #dns_rrdata_rrsig{ type_covered = Type, alg = Alg, labels = count_labels(LLabels), original_ttl = TTL, inception = Incept, expiration = Expire, keytag = KeyTag, signers_name = SignersName }, RRSigRDataBin = rrsig_to_digestable(RRSigData), SigInput0 = [RRSigRDataBin | RRSetBin], SigInput = preprocess_sig_input(Alg, SigInput0), {RRSigData, SigInput}. -spec preprocess_sig_input(sigalg(), [binary()]) -> binary(). preprocess_sig_input(Alg, SigInput) when Alg =:= ?DNS_ALG_DSA orelse Alg =:= ?DNS_ALG_NSEC3DSA -> NewSigInput = iolist_to_binary(SigInput), NewSigInputSize = byte_size(NewSigInput), <>; preprocess_sig_input(Alg, SigInput) when Alg =:= ?DNS_ALG_NSEC3RSASHA1 orelse Alg =:= ?DNS_ALG_RSASHA1 orelse Alg =:= ?DNS_ALG_RSASHA256 orelse Alg =:= ?DNS_ALG_RSASHA512 -> {Prefix, HashType} = choose_sha_prefix_and_type(Alg), Hash = crypto:hash(HashType, SigInput), <>; preprocess_sig_input(?DNS_ALG_ECDSAP256SHA256, SigInput) -> crypto:hash(sha256, SigInput); preprocess_sig_input(?DNS_ALG_ECDSAP384SHA384, SigInput) -> crypto:hash(sha384, SigInput); preprocess_sig_input(Alg, SigInput) when Alg =:= ?DNS_ALG_ED25519 orelse Alg =:= ?DNS_ALG_ED448 -> iolist_to_binary(SigInput). -spec choose_sha_prefix_and_type(sigalg()) -> {binary(), sha | sha256 | sha512}. choose_sha_prefix_and_type(?DNS_ALG_RSASHA1) -> {?RSASHA1_PREFIX, sha}; choose_sha_prefix_and_type(?DNS_ALG_NSEC3RSASHA1) -> {?RSASHA1_PREFIX, sha}; choose_sha_prefix_and_type(?DNS_ALG_RSASHA256) -> {?RSASHA256_PREFIX, sha256}; choose_sha_prefix_and_type(?DNS_ALG_RSASHA512) -> {?RSASHA512_PREFIX, sha512}. -doc "Generates and appends a DNS Key records key tag.". -spec add_keytag_to_dnskey(dns:rr()) -> dns:rr(). add_keytag_to_dnskey( #dns_rr{type = ?DNS_TYPE_DNSKEY, data = #dns_rrdata_dnskey{} = Data} = RR ) -> add_keytag_to_key_rr(RR, Data, ?DNS_TYPE_DNSKEY). -doc "Generates and appends a CDNSKEY records key tag.". -spec add_keytag_to_cdnskey(dns:rr()) -> dns:rr(). add_keytag_to_cdnskey( #dns_rr{type = ?DNS_TYPE_CDNSKEY, data = #dns_rrdata_cdnskey{} = Data} = RR ) -> add_keytag_to_key_rr(RR, Data, ?DNS_TYPE_CDNSKEY). -spec add_keytag_to_key_rr(dns:rr(), dns:rrdata(), dns:type()) -> dns:rr(). add_keytag_to_key_rr(RR, Data, Type) -> KeyBin = dns_encode:encode_rrdata(?DNS_CLASS_IN, Data), NewData = dns_decode:decode_rrdata(KeyBin, ?DNS_CLASS_IN, Type), RR#dns_rr{data = NewData}. -spec rrsig_to_digestable(dns:rrdata_rrsig()) -> binary(). rrsig_to_digestable(#dns_rrdata_rrsig{} = Data) -> dns_encode:encode_rrdata(?DNS_CLASS_IN, Data#dns_rrdata_rrsig{signature = <<>>}). -spec canonical_rrdata_bin(dns:rr()) -> binary(). canonical_rrdata_bin(#dns_rr{class = Class, data = Data0}) -> dns_encode:encode_rrdata(Class, canonical_rrdata_form(Data0)). -doc "Converts a resource record data record to DNSSEC canonical form.". -spec canonical_rrdata_form(dns:rrdata()) -> dns:rrdata(). canonical_rrdata_form(#dns_rrdata_afsdb{hostname = Hostname} = Data) -> Data#dns_rrdata_afsdb{hostname = dns_domain:to_lower(Hostname)}; canonical_rrdata_form(#dns_rrdata_cname{dname = DName} = Data) -> Data#dns_rrdata_cname{dname = dns_domain:to_lower(DName)}; canonical_rrdata_form(#dns_rrdata_dname{dname = DName} = Data) -> Data#dns_rrdata_dname{dname = dns_domain:to_lower(DName)}; canonical_rrdata_form(#dns_rrdata_dsync{target = Target} = Data) -> Data#dns_rrdata_dsync{target = dns_domain:to_lower(Target)}; canonical_rrdata_form(#dns_rrdata_ipseckey{gateway = Gateway} = Data) when is_binary(Gateway) -> Data#dns_rrdata_ipseckey{gateway = dns_domain:to_lower(Gateway)}; canonical_rrdata_form(#dns_rrdata_kx{exchange = Exchange} = Data) -> Data#dns_rrdata_kx{exchange = dns_domain:to_lower(Exchange)}; canonical_rrdata_form(#dns_rrdata_mb{madname = MaDname} = Data) -> Data#dns_rrdata_mb{madname = dns_domain:to_lower(MaDname)}; canonical_rrdata_form(#dns_rrdata_mg{madname = MaDname} = Data) -> Data#dns_rrdata_mg{madname = dns_domain:to_lower(MaDname)}; canonical_rrdata_form(#dns_rrdata_minfo{rmailbx = RmailBx, emailbx = EmailBx} = Data) -> Data#dns_rrdata_minfo{ rmailbx = dns_domain:to_lower(RmailBx), emailbx = dns_domain:to_lower(EmailBx) }; canonical_rrdata_form(#dns_rrdata_mr{newname = NewName} = Data) -> Data#dns_rrdata_mr{newname = dns_domain:to_lower(NewName)}; canonical_rrdata_form(#dns_rrdata_mx{exchange = Exchange} = Data) -> Data#dns_rrdata_mx{exchange = dns_domain:to_lower(Exchange)}; canonical_rrdata_form(#dns_rrdata_naptr{replacement = Replacement} = Data) -> Data#dns_rrdata_naptr{replacement = dns_domain:to_lower(Replacement)}; canonical_rrdata_form(#dns_rrdata_ns{dname = DName} = Data) -> Data#dns_rrdata_ns{dname = dns_domain:to_lower(DName)}; canonical_rrdata_form(#dns_rrdata_nsec{next_dname = NextDname} = Data) -> Data#dns_rrdata_nsec{next_dname = dns_domain:to_lower(NextDname)}; canonical_rrdata_form(#dns_rrdata_nxt{dname = DName} = Data) -> Data#dns_rrdata_nxt{dname = dns_domain:to_lower(DName)}; canonical_rrdata_form(#dns_rrdata_ptr{dname = DName} = Data) -> Data#dns_rrdata_ptr{dname = dns_domain:to_lower(DName)}; canonical_rrdata_form(#dns_rrdata_rp{mbox = Mbox, txt = Txt} = Data) -> Data#dns_rrdata_rp{ mbox = dns_domain:to_lower(Mbox), txt = dns_domain:to_lower(Txt) }; canonical_rrdata_form(#dns_rrdata_rrsig{signers_name = SignersName} = Data) -> Data#dns_rrdata_rrsig{signers_name = dns_domain:to_lower(SignersName)}; canonical_rrdata_form(#dns_rrdata_rt{host = Host} = Data) -> Data#dns_rrdata_rt{host = dns_domain:to_lower(Host)}; canonical_rrdata_form(#dns_rrdata_soa{mname = Mname, rname = Rname} = Data) -> Data#dns_rrdata_soa{ mname = dns_domain:to_lower(Mname), rname = dns_domain:to_lower(Rname) }; canonical_rrdata_form(#dns_rrdata_srv{target = Target} = Data) -> Data#dns_rrdata_srv{target = dns_domain:to_lower(Target)}; canonical_rrdata_form(#dns_rrdata_svcb{target_name = TargetName} = Data) -> Data#dns_rrdata_svcb{target_name = dns_domain:to_lower(TargetName)}; canonical_rrdata_form(#dns_rrdata_https{target_name = TargetName} = Data) -> Data#dns_rrdata_https{target_name = dns_domain:to_lower(TargetName)}; canonical_rrdata_form(X) -> X. -spec name_ancestors(dns:dname(), dns:dname()) -> [binary()]. name_ancestors(Name, ZoneName) -> NameLwr = dns_domain:to_lower(Name), ZoneNameLwr = dns_domain:to_lower(ZoneName), gen_name_ancestors(NameLwr, ZoneNameLwr). -spec gen_name_ancestors(binary(), binary()) -> [binary()]. gen_name_ancestors(ZoneName, ZoneName) -> []; gen_name_ancestors(Name, ZoneName) when byte_size(Name) > byte_size(ZoneName) + 1 -> Offset = byte_size(Name) - byte_size(ZoneName) - 1, case Name of <> -> case dns_domain:split(RelName) of [_] -> []; [_ | Labels0] -> [FirstLabel | Labels] = lists:reverse(Labels0), gen_name_ancestors_labels(Labels, [<>]) end; _ -> erlang:error(name_mismatch) end. gen_name_ancestors_labels([], Anc) -> Anc; gen_name_ancestors_labels([Label | Labels], [Parent | _] = Asc) -> NewName = <