001/*
002 * Copyright 2015-2024 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.dnslabel.DnsLabel;
014import org.minidns.record.Record.TYPE;
015import org.minidns.util.Base32;
016
017import java.io.DataInputStream;
018import java.io.DataOutputStream;
019import java.io.IOException;
020import java.math.BigInteger;
021import java.util.Arrays;
022import java.util.Collections;
023import java.util.HashMap;
024import java.util.List;
025import java.util.Locale;
026import java.util.Map;
027
028/**
029 * NSEC3 record payload.
030 */
031public class NSEC3 extends Data {
032
033    /**
034     * This Flag indicates whether this NSEC3 RR may cover unsigned
035     * delegations.
036     */
037    public static final byte FLAG_OPT_OUT = 0x1;
038
039    private static final Map<Byte, HashAlgorithm> HASH_ALGORITHM_LUT = new HashMap<>();
040
041    /**
042     * DNSSEC NSEC3 Hash Algorithms.
043     *
044     * @see <a href=
045     *      "https://www.iana.org/assignments/dnssec-nsec3-parameters/dnssec-nsec3-parameters.xhtml#dnssec-nsec3-parameters-3">
046     *      IANA DNSSEC NSEC3 Hash Algorithms</a>
047     */
048    public enum HashAlgorithm {
049        RESERVED(0, "Reserved"),
050        SHA1(1, "SHA-1"),
051        ;
052
053        HashAlgorithm(int value, String description) {
054            if (value < 0 || value > 255) {
055                throw new IllegalArgumentException();
056            }
057            this.value = (byte) value;
058            this.description = description;
059            HASH_ALGORITHM_LUT.put(this.value, this);
060        }
061
062        public final byte value;
063        public final String description;
064
065        public static HashAlgorithm forByte(byte b) {
066            return HASH_ALGORITHM_LUT.get(b);
067        }
068    }
069
070    /**
071     * The cryptographic hash algorithm used. If MiniDNS
072     * isn't aware of the hash algorithm, then this field will be
073     * <code>null</code>.
074     * 
075     * @see #hashAlgorithmByte
076     */
077    public final HashAlgorithm hashAlgorithm;
078
079    /**
080     * The byte value of the cryptographic hash algorithm used.
081     */
082    public final byte hashAlgorithmByte;
083
084    /**
085     * Bitmap of flags: {@link #FLAG_OPT_OUT}.
086     */
087    public final byte flags;
088
089    /**
090     * The number of iterations the hash algorithm is applied.
091     */
092    public final int /* unsigned short */ iterations;
093
094    /**
095     * The salt appended to the next owner name before hashing.
096     */
097    private final byte[] salt;
098
099    /**
100     * The next hashed owner name in hash order.
101     */
102    private final byte[] nextHashed;
103
104    /**
105     * The type bitmap in wire format. If this NSEC3 was parsed, then those are the exact bytes as received.
106     *
107     * @see NSEC#typeValues
108     */
109    private final byte[] typeBitmap;
110
111    /**
112     * The RR types existing at the original owner name. This only contains the types known to MiniDNS, see
113     * {@link #typeValues} for all types.
114     */
115    public final List<TYPE> types;
116
117    /**
118     * The numeric values of all RR types existing at the original owner name, including the ones unknown to MiniDNS.
119     */
120    public final List<Integer> typeValues;
121
122    public static NSEC3 parse(DataInputStream dis, int length) throws IOException {
123        byte hashAlgorithm = dis.readByte();
124        byte flags = dis.readByte();
125        int iterations = dis.readUnsignedShort();
126        int saltLength = dis.readUnsignedByte();
127        byte[] salt = new byte[saltLength];
128        if (dis.read(salt) != salt.length) throw new IOException();
129        int hashLength = dis.readUnsignedByte();
130        byte[] nextHashed = new byte[hashLength];
131        if (dis.read(nextHashed) != nextHashed.length) throw new IOException();
132        byte[] typeBitmap = new byte[length - (6 + saltLength + hashLength)];
133        if (dis.read(typeBitmap) != typeBitmap.length) throw new IOException();
134        List<Integer> typeValues = NSEC.readTypeBitMap(typeBitmap);
135        return new NSEC3(null, hashAlgorithm, flags, iterations, salt, nextHashed, typeBitmap, typeValues);
136    }
137
138    private NSEC3(HashAlgorithm hashAlgorithm, byte hashAlgorithmByte, byte flags, int iterations, byte[] salt, byte[] nextHashed, List<TYPE> types) {
139        this(hashAlgorithm, hashAlgorithmByte, flags, iterations, salt, nextHashed, NSEC.createTypeBitMap(types), NSEC.toTypeValues(types));
140    }
141
142    private NSEC3(HashAlgorithm hashAlgorithm, byte hashAlgorithmByte, byte flags, int iterations, byte[] salt, byte[] nextHashed, byte[] typeBitmap, List<Integer> typeValues) {
143        assert hashAlgorithmByte == (hashAlgorithm != null ? hashAlgorithm.value : hashAlgorithmByte);
144        this.hashAlgorithmByte = hashAlgorithmByte;
145        this.hashAlgorithm = hashAlgorithm != null ? hashAlgorithm : HashAlgorithm.forByte(hashAlgorithmByte);
146
147        this.flags = flags;
148        this.iterations = iterations;
149        this.salt = salt;
150        this.nextHashed = nextHashed;
151        this.typeBitmap = typeBitmap;
152        this.typeValues = Collections.unmodifiableList(typeValues);
153        this.types = Collections.unmodifiableList(NSEC.toKnownTypes(typeValues));
154    }
155
156    public NSEC3(byte hashAlgorithm, byte flags, int iterations, byte[] salt, byte[] nextHashed, List<TYPE> types) {
157        this(null, hashAlgorithm, flags, iterations, salt, nextHashed, types);
158    }
159
160    public NSEC3(byte hashAlgorithm, byte flags, int iterations, byte[] salt, byte[] nextHashed, TYPE... types) {
161        this(null, hashAlgorithm, flags, iterations, salt, nextHashed, Arrays.asList(types));
162    }
163
164    @Override
165    public TYPE getType() {
166        return TYPE.NSEC3;
167    }
168
169    @Override
170    public void serialize(DataOutputStream dos) throws IOException {
171        dos.writeByte(hashAlgorithmByte);
172        dos.writeByte(flags);
173        dos.writeShort(iterations);
174        dos.writeByte(salt.length);
175        dos.write(salt);
176        dos.writeByte(nextHashed.length);
177        dos.write(nextHashed);
178        dos.write(typeBitmap);
179    }
180
181    @Override
182    public String toString() {
183        StringBuilder sb = new StringBuilder()
184                .append(hashAlgorithm).append(' ')
185                .append(flags).append(' ')
186                .append(iterations).append(' ')
187                .append(salt.length == 0 ? "-" : new BigInteger(1, salt).toString(16).toUpperCase(Locale.ROOT)).append(' ')
188                .append(Base32.encodeToString(nextHashed));
189        NSEC.appendTypes(sb, typeValues);
190        return sb.toString();
191    }
192
193    public byte[] getSalt() {
194        return salt.clone();
195    }
196
197    public int getSaltLength() {
198        return salt.length;
199    }
200
201    public byte[] getNextHashed() {
202        return nextHashed.clone();
203    }
204
205    private String nextHashedBase32Cache;
206
207    public String getNextHashedBase32() {
208        if (nextHashedBase32Cache == null) {
209            nextHashedBase32Cache = Base32.encodeToString(nextHashed);
210        }
211        return nextHashedBase32Cache;
212    }
213
214    private DnsLabel nextHashedDnsLabelCache;
215
216    public DnsLabel getNextHashedDnsLabel() {
217        if (nextHashedDnsLabelCache == null) {
218            String nextHashedBase32 = getNextHashedBase32();
219            nextHashedDnsLabelCache = DnsLabel.from(nextHashedBase32);
220        }
221        return nextHashedDnsLabelCache;
222    }
223
224    public void copySaltInto(byte[] dest, int destPos) {
225        System.arraycopy(salt, 0, dest, destPos, salt.length);
226    }
227}