// Copyright (c) 1999-2010 Brian Wellington (bwelling@xbill.org) package org.xbill.DNS; import java.io.*; import java.math.*; import java.security.*; import java.security.interfaces.*; import java.security.spec.*; import java.util.*; /** * Constants and methods relating to DNSSEC. * * DNSSEC provides authentication for DNS information. * @see RRSIGRecord * @see DNSKEYRecord * @see RRset * * @author Brian Wellington */ public class DNSSEC { public static class Algorithm { private Algorithm() {} /** RSA/MD5 public key (deprecated) */ public static final int RSAMD5 = 1; /** Diffie Hellman key */ public static final int DH = 2; /** DSA public key */ public static final int DSA = 3; /** RSA/SHA1 public key */ public static final int RSASHA1 = 5; /** DSA/SHA1, NSEC3-aware public key */ public static final int DSA_NSEC3_SHA1 = 6; /** RSA/SHA1, NSEC3-aware public key */ public static final int RSA_NSEC3_SHA1 = 7; /** RSA/SHA256 public key */ public static final int RSASHA256 = 8; /** RSA/SHA512 public key */ public static final int RSASHA512 = 10; /** ECDSA Curve P-256 with SHA-256 public key **/ public static final int ECDSAP256SHA256 = 13; /** ECDSA Curve P-384 with SHA-384 public key **/ public static final int ECDSAP384SHA384 = 14; /** Indirect keys; the actual key is elsewhere. */ public static final int INDIRECT = 252; /** Private algorithm, specified by domain name */ public static final int PRIVATEDNS = 253; /** Private algorithm, specified by OID */ public static final int PRIVATEOID = 254; private static Mnemonic algs = new Mnemonic("DNSSEC algorithm", Mnemonic.CASE_UPPER); static { algs.setMaximum(0xFF); algs.setNumericAllowed(true); algs.add(RSAMD5, "RSAMD5"); algs.add(DH, "DH"); algs.add(DSA, "DSA"); algs.add(RSASHA1, "RSASHA1"); algs.add(DSA_NSEC3_SHA1, "DSA-NSEC3-SHA1"); algs.add(RSA_NSEC3_SHA1, "RSA-NSEC3-SHA1"); algs.add(RSASHA256, "RSASHA256"); algs.add(RSASHA512, "RSASHA512"); algs.add(ECDSAP256SHA256, "ECDSAP256SHA256"); algs.add(ECDSAP384SHA384, "ECDSAP384SHA384"); algs.add(INDIRECT, "INDIRECT"); algs.add(PRIVATEDNS, "PRIVATEDNS"); algs.add(PRIVATEOID, "PRIVATEOID"); } /** * Converts an algorithm into its textual representation */ public static String string(int alg) { return algs.getText(alg); } /** * Converts a textual representation of an algorithm into its numeric * code. Integers in the range 0..255 are also accepted. * @param s The textual representation of the algorithm * @return The algorithm code, or -1 on error. */ public static int value(String s) { return algs.getValue(s); } } private DNSSEC() { } private static void digestSIG(DNSOutput out, SIGBase sig) { out.writeU16(sig.getTypeCovered()); out.writeU8(sig.getAlgorithm()); out.writeU8(sig.getLabels()); out.writeU32(sig.getOrigTTL()); out.writeU32(sig.getExpire().getTime() / 1000); out.writeU32(sig.getTimeSigned().getTime() / 1000); out.writeU16(sig.getFootprint()); sig.getSigner().toWireCanonical(out); } /** * Creates a byte array containing the concatenation of the fields of the * SIG record and the RRsets to be signed/verified. This does not perform * a cryptographic digest. * @param rrsig The RRSIG record used to sign/verify the rrset. * @param rrset The data to be signed/verified. * @return The data to be cryptographically signed or verified. */ public static byte [] digestRRset(RRSIGRecord rrsig, RRset rrset) { DNSOutput out = new DNSOutput(); digestSIG(out, rrsig); int size = rrset.size(); Record [] records = new Record[size]; Iterator it = rrset.rrs(); Name name = rrset.getName(); Name wild = null; int sigLabels = rrsig.getLabels() + 1; // Add the root label back. if (name.labels() > sigLabels) wild = name.wild(name.labels() - sigLabels); while (it.hasNext()) records[--size] = (Record) it.next(); Arrays.sort(records); DNSOutput header = new DNSOutput(); if (wild != null) wild.toWireCanonical(header); else name.toWireCanonical(header); header.writeU16(rrset.getType()); header.writeU16(rrset.getDClass()); header.writeU32(rrsig.getOrigTTL()); for (int i = 0; i < records.length; i++) { out.writeByteArray(header.toByteArray()); int lengthPosition = out.current(); out.writeU16(0); out.writeByteArray(records[i].rdataToWireCanonical()); int rrlength = out.current() - lengthPosition - 2; out.save(); out.jump(lengthPosition); out.writeU16(rrlength); out.restore(); } return out.toByteArray(); } /** * Creates a byte array containing the concatenation of the fields of the * SIG(0) record and the message to be signed. This does not perform * a cryptographic digest. * @param sig The SIG record used to sign the rrset. * @param msg The message to be signed. * @param previous If this is a response, the signature from the query. * @return The data to be cryptographically signed. */ public static byte [] digestMessage(SIGRecord sig, Message msg, byte [] previous) { DNSOutput out = new DNSOutput(); digestSIG(out, sig); if (previous != null) out.writeByteArray(previous); msg.toWire(out); return out.toByteArray(); } /** * A DNSSEC exception. */ public static class DNSSECException extends Exception { DNSSECException(String s) { super(s); } } /** * An algorithm is unsupported by this DNSSEC implementation. */ public static class UnsupportedAlgorithmException extends DNSSECException { UnsupportedAlgorithmException(int alg) { super("Unsupported algorithm: " + alg); } } /** * The cryptographic data in a DNSSEC key is malformed. */ public static class MalformedKeyException extends DNSSECException { MalformedKeyException(KEYBase rec) { super("Invalid key data: " + rec.rdataToString()); } } /** * A DNSSEC verification failed because fields in the DNSKEY and RRSIG records * do not match. */ public static class KeyMismatchException extends DNSSECException { private KEYBase key; private SIGBase sig; KeyMismatchException(KEYBase key, SIGBase sig) { super("key " + key.getName() + "/" + DNSSEC.Algorithm.string(key.getAlgorithm()) + "/" + key.getFootprint() + " " + "does not match signature " + sig.getSigner() + "/" + DNSSEC.Algorithm.string(sig.getAlgorithm()) + "/" + sig.getFootprint()); } } /** * A DNSSEC verification failed because the signature has expired. */ public static class SignatureExpiredException extends DNSSECException { private Date when, now; SignatureExpiredException(Date when, Date now) { super("signature expired"); this.when = when; this.now = now; } /** * @return When the signature expired */ public Date getExpiration() { return when; } /** * @return When the verification was attempted */ public Date getVerifyTime() { return now; } } /** * A DNSSEC verification failed because the signature has not yet become valid. */ public static class SignatureNotYetValidException extends DNSSECException { private Date when, now; SignatureNotYetValidException(Date when, Date now) { super("signature is not yet valid"); this.when = when; this.now = now; } /** * @return When the signature will become valid */ public Date getExpiration() { return when; } /** * @return When the verification was attempted */ public Date getVerifyTime() { return now; } } /** * A DNSSEC verification failed because the cryptographic signature * verification failed. */ public static class SignatureVerificationException extends DNSSECException { SignatureVerificationException() { super("signature verification failed"); } } /** * The key data provided is inconsistent. */ public static class IncompatibleKeyException extends IllegalArgumentException { IncompatibleKeyException() { super("incompatible keys"); } } private static int BigIntegerLength(BigInteger i) { return (i.bitLength() + 7) / 8; } private static BigInteger readBigInteger(DNSInput in, int len) throws IOException { byte [] b = in.readByteArray(len); return new BigInteger(1, b); } private static BigInteger readBigInteger(DNSInput in) { byte [] b = in.readByteArray(); return new BigInteger(1, b); } private static void writeBigInteger(DNSOutput out, BigInteger val) { byte [] b = val.toByteArray(); if (b[0] == 0) out.writeByteArray(b, 1, b.length - 1); else out.writeByteArray(b); } private static PublicKey toRSAPublicKey(KEYBase r) throws IOException, GeneralSecurityException { DNSInput in = new DNSInput(r.getKey()); int exponentLength = in.readU8(); if (exponentLength == 0) exponentLength = in.readU16(); BigInteger exponent = readBigInteger(in, exponentLength); BigInteger modulus = readBigInteger(in); KeyFactory factory = KeyFactory.getInstance("RSA"); return factory.generatePublic(new RSAPublicKeySpec(modulus, exponent)); } private static PublicKey toDSAPublicKey(KEYBase r) throws IOException, GeneralSecurityException, MalformedKeyException { DNSInput in = new DNSInput(r.getKey()); int t = in.readU8(); if (t > 8) throw new MalformedKeyException(r); BigInteger q = readBigInteger(in, 20); BigInteger p = readBigInteger(in, 64 + t*8); BigInteger g = readBigInteger(in, 64 + t*8); BigInteger y = readBigInteger(in, 64 + t*8); KeyFactory factory = KeyFactory.getInstance("DSA"); return factory.generatePublic(new DSAPublicKeySpec(y, p, q, g)); } private static class ECKeyInfo { int length; public BigInteger p, a, b, gx, gy, n; EllipticCurve curve; ECParameterSpec spec; ECKeyInfo(int length, String p_str, String a_str, String b_str, String gx_str, String gy_str, String n_str) { this.length = length; p = new BigInteger(p_str, 16); a = new BigInteger(a_str, 16); b = new BigInteger(b_str, 16); gx = new BigInteger(gx_str, 16); gy = new BigInteger(gy_str, 16); n = new BigInteger(n_str, 16); curve = new EllipticCurve(new ECFieldFp(p), a, b); spec = new ECParameterSpec(curve, new ECPoint(gx, gy), n, 1); } } // RFC 5114 Section 2.6 private static final ECKeyInfo ECDSA_P256 = new ECKeyInfo(32, "FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", "FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC", "5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B", "6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296", "4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5", "FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551"); // RFC 5114 Section 2.7 private static final ECKeyInfo ECDSA_P384 = new ECKeyInfo(48, "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF", "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC", "B3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF", "AA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7", "3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F", "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973"); private static PublicKey toECDSAPublicKey(KEYBase r, ECKeyInfo keyinfo) throws IOException, GeneralSecurityException, MalformedKeyException { DNSInput in = new DNSInput(r.getKey()); // RFC 6605 Section 4 BigInteger x = readBigInteger(in, keyinfo.length); BigInteger y = readBigInteger(in, keyinfo.length); ECPoint q = new ECPoint(x, y); KeyFactory factory = KeyFactory.getInstance("EC"); return factory.generatePublic(new ECPublicKeySpec(q, keyinfo.spec)); } /** Converts a KEY/DNSKEY record into a PublicKey */ static PublicKey toPublicKey(KEYBase r) throws DNSSECException { int alg = r.getAlgorithm(); try { switch (alg) { case Algorithm.RSAMD5: case Algorithm.RSASHA1: case Algorithm.RSA_NSEC3_SHA1: case Algorithm.RSASHA256: case Algorithm.RSASHA512: return toRSAPublicKey(r); case Algorithm.DSA: case Algorithm.DSA_NSEC3_SHA1: return toDSAPublicKey(r); case Algorithm.ECDSAP256SHA256: return toECDSAPublicKey(r, ECDSA_P256); case Algorithm.ECDSAP384SHA384: return toECDSAPublicKey(r, ECDSA_P384); default: throw new UnsupportedAlgorithmException(alg); } } catch (IOException e) { throw new MalformedKeyException(r); } catch (GeneralSecurityException e) { throw new DNSSECException(e.toString()); } } private static byte [] fromRSAPublicKey(RSAPublicKey key) { DNSOutput out = new DNSOutput(); BigInteger exponent = key.getPublicExponent(); BigInteger modulus = key.getModulus(); int exponentLength = BigIntegerLength(exponent); if (exponentLength < 256) out.writeU8(exponentLength); else { out.writeU8(0); out.writeU16(exponentLength); } writeBigInteger(out, exponent); writeBigInteger(out, modulus); return out.toByteArray(); } private static byte [] fromDSAPublicKey(DSAPublicKey key) { DNSOutput out = new DNSOutput(); BigInteger q = key.getParams().getQ(); BigInteger p = key.getParams().getP(); BigInteger g = key.getParams().getG(); BigInteger y = key.getY(); int t = (p.toByteArray().length - 64) / 8; out.writeU8(t); writeBigInteger(out, q); writeBigInteger(out, p); writeBigInteger(out, g); writeBigInteger(out, y); return out.toByteArray(); } private static byte [] fromECDSAPublicKey(ECPublicKey key) { DNSOutput out = new DNSOutput(); BigInteger x = key.getW().getAffineX(); BigInteger y = key.getW().getAffineY(); writeBigInteger(out, x); writeBigInteger(out, y); return out.toByteArray(); } /** Builds a DNSKEY record from a PublicKey */ static byte [] fromPublicKey(PublicKey key, int alg) throws DNSSECException { switch (alg) { case Algorithm.RSAMD5: case Algorithm.RSASHA1: case Algorithm.RSA_NSEC3_SHA1: case Algorithm.RSASHA256: case Algorithm.RSASHA512: if (! (key instanceof RSAPublicKey)) throw new IncompatibleKeyException(); return fromRSAPublicKey((RSAPublicKey) key); case Algorithm.DSA: case Algorithm.DSA_NSEC3_SHA1: if (! (key instanceof DSAPublicKey)) throw new IncompatibleKeyException(); return fromDSAPublicKey((DSAPublicKey) key); case Algorithm.ECDSAP256SHA256: case Algorithm.ECDSAP384SHA384: if (! (key instanceof ECPublicKey)) throw new IncompatibleKeyException(); return fromECDSAPublicKey((ECPublicKey) key); default: throw new UnsupportedAlgorithmException(alg); } } /** * Convert an algorithm number to the corresponding JCA string. * @param alg The algorithm number. * @throws UnsupportedAlgorithmException The algorithm is unknown. */ public static String algString(int alg) throws UnsupportedAlgorithmException { switch (alg) { case Algorithm.RSAMD5: return "MD5withRSA"; case Algorithm.DSA: case Algorithm.DSA_NSEC3_SHA1: return "SHA1withDSA"; case Algorithm.RSASHA1: case Algorithm.RSA_NSEC3_SHA1: return "SHA1withRSA"; case Algorithm.RSASHA256: return "SHA256withRSA"; case Algorithm.RSASHA512: return "SHA512withRSA"; case Algorithm.ECDSAP256SHA256: return "SHA256withECDSA"; case Algorithm.ECDSAP384SHA384: return "SHA384withECDSA"; default: throw new UnsupportedAlgorithmException(alg); } } private static final int ASN1_SEQ = 0x30; private static final int ASN1_INT = 0x2; private static final int DSA_LEN = 20; private static byte [] DSASignaturefromDNS(byte [] dns) throws DNSSECException, IOException { if (dns.length != 1 + DSA_LEN * 2) throw new SignatureVerificationException(); DNSInput in = new DNSInput(dns); DNSOutput out = new DNSOutput(); int t = in.readU8(); byte [] r = in.readByteArray(DSA_LEN); int rlen = DSA_LEN; if (r[0] < 0) rlen++; byte [] s = in.readByteArray(DSA_LEN); int slen = DSA_LEN; if (s[0] < 0) slen++; out.writeU8(ASN1_SEQ); out.writeU8(rlen + slen + 4); out.writeU8(ASN1_INT); out.writeU8(rlen); if (rlen > DSA_LEN) out.writeU8(0); out.writeByteArray(r); out.writeU8(ASN1_INT); out.writeU8(slen); if (slen > DSA_LEN) out.writeU8(0); out.writeByteArray(s); return out.toByteArray(); } private static byte [] DSASignaturetoDNS(byte [] signature, int t) throws IOException { DNSInput in = new DNSInput(signature); DNSOutput out = new DNSOutput(); out.writeU8(t); int tmp = in.readU8(); if (tmp != ASN1_SEQ) throw new IOException(); int seqlen = in.readU8(); tmp = in.readU8(); if (tmp != ASN1_INT) throw new IOException(); int rlen = in.readU8(); if (rlen == DSA_LEN + 1) { if (in.readU8() != 0) throw new IOException(); } else if (rlen != DSA_LEN) throw new IOException(); byte [] bytes = in.readByteArray(DSA_LEN); out.writeByteArray(bytes); tmp = in.readU8(); if (tmp != ASN1_INT) throw new IOException(); int slen = in.readU8(); if (slen == DSA_LEN + 1) { if (in.readU8() != 0) throw new IOException(); } else if (slen != DSA_LEN) throw new IOException(); bytes = in.readByteArray(DSA_LEN); out.writeByteArray(bytes); return out.toByteArray(); } private static byte [] ECDSASignaturefromDNS(byte [] signature, ECKeyInfo keyinfo) throws DNSSECException, IOException { if (signature.length != keyinfo.length * 2) throw new SignatureVerificationException(); DNSInput in = new DNSInput(signature); DNSOutput out = new DNSOutput(); byte [] r = in.readByteArray(keyinfo.length); int rlen = keyinfo.length; if (r[0] < 0) rlen++; byte [] s = in.readByteArray(keyinfo.length); int slen = keyinfo.length; if (s[0] < 0) slen++; out.writeU8(ASN1_SEQ); out.writeU8(rlen + slen + 4); out.writeU8(ASN1_INT); out.writeU8(rlen); if (rlen > keyinfo.length) out.writeU8(0); out.writeByteArray(r); out.writeU8(ASN1_INT); out.writeU8(slen); if (slen > keyinfo.length) out.writeU8(0); out.writeByteArray(s); return out.toByteArray(); } private static byte [] ECDSASignaturetoDNS(byte [] signature, ECKeyInfo keyinfo) throws IOException { DNSInput in = new DNSInput(signature); DNSOutput out = new DNSOutput(); int tmp = in.readU8(); if (tmp != ASN1_SEQ) throw new IOException(); int seqlen = in.readU8(); tmp = in.readU8(); if (tmp != ASN1_INT) throw new IOException(); int rlen = in.readU8(); if (rlen == keyinfo.length + 1) { if (in.readU8() != 0) throw new IOException(); } else if (rlen != keyinfo.length) throw new IOException(); byte[] bytes = in.readByteArray(keyinfo.length); out.writeByteArray(bytes); tmp = in.readU8(); if (tmp != ASN1_INT) throw new IOException(); int slen = in.readU8(); if (slen == keyinfo.length + 1) { if (in.readU8() != 0) throw new IOException(); } else if (slen != keyinfo.length) throw new IOException(); bytes = in.readByteArray(keyinfo.length); out.writeByteArray(bytes); return out.toByteArray(); } private static void verify(PublicKey key, int alg, byte [] data, byte [] signature) throws DNSSECException { if (key instanceof DSAPublicKey) { try { signature = DSASignaturefromDNS(signature); } catch (IOException e) { throw new IllegalStateException(); } } else if (key instanceof ECPublicKey) { try { switch (alg) { case Algorithm.ECDSAP256SHA256: signature = ECDSASignaturefromDNS(signature, ECDSA_P256); break; case Algorithm.ECDSAP384SHA384: signature = ECDSASignaturefromDNS(signature, ECDSA_P384); break; default: throw new UnsupportedAlgorithmException(alg); } } catch (IOException e) { throw new IllegalStateException(); } } try { Signature s = Signature.getInstance(algString(alg)); s.initVerify(key); s.update(data); if (!s.verify(signature)) throw new SignatureVerificationException(); } catch (GeneralSecurityException e) { throw new DNSSECException(e.toString()); } } private static boolean matches(SIGBase sig, KEYBase key) { return (key.getAlgorithm() == sig.getAlgorithm() && key.getFootprint() == sig.getFootprint() && key.getName().equals(sig.getSigner())); } /** * Verify a DNSSEC signature. * @param rrset The data to be verified. * @param rrsig The RRSIG record containing the signature. * @param key The DNSKEY record to verify the signature with. * @throws UnsupportedAlgorithmException The algorithm is unknown * @throws MalformedKeyException The key is malformed * @throws KeyMismatchException The key and signature do not match * @throws SignatureExpiredException The signature has expired * @throws SignatureNotYetValidException The signature is not yet valid * @throws SignatureVerificationException The signature does not verify. * @throws DNSSECException Some other error occurred. */ public static void verify(RRset rrset, RRSIGRecord rrsig, DNSKEYRecord key) throws DNSSECException { if (!matches(rrsig, key)) throw new KeyMismatchException(key, rrsig); Date now = new Date(); if (now.compareTo(rrsig.getExpire()) > 0) throw new SignatureExpiredException(rrsig.getExpire(), now); if (now.compareTo(rrsig.getTimeSigned()) < 0) throw new SignatureNotYetValidException(rrsig.getTimeSigned(), now); verify(key.getPublicKey(), rrsig.getAlgorithm(), digestRRset(rrsig, rrset), rrsig.getSignature()); } private static byte [] sign(PrivateKey privkey, PublicKey pubkey, int alg, byte [] data, String provider) throws DNSSECException { byte [] signature; try { Signature s; if (provider != null) s = Signature.getInstance(algString(alg), provider); else s = Signature.getInstance(algString(alg)); s.initSign(privkey); s.update(data); signature = s.sign(); } catch (GeneralSecurityException e) { throw new DNSSECException(e.toString()); } if (pubkey instanceof DSAPublicKey) { try { DSAPublicKey dsa = (DSAPublicKey) pubkey; BigInteger P = dsa.getParams().getP(); int t = (BigIntegerLength(P) - 64) / 8; signature = DSASignaturetoDNS(signature, t); } catch (IOException e) { throw new IllegalStateException(); } } else if (pubkey instanceof ECPublicKey) { try { switch (alg) { case Algorithm.ECDSAP256SHA256: signature = ECDSASignaturetoDNS(signature, ECDSA_P256); break; case Algorithm.ECDSAP384SHA384: signature = ECDSASignaturetoDNS(signature, ECDSA_P384); break; default: throw new UnsupportedAlgorithmException(alg); } } catch (IOException e) { throw new IllegalStateException(); } } return signature; } static void checkAlgorithm(PrivateKey key, int alg) throws UnsupportedAlgorithmException { switch (alg) { case Algorithm.RSAMD5: case Algorithm.RSASHA1: case Algorithm.RSA_NSEC3_SHA1: case Algorithm.RSASHA256: case Algorithm.RSASHA512: if (! (key instanceof RSAPrivateKey)) throw new IncompatibleKeyException(); break; case Algorithm.DSA: case Algorithm.DSA_NSEC3_SHA1: if (! (key instanceof DSAPrivateKey)) throw new IncompatibleKeyException(); break; case Algorithm.ECDSAP256SHA256: case Algorithm.ECDSAP384SHA384: if (! (key instanceof ECPrivateKey)) throw new IncompatibleKeyException(); break; default: throw new UnsupportedAlgorithmException(alg); } } /** * Generate a DNSSEC signature. key and privateKey must refer to the * same underlying cryptographic key. * @param rrset The data to be signed * @param key The DNSKEY record to use as part of signing * @param privkey The PrivateKey to use when signing * @param inception The time at which the signatures should become valid * @param expiration The time at which the signatures should expire * @throws UnsupportedAlgorithmException The algorithm is unknown * @throws MalformedKeyException The key is malformed * @throws DNSSECException Some other error occurred. * @return The generated signature */ public static RRSIGRecord sign(RRset rrset, DNSKEYRecord key, PrivateKey privkey, Date inception, Date expiration) throws DNSSECException { return sign(rrset, key, privkey, inception, expiration, null); } /** * Generate a DNSSEC signature. key and privateKey must refer to the * same underlying cryptographic key. * @param rrset The data to be signed * @param key The DNSKEY record to use as part of signing * @param privkey The PrivateKey to use when signing * @param inception The time at which the signatures should become valid * @param expiration The time at which the signatures should expire * @param provider The name of the JCA provider. If non-null, it will be * passed to JCA getInstance() methods. * @throws UnsupportedAlgorithmException The algorithm is unknown * @throws MalformedKeyException The key is malformed * @throws DNSSECException Some other error occurred. * @return The generated signature */ public static RRSIGRecord sign(RRset rrset, DNSKEYRecord key, PrivateKey privkey, Date inception, Date expiration, String provider) throws DNSSECException { int alg = key.getAlgorithm(); checkAlgorithm(privkey, alg); RRSIGRecord rrsig = new RRSIGRecord(rrset.getName(), rrset.getDClass(), rrset.getTTL(), rrset.getType(), alg, rrset.getTTL(), expiration, inception, key.getFootprint(), key.getName(), null); rrsig.setSignature(sign(privkey, key.getPublicKey(), alg, digestRRset(rrsig, rrset), provider)); return rrsig; } static SIGRecord signMessage(Message message, SIGRecord previous, KEYRecord key, PrivateKey privkey, Date inception, Date expiration) throws DNSSECException { int alg = key.getAlgorithm(); checkAlgorithm(privkey, alg); SIGRecord sig = new SIGRecord(Name.root, DClass.ANY, 0, 0, alg, 0, expiration, inception, key.getFootprint(), key.getName(), null); DNSOutput out = new DNSOutput(); digestSIG(out, sig); if (previous != null) out.writeByteArray(previous.getSignature()); message.toWire(out); sig.setSignature(sign(privkey, key.getPublicKey(), alg, out.toByteArray(), null)); return sig; } static void verifyMessage(Message message, byte [] bytes, SIGRecord sig, SIGRecord previous, KEYRecord key) throws DNSSECException { if (!matches(sig, key)) throw new KeyMismatchException(key, sig); Date now = new Date(); if (now.compareTo(sig.getExpire()) > 0) throw new SignatureExpiredException(sig.getExpire(), now); if (now.compareTo(sig.getTimeSigned()) < 0) throw new SignatureNotYetValidException(sig.getTimeSigned(), now); DNSOutput out = new DNSOutput(); digestSIG(out, sig); if (previous != null) out.writeByteArray(previous.getSignature()); Header header = (Header) message.getHeader().clone(); header.decCount(Section.ADDITIONAL); out.writeByteArray(header.toWire()); out.writeByteArray(bytes, Header.LENGTH, message.sig0start - Header.LENGTH); verify(key.getPublicKey(), sig.getAlgorithm(), out.toByteArray(), sig.getSignature()); } /** * Generate the digest value for a DS key * @param key Which is covered by the DS record * @param digestid The type of digest * @return The digest value as an array of bytes */ static byte [] generateDSDigest(DNSKEYRecord key, int digestid) { MessageDigest digest; try { switch (digestid) { case DSRecord.Digest.SHA1: digest = MessageDigest.getInstance("sha-1"); break; case DSRecord.Digest.SHA256: digest = MessageDigest.getInstance("sha-256"); break; case DSRecord.Digest.SHA384: digest = MessageDigest.getInstance("sha-384"); break; default: throw new IllegalArgumentException( "unknown DS digest type " + digestid); } } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("no message digest support"); } digest.update(key.getName().toWire()); digest.update(key.rdataToWireCanonical()); return digest.digest(); } }