001/*
002 * Copyright 2015-2018 the original author or authors
003 *
004 * This software is licensed under the Apache License, Version 2.0,
005 * the GNU Lesser General Public License version 2 or later ("LGPL")
006 * and the WTFPL.
007 * You may choose either license to govern your use of this software only
008 * upon the condition that you accept all of the terms of either
009 * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
010 */
011package org.minidns.record;
012
013import org.minidns.record.Record.TYPE;
014import org.minidns.util.Base32;
015
016import java.io.DataInputStream;
017import java.io.DataOutputStream;
018import java.io.IOException;
019import java.math.BigInteger;
020import java.util.HashMap;
021import java.util.Map;
022
023/**
024 * NSEC3 record payload.
025 */
026public class NSEC3 extends Data {
027
028    /**
029     * This Flag indicates whether this NSEC3 RR may cover unsigned
030     * delegations.
031     */
032    public static final byte FLAG_OPT_OUT = 0x1;
033
034    private static final Map<Byte, HashAlgorithm> HASH_ALGORITHM_LUT = new HashMap<>();
035
036    /**
037     * DNSSEC NSEC3 Hash Algorithms.
038     *
039     * @see <a href=
040     *      "https://www.iana.org/assignments/dnssec-nsec3-parameters/dnssec-nsec3-parameters.xhtml#dnssec-nsec3-parameters-3">
041     *      IANA DNSSEC NSEC3 Hash Algorithms</a>
042     */
043    public enum HashAlgorithm {
044        RESERVED(0, "Reserved"),
045        SHA1(1, "SHA-1"),
046        ;
047
048        HashAlgorithm(int value, String description) {
049            if (value < 0 || value > 255) {
050                throw new IllegalArgumentException();
051            }
052            this.value = (byte) value;
053            this.description = description;
054            HASH_ALGORITHM_LUT.put(this.value, this);
055        }
056
057        public final byte value;
058        public final String description;
059
060        public static HashAlgorithm forByte(byte b) {
061            return HASH_ALGORITHM_LUT.get(b);
062        }
063    }
064
065    /**
066     * The cryptographic hash algorithm used. If MiniDNS
067     * isn't aware of the hash algorithm, then this field will be
068     * <code>null</code>.
069     * 
070     * @see #hashAlgorithmByte
071     */
072    public final HashAlgorithm hashAlgorithm;
073
074    /**
075     * The byte value of the cryptographic hash algorithm used.
076     */
077    public final byte hashAlgorithmByte;
078
079    /**
080     * Bitmap of flags: {@link #FLAG_OPT_OUT}.
081     */
082    public final byte flags;
083
084    /**
085     * The number of iterations the hash algorithm is applied.
086     */
087    public final int /* unsigned short */ iterations;
088
089    /**
090     * The salt appended to the next owner name before hashing.
091     */
092    public final byte[] salt;
093
094    /**
095     * The next hashed owner name in hash order.
096     */
097    public final byte[] nextHashed;
098
099    private final byte[] typeBitmap;
100
101    /**
102     * The RR types existing at the original owner name.
103     */
104    public final TYPE[] types;
105
106    public static NSEC3 parse(DataInputStream dis, int length) throws IOException {
107        byte hashAlgorithm = dis.readByte();
108        byte flags = dis.readByte();
109        int iterations = dis.readUnsignedShort();
110        int saltLength = dis.readUnsignedByte();
111        byte[] salt = new byte[saltLength];
112        if (dis.read(salt) != salt.length) throw new IOException();
113        int hashLength = dis.readUnsignedByte();
114        byte[] nextHashed = new byte[hashLength];
115        if (dis.read(nextHashed) != nextHashed.length) throw new IOException();
116        byte[] typeBitmap = new byte[length - (6 + saltLength + hashLength)];
117        if (dis.read(typeBitmap) != typeBitmap.length) throw new IOException();
118        TYPE[] types = NSEC.readTypeBitMap(typeBitmap);
119        return new NSEC3(hashAlgorithm, flags, iterations, salt, nextHashed, types);
120    }
121
122    private NSEC3(HashAlgorithm hashAlgorithm, byte hashAlgorithmByte, byte flags, int iterations, byte[] salt, byte[] nextHashed, TYPE[] types) {
123        assert hashAlgorithmByte == (hashAlgorithm != null ? hashAlgorithm.value : hashAlgorithmByte);
124        this.hashAlgorithmByte = hashAlgorithmByte;
125        this.hashAlgorithm = hashAlgorithm != null ? hashAlgorithm : HashAlgorithm.forByte(hashAlgorithmByte);
126
127        this.flags = flags;
128        this.iterations = iterations;
129        this.salt = salt;
130        this.nextHashed = nextHashed;
131        this.types = types;
132        this.typeBitmap = NSEC.createTypeBitMap(types);
133    }
134
135    public NSEC3(byte hashAlgorithm, byte flags, int iterations, byte[] salt, byte[] nextHashed, TYPE[] types) {
136        this(null, hashAlgorithm, flags, iterations, salt, nextHashed, types);
137    }
138
139    @Override
140    public TYPE getType() {
141        return TYPE.NSEC3;
142    }
143
144    @Override
145    public void serialize(DataOutputStream dos) throws IOException {
146        dos.writeByte(hashAlgorithmByte);
147        dos.writeByte(flags);
148        dos.writeShort(iterations);
149        dos.writeByte(salt.length);
150        dos.write(salt);
151        dos.writeByte(nextHashed.length);
152        dos.write(nextHashed);
153        dos.write(typeBitmap);
154    }
155
156    @Override
157    public String toString() {
158        StringBuilder sb = new StringBuilder()
159                .append(hashAlgorithm).append(' ')
160                .append(flags).append(' ')
161                .append(iterations).append(' ')
162                .append(salt.length == 0 ? "-" : new BigInteger(1, salt).toString(16).toUpperCase()).append(' ')
163                .append(Base32.encodeToString(nextHashed));
164        for (TYPE type : types) {
165            sb.append(' ').append(type);
166        }
167        return sb.toString();
168    }
169}