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