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}