diff options
Diffstat (limited to 'src/org/xbill/DNS/DNSSEC.java')
-rw-r--r-- | src/org/xbill/DNS/DNSSEC.java | 1026 |
1 files changed, 1026 insertions, 0 deletions
diff --git a/src/org/xbill/DNS/DNSSEC.java b/src/org/xbill/DNS/DNSSEC.java new file mode 100644 index 0000000..2707947 --- /dev/null +++ b/src/org/xbill/DNS/DNSSEC.java @@ -0,0 +1,1026 @@ +// 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(); +} + +} |