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