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}