diff --git a/ChangeLog b/ChangeLog index 6b01e7d..8d0909d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2024-04-13 David Blacka + + * Remove support for ECC_GOST + * Create a new DSAlgorithm class, move DS creation into that + * Add support for DS algorithms 3 and 4 -- bouncycastle crypto + provider used for DS algoirthm 3 (GOST R 34.11-94) + * Moved support for loading the bouncycastle provider to the new + DSAlgorithm class + 2024-04-07 David Blacka * Released version 0.20 diff --git a/src/main/java/com/verisignlabs/dnssec/cl/DSTool.java b/src/main/java/com/verisignlabs/dnssec/cl/DSTool.java index 7e5961f..c9af186 100644 --- a/src/main/java/com/verisignlabs/dnssec/cl/DSTool.java +++ b/src/main/java/com/verisignlabs/dnssec/cl/DSTool.java @@ -22,16 +22,14 @@ import java.io.IOException; import java.io.PrintWriter; import org.apache.commons.cli.Option; -import org.xbill.DNS.CDSRecord; -import org.xbill.DNS.DLVRecord; import org.xbill.DNS.DNSKEYRecord; import org.xbill.DNS.DNSSEC; import org.xbill.DNS.DSRecord; import org.xbill.DNS.Record; import com.verisignlabs.dnssec.security.BINDKeyUtils; +import com.verisignlabs.dnssec.security.DSAlgorithm; import com.verisignlabs.dnssec.security.DnsKeyPair; -import com.verisignlabs.dnssec.security.SignUtils; /** * This class forms the command line implementation of a DNSSEC DS/DLV generator @@ -59,7 +57,6 @@ public class DSTool extends CLBase { * state. */ - /** * Set up the command line options. * @@ -68,8 +65,11 @@ public class DSTool extends CLBase { protected void setupOptions() { opts.addOption(Option.builder("D").longOpt("dlv").desc("Generate a DLV record instead.").build()); opts.addOption(Option.builder("C").longOpt("cds").desc("Generate a CDS record instead").build()); + String[] algStrings = DSAlgorithm.getInstance().supportedAlgorithmMnemonics(); + String algStringSet = String.join(" | ", algStrings); opts.addOption( - Option.builder("d").hasArg().argName("id").longOpt("digest").desc("The digest algorithm to use").build()); + Option.builder("d").hasArg().argName("id").longOpt("digest").desc(algStringSet + ": default is SHA256") + .build()); opts.addOption(Option.builder("f").hasArg().argName("file").longOpt("output").desc("output to file").build()); opts.addOption(Option.builder("T").longOpt("ttl").hasArg().desc("TTL to use for generated DS/CDS record").build()); } @@ -99,6 +99,7 @@ public class DSTool extends CLBase { } public void createDS(String keyname) throws IOException { + DSAlgorithm dsAlgorithm = DSAlgorithm.getInstance(); DnsKeyPair key = BINDKeyUtils.loadKey(keyname, null); DNSKEYRecord dnskey = key.getDNSKEYRecord(); @@ -107,21 +108,17 @@ public class DSTool extends CLBase { } long ttl = dsTTL < 0 ? dnskey.getTTL() : dsTTL; - DSRecord ds = SignUtils.calculateDSRecord(dnskey, digestId, ttl); + DSRecord ds = dsAlgorithm.calculateDSRecord(dnskey, digestId, ttl); Record res; switch (createType) { case DLV: log.fine("creating DLV."); - DLVRecord dlv = new DLVRecord(ds.getName(), ds.getDClass(), ds.getTTL(), ds.getFootprint(), ds.getAlgorithm(), - ds.getDigestID(), ds.getDigest()); - res = dlv; + res = dsAlgorithm.dsToDLV(ds); break; case CDS: log.fine("creating CDS."); - CDSRecord cds = new CDSRecord(ds.getName(), ds.getDClass(), ds.getTTL(), ds.getFootprint(), ds.getAlgorithm(), - ds.getDClass(), ds.getDigest()); - res = cds; + res = dsAlgorithm.dstoCDS(ds); break; default: res = ds; @@ -138,14 +135,14 @@ public class DSTool extends CLBase { } public void execute() throws Exception { - for (String keyname : keynames){ + for (String keyname : keynames) { createDS(keyname); } } public static void main(String[] args) { DSTool tool = new DSTool("dstool", "jdnssec-dstool [..options..] keyfile [keyfile..]"); - + tool.run(args); } } diff --git a/src/main/java/com/verisignlabs/dnssec/cl/KeyGen.java b/src/main/java/com/verisignlabs/dnssec/cl/KeyGen.java index 5075f8a..ebf819d 100644 --- a/src/main/java/com/verisignlabs/dnssec/cl/KeyGen.java +++ b/src/main/java/com/verisignlabs/dnssec/cl/KeyGen.java @@ -67,7 +67,7 @@ public class KeyGen extends CLBase { String[] algStrings = DnsKeyAlgorithm.getInstance().supportedAlgMnemonics(); String algStringSet = String.join(" | ", algStrings); opts.addOption(Option.builder("a").hasArg().argName("algorithm") - .desc(algStringSet + " | alias, ECDSAP256SHA256 is default.").build()); + .desc(algStringSet + " | aliases, ECDSAP256SHA256 is default.").build()); opts.addOption(Option.builder("b").hasArg().argName("size").desc( "key size, in bits (default 2048). RSA: [512..4096], DSA: [512..1024], DH: [128..4096], ECDSA: ignored, EdDSA: ignored") diff --git a/src/main/java/com/verisignlabs/dnssec/security/BINDKeyUtils.java b/src/main/java/com/verisignlabs/dnssec/security/BINDKeyUtils.java index 381a525..44e1b7e 100644 --- a/src/main/java/com/verisignlabs/dnssec/security/BINDKeyUtils.java +++ b/src/main/java/com/verisignlabs/dnssec/security/BINDKeyUtils.java @@ -134,8 +134,7 @@ public class BINDKeyUtils { public static DnsKeyPair loadKeyPair(String keyFileBasePath, File inDirectory) throws IOException { keyFileBasePath = fixKeyFileBasePath(keyFileBasePath); - // FIXME: should we throw the IOException when one of the files - // cannot be found, or just when both cannot be found? + File publicKeyFile = new File(inDirectory, keyFileBasePath + ".key"); File privateKeyFile = new File(inDirectory, keyFileBasePath + ".private"); @@ -251,8 +250,6 @@ public class BINDKeyUtils { if (privateKeyString == null) return null; - // FIXME: should this swallow exceptions or actually propagate - // them? try { DnsKeyConverter conv = new DnsKeyConverter(); return conv.parsePrivateKeyString(privateKeyString); diff --git a/src/main/java/com/verisignlabs/dnssec/security/DSAlgorithm.java b/src/main/java/com/verisignlabs/dnssec/security/DSAlgorithm.java new file mode 100644 index 0000000..6ea6609 --- /dev/null +++ b/src/main/java/com/verisignlabs/dnssec/security/DSAlgorithm.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2006, 2022 Verisign. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. The name of the author may not + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +package com.verisignlabs.dnssec.security; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.Security; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.logging.Logger; + +import org.xbill.DNS.CDSRecord; +import org.xbill.DNS.DLVRecord; +import org.xbill.DNS.DNSKEYRecord; +import org.xbill.DNS.DNSOutput; +import org.xbill.DNS.DNSSEC; +import org.xbill.DNS.DSRecord; + +/** + * This class handles the implementation behind converting DNSKEYs into + * DSRecords. It primarily exists to bootstrap whatever crypto libraries we + * might need to do so. + * + * @author David Blacka + */ +public class DSAlgorithm { + + private Logger log = Logger.getLogger(this.getClass().toString()); + + HashSet mSupportedAlgorithms = null; + + private static DSAlgorithm mInstance = null; + + public DSAlgorithm() { + mSupportedAlgorithms = new HashSet<>(); + mSupportedAlgorithms.add(DNSSEC.Digest.SHA1); + mSupportedAlgorithms.add(DNSSEC.Digest.SHA256); + mSupportedAlgorithms.add(DNSSEC.Digest.SHA384); + // Attempt to add the bouncycastle provider. This is so we can use this + // provider if it is available, but not require the user to add it as one of + // the java.security providers. + try { + Class bcProviderClass = Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider"); + Provider bcProvider = (Provider) bcProviderClass.getDeclaredConstructor().newInstance(); + Security.addProvider(bcProvider); + log.fine("bouncycastle crypto provider loaded"); + mSupportedAlgorithms.add(DNSSEC.Digest.GOST3411); + } catch (ReflectiveOperationException e) { + // do nothing, this is the normal case + } + + } + + public String[] supportedAlgorithmMnemonics() { + ArrayList algs = new ArrayList<>(); + + for (int digestId : mSupportedAlgorithms) { + algs.add(DNSSEC.Digest.string(digestId)); + } + + String[] result = new String[algs.size()]; + return algs.toArray(result); + } + + /** + * Given a DNSKEY record, generate the DS record from it. + * + * @param keyrec the KEY record in question. + * @param digestAlg The digest algorithm (SHA-1, SHA-256, etc.). + * @param ttl the desired TTL for the generated DS record. If zero, or + * negative, the original KEY RR's TTL will be used. + * @return the corresponding {@link org.xbill.DNS.DSRecord} + */ + public DSRecord calculateDSRecord(DNSKEYRecord keyrec, int digestAlg, long ttl) { + if (keyrec == null) + return null; + + if (ttl <= 0) + ttl = keyrec.getTTL(); + + DNSOutput os = new DNSOutput(); + + os.writeByteArray(keyrec.getName().toWireCanonical()); + os.writeByteArray(keyrec.rdataToWireCanonical()); + + try { + byte[] digest; + MessageDigest md; + + switch (digestAlg) { + case DNSSEC.Digest.SHA1: + md = MessageDigest.getInstance("SHA"); + digest = md.digest(os.toByteArray()); + break; + case DNSSEC.Digest.SHA256: + md = MessageDigest.getInstance("SHA-256"); + digest = md.digest(os.toByteArray()); + break; + case DNSSEC.Digest.GOST3411: + // The standard Java crypto providers don't have this, but bouncycastle does + if (java.security.Security.getProviders("MessageDigest.GOST3411") != null) { + md = MessageDigest.getInstance("GOST3411"); + digest = md.digest(os.toByteArray()); + } else { + throw new IllegalArgumentException("Unsupported digest id: " + digestAlg); + } + break; + case DNSSEC.Digest.SHA384: + md = MessageDigest.getInstance("SHA-384"); + digest = md.digest(os.toByteArray()); + break; + default: + throw new IllegalArgumentException("Unknown digest id: " + digestAlg); + } + + return new DSRecord(keyrec.getName(), keyrec.getDClass(), ttl, + keyrec.getFootprint(), keyrec.getAlgorithm(), digestAlg, + digest); + + } catch (NoSuchAlgorithmException e) { + log.severe(e.toString()); + return null; + } + } + + public DLVRecord dsToDLV(DSRecord ds) { + return new DLVRecord(ds.getName(), ds.getDClass(), ds.getTTL(), ds.getFootprint(), ds.getAlgorithm(), + ds.getDigestID(), ds.getDigest()); + } + + public CDSRecord dstoCDS(DSRecord ds) { + return new CDSRecord(ds.getName(), ds.getDClass(), ds.getTTL(), ds.getFootprint(), ds.getAlgorithm(), + ds.getDClass(), ds.getDigest()); + } + + public static DSAlgorithm getInstance() { + if (mInstance == null) { + mInstance = new DSAlgorithm(); + } + return mInstance; + } +} \ No newline at end of file diff --git a/src/main/java/com/verisignlabs/dnssec/security/DnsKeyAlgorithm.java b/src/main/java/com/verisignlabs/dnssec/security/DnsKeyAlgorithm.java index 07f9dc8..f18b96d 100644 --- a/src/main/java/com/verisignlabs/dnssec/security/DnsKeyAlgorithm.java +++ b/src/main/java/com/verisignlabs/dnssec/security/DnsKeyAlgorithm.java @@ -27,20 +27,14 @@ package com.verisignlabs.dnssec.security; -import java.math.BigInteger; import java.security.AlgorithmParameters; import java.security.InvalidAlgorithmParameterException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; -import java.security.Provider; -import java.security.Security; import java.security.Signature; -import java.security.spec.ECFieldFp; import java.security.spec.ECGenParameterSpec; import java.security.spec.ECParameterSpec; -import java.security.spec.ECPoint; -import java.security.spec.EllipticCurve; import java.security.spec.InvalidParameterSpecException; import java.security.spec.NamedParameterSpec; import java.security.spec.RSAKeyGenParameterSpec; @@ -73,7 +67,6 @@ public class DnsKeyAlgorithm { RSA, DH, DSA, - ECC_GOST, ECDSA, EDDSA; } @@ -129,12 +122,8 @@ public class DnsKeyAlgorithm { private KeyPairGenerator mRSAKeyGenerator; /** This is a cached key pair generator for DSA keys. */ private KeyPairGenerator mDSAKeyGenerator; - /** This is a cached key pair generator for ECC GOST keys. */ - private KeyPairGenerator mECGOSTKeyGenerator; /** This is a cached key pair generator for ECDSA_P256 keys. */ private KeyPairGenerator mECKeyGenerator; - /** This is a cached key pair generator for EdDSA keys. */ - private KeyPairGenerator mEdKeyGenerator; private Logger log = Logger.getLogger(this.getClass().toString()); @@ -142,17 +131,6 @@ public class DnsKeyAlgorithm { private static DnsKeyAlgorithm mInstance = null; public DnsKeyAlgorithm() { - // Attempt to add the bouncycastle provider. This is so we can use this - // provider if it is available, but not require the user to add it as one of - // the java.security providers. - try { - Class bcProviderClass = Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider"); - Provider bcProvider = (Provider) bcProviderClass.getDeclaredConstructor().newInstance(); - Security.addProvider(bcProvider); - } catch (ReflectiveOperationException e) { - log.fine("Unable to load BC provider"); - } - initialize(); } @@ -189,14 +167,6 @@ public class DnsKeyAlgorithm { addAlgorithm(DNSSEC.Algorithm.RSASHA512, "SHA512withRSA", BaseAlgorithm.RSA); addMnemonic("RSASHA512", DNSSEC.Algorithm.RSASHA512); - // ECC-GOST is not supported by Java 1.8's Sun crypto provider. The - // bouncycastle.org provider, however, does support it. - // GostR3410-2001-CryptoPro-A is the named curve in the BC provider, but we - // will get the parameters directly. - addAlgorithm(DNSSEC.Algorithm.ECC_GOST, "GOST3411withECGOST3410", BaseAlgorithm.ECC_GOST, null); - addMnemonic("ECCGOST", DNSSEC.Algorithm.ECC_GOST); - addMnemonic("ECC-GOST", DNSSEC.Algorithm.ECC_GOST); - addAlgorithm(DNSSEC.Algorithm.ECDSAP256SHA256, "SHA256withECDSA", BaseAlgorithm.ECDSA, "secp256r1"); addMnemonic("ECDSAP256SHA256", DNSSEC.Algorithm.ECDSAP256SHA256); addMnemonic("ECDSA-P256", DNSSEC.Algorithm.ECDSAP256SHA256); @@ -226,9 +196,7 @@ public class DnsKeyAlgorithm { * library (SunEC). */ private void addECDSAAlgorithm(int algorithm, String sigName, String curveName) { - ECParameterSpec ecSpec = ECSpecFromAlgorithm(algorithm); - if (ecSpec == null) - ecSpec = ECSpecFromName(curveName); + ECParameterSpec ecSpec = ECSpecFromName(curveName); if (ecSpec == null) return; @@ -278,10 +246,15 @@ public class DnsKeyAlgorithm { * @param curveName the name of the curve. */ private void addAlgorithm(int algorithm, String sigName, BaseAlgorithm baseType, String curveName) { - if (baseType == BaseAlgorithm.ECDSA) { - addECDSAAlgorithm(algorithm, sigName, curveName); - } else if (baseType == BaseAlgorithm.EDDSA) { - addEdDSAAlgorithm(algorithm, sigName, curveName); + switch (baseType) { + case ECDSA: + addECDSAAlgorithm(algorithm, sigName, curveName); + break; + case EDDSA: + addEdDSAAlgorithm(algorithm, sigName, curveName); + break; + default: + throw new IllegalArgumentException("Non-Ellipic curve algorithm passed."); } } @@ -324,25 +297,6 @@ public class DnsKeyAlgorithm { return mAlgorithmMap.get(alg); } - // For curves where we don't (or can't) get the parameters from a standard - // name, we can construct the parameters here. For now, we only do this for - // the ECC-GOST curve. - private ECParameterSpec ECSpecFromAlgorithm(int algorithm) { - if (algorithm == DNSSEC.Algorithm.ECC_GOST) { - // From RFC 4357 Section 11.4 - BigInteger p = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD97", 16); - BigInteger a = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD94", 16); - BigInteger b = new BigInteger("A6", 16); - BigInteger gx = new BigInteger("1", 16); - BigInteger gy = new BigInteger("8D91E471E0989CDA27DF505A453F2B7635294F2DDF23E3B122ACC99C9E9F1E14", 16); - BigInteger n = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6C611070995AD10045841B09B761B893", 16); - - EllipticCurve curve = new EllipticCurve(new ECFieldFp(p), a, b); - return new ECParameterSpec(curve, new ECPoint(gx, gy), n, 1); - } - return null; - } - // Fetch the curve parameters from a named ECDSA curve. private ECParameterSpec ECSpecFromName(String stdName) { try { @@ -529,22 +483,6 @@ public class DnsKeyAlgorithm { pair = mDSAKeyGenerator.generateKeyPair(); break; } - case ECC_GOST: { - if (mECGOSTKeyGenerator == null) { - mECGOSTKeyGenerator = KeyPairGenerator.getInstance("ECGOST3410"); - } - - ECParameterSpec ecSpec = getEllipticCurveParams(algorithm); - try { - mECGOSTKeyGenerator.initialize(ecSpec); - } catch (InvalidAlgorithmParameterException e) { - // Fold the InvalidAlgorithmParameterException into our existing - // thrown exception. Ugly, but requires less code change. - throw new NoSuchAlgorithmException("invalid key parameter spec"); - } - pair = mECGOSTKeyGenerator.generateKeyPair(); - break; - } case ECDSA: { if (mECKeyGenerator == null) { mECKeyGenerator = KeyPairGenerator.getInstance("EC"); @@ -563,9 +501,9 @@ public class DnsKeyAlgorithm { } case EDDSA: { EdAlgEntry entry = (EdAlgEntry) getEntry(algorithm); - mEdKeyGenerator = KeyPairGenerator.getInstance(entry.curveName); + KeyPairGenerator edKeyGenerator = KeyPairGenerator.getInstance(entry.curveName); - pair = mEdKeyGenerator.generateKeyPair(); + pair = edKeyGenerator.generateKeyPair(); break; } default: diff --git a/src/main/java/com/verisignlabs/dnssec/security/DnsKeyConverter.java b/src/main/java/com/verisignlabs/dnssec/security/DnsKeyConverter.java index dea4af4..c5b376e 100644 --- a/src/main/java/com/verisignlabs/dnssec/security/DnsKeyConverter.java +++ b/src/main/java/com/verisignlabs/dnssec/security/DnsKeyConverter.java @@ -201,8 +201,6 @@ public class DnsKeyConverter { return parsePrivateDSA(lines); case DH: return parsePrivateDH(lines); - case ECC_GOST: - return parsePrivateECDSA(lines, alg); case ECDSA: return parsePrivateECDSA(lines, alg); case EDDSA: diff --git a/src/main/java/com/verisignlabs/dnssec/security/SignUtils.java b/src/main/java/com/verisignlabs/dnssec/security/SignUtils.java index 4801158..9882884 100644 --- a/src/main/java/com/verisignlabs/dnssec/security/SignUtils.java +++ b/src/main/java/com/verisignlabs/dnssec/security/SignUtils.java @@ -1317,6 +1317,8 @@ public class SignUtils { */ public static void generateDSRecords(Name zonename, List records, int digestAlg) { + DSAlgorithm dsAlgorithm = DSAlgorithm.getInstance(); + for (ListIterator i = records.listIterator(); i.hasNext();) { Record r = i.next(); if (r == null) @@ -1328,7 +1330,7 @@ public class SignUtils { // Convert non-zone level KEY records into DS records. if (r.getType() == Type.DNSKEY && !rName.equals(zonename)) { - DSRecord ds = calculateDSRecord((DNSKEYRecord) r, digestAlg, r.getTTL()); + DSRecord ds = dsAlgorithm.calculateDSRecord((DNSKEYRecord) r, digestAlg, r.getTTL()); i.set(ds); } @@ -1376,53 +1378,6 @@ public class SignUtils { } } - /** - * Given a DNSKEY record, generate the DS record from it. - * - * @param keyrec the KEY record in question. - * @param digestAlg The digest algorithm (SHA-1, SHA-256, etc.). - * @param ttl the desired TTL for the generated DS record. If zero, or - * negative, the original KEY RR's TTL will be used. - * @return the corresponding {@link org.xbill.DNS.DSRecord} - */ - public static DSRecord calculateDSRecord(DNSKEYRecord keyrec, int digestAlg, long ttl) { - if (keyrec == null) - return null; - - if (ttl <= 0) - ttl = keyrec.getTTL(); - - DNSOutput os = new DNSOutput(); - - os.writeByteArray(keyrec.getName().toWireCanonical()); - os.writeByteArray(keyrec.rdataToWireCanonical()); - - try { - byte[] digest; - MessageDigest md; - - switch (digestAlg) { - case DNSSEC.Digest.SHA1: - md = MessageDigest.getInstance("SHA"); - digest = md.digest(os.toByteArray()); - break; - case DNSSEC.Digest.SHA256: - md = MessageDigest.getInstance("SHA-256"); - digest = md.digest(os.toByteArray()); - break; - default: - throw new IllegalArgumentException("Unknown digest id: " + digestAlg); - } - - return new DSRecord(keyrec.getName(), keyrec.getDClass(), ttl, - keyrec.getFootprint(), keyrec.getAlgorithm(), digestAlg, - digest); - - } catch (NoSuchAlgorithmException e) { - log.severe(e.toString()); - return null; - } - } /** * Calculate an NSEC3 hash based on a DNS name and NSEC3 hash parameters.