Skip to content

Commit c32a9b9

Browse files
committed
Add support for new TSIG algorithms from RFC 8945
1 parent 6cdb8d0 commit c32a9b9

2 files changed

Lines changed: 162 additions & 54 deletions

File tree

src/main/java/org/xbill/DNS/TSIG.java

Lines changed: 138 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
import java.util.HashMap;
1313
import java.util.Map;
1414
import java.util.Objects;
15+
import java.util.TreeMap;
16+
import java.util.regex.Matcher;
17+
import java.util.regex.Pattern;
1518
import javax.crypto.Mac;
1619
import javax.crypto.SecretKey;
1720
import javax.crypto.spec.SecretKeySpec;
@@ -31,45 +34,121 @@
3134
public class TSIG {
3235
// https://www.iana.org/assignments/tsig-algorithm-names/tsig-algorithm-names.xml
3336

34-
/** The domain name representing the gss-tsig algorithm. */
37+
/**
38+
* The domain name representing the gss-tsig algorithm.
39+
*
40+
* @see <a href="https://datatracker.ietf.org/doc/html/rfc3645">RFC 3645</a>
41+
*/
3542
public static final Name GSS_TSIG = Name.fromConstantString("gss-tsig.");
3643

37-
/** The domain name representing the HMAC-MD5 algorithm. */
44+
/**
45+
* The domain name representing the HMAC-MD5 algorithm.
46+
*
47+
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8945">RFC 8945</a>
48+
*/
3849
public static final Name HMAC_MD5 = Name.fromConstantString("HMAC-MD5.SIG-ALG.REG.INT.");
3950

4051
/**
4152
* The domain name representing the HMAC-MD5 algorithm.
4253
*
54+
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8945">RFC 8945</a>
4355
* @deprecated use {@link #HMAC_MD5}
4456
*/
4557
@Deprecated public static final Name HMAC = HMAC_MD5;
4658

47-
/** The domain name representing the HMAC-SHA1 algorithm. */
59+
/**
60+
* The domain name representing the HMAC-SHA1 algorithm.
61+
*
62+
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8945">RFC 8945</a>
63+
*/
4864
public static final Name HMAC_SHA1 = Name.fromConstantString("hmac-sha1.");
4965

50-
/** The domain name representing the HMAC-SHA224 algorithm. */
66+
/**
67+
* The domain name representing the HMAC-SHA224 algorithm.
68+
*
69+
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8945">RFC 8945</a>
70+
*/
5171
public static final Name HMAC_SHA224 = Name.fromConstantString("hmac-sha224.");
5272

53-
/** The domain name representing the HMAC-SHA256 algorithm. */
73+
/**
74+
* The domain name representing the HMAC-SHA256 algorithm.
75+
*
76+
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8945">RFC 8945</a>
77+
*/
5478
public static final Name HMAC_SHA256 = Name.fromConstantString("hmac-sha256.");
5579

56-
/** The domain name representing the HMAC-SHA384 algorithm. */
80+
/**
81+
* The domain name representing the HMAC-SHA384 algorithm.
82+
*
83+
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8945">RFC 8945</a>
84+
*/
5785
public static final Name HMAC_SHA384 = Name.fromConstantString("hmac-sha384.");
5886

59-
/** The domain name representing the HMAC-SHA512 algorithm. */
87+
/**
88+
* The domain name representing the HMAC-SHA512 algorithm.
89+
*
90+
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8945">RFC 8945</a>
91+
*/
6092
public static final Name HMAC_SHA512 = Name.fromConstantString("hmac-sha512.");
6193

94+
/**
95+
* The domain name representing the HMAC-SHA256-128 algorithm.
96+
*
97+
* @see <a href="https://datatracker.ietf.org/doc/html/rfc4868">RFC 4868</a>
98+
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8945">RFC 8945</a>
99+
* @since 3.6.3
100+
*/
101+
public static final Name HMAC_SHA256_128 = Name.fromConstantString("hmac-sha256-128.");
102+
103+
/**
104+
* The domain name representing the HMAC-SHA384-192 algorithm.
105+
*
106+
* @see <a href="https://datatracker.ietf.org/doc/html/rfc4868">RFC 4868</a>
107+
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8945">RFC 8945</a>
108+
* @since 3.6.3
109+
*/
110+
public static final Name HMAC_SHA384_192 = Name.fromConstantString("hmac-sha384-192.");
111+
112+
/**
113+
* The domain name representing the HMAC-SHA512-256 algorithm.
114+
*
115+
* @see <a href="https://datatracker.ietf.org/doc/html/rfc4868">RFC 4868</a>
116+
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8945">RFC 8945</a>
117+
* @since 3.6.3
118+
*/
119+
public static final Name HMAC_SHA512_256 = Name.fromConstantString("hmac-sha512-256.");
120+
62121
private static final Map<Name, String> algMap;
122+
private static final Map<Name, Integer> algLengthMap;
123+
private static final Pattern javaAlgNamePattern =
124+
Pattern.compile(
125+
"^Hmac(?<alg>(SHA(1|\\d{3})|MD5))(/(?<length>\\d{3}))?$", Pattern.CASE_INSENSITIVE);
63126

64127
static {
65-
Map<Name, String> out = new HashMap<>();
66-
out.put(HMAC_MD5, "HmacMD5");
67-
out.put(HMAC_SHA1, "HmacSHA1");
68-
out.put(HMAC_SHA224, "HmacSHA224");
69-
out.put(HMAC_SHA256, "HmacSHA256");
70-
out.put(HMAC_SHA384, "HmacSHA384");
71-
out.put(HMAC_SHA512, "HmacSHA512");
72-
algMap = Collections.unmodifiableMap(out);
128+
Map<Name, String> names = new TreeMap<>();
129+
names.put(HMAC_MD5, "HmacMD5");
130+
names.put(HMAC_SHA1, "HmacSHA1");
131+
names.put(HMAC_SHA224, "HmacSHA224");
132+
names.put(HMAC_SHA256, "HmacSHA256");
133+
names.put(HMAC_SHA384, "HmacSHA384");
134+
names.put(HMAC_SHA512, "HmacSHA512");
135+
// These must always be after the non-truncated versions
136+
names.put(HMAC_SHA256_128, "HmacSHA256");
137+
names.put(HMAC_SHA384_192, "HmacSHA384");
138+
names.put(HMAC_SHA512_256, "HmacSHA512");
139+
algMap = Collections.unmodifiableMap(names);
140+
141+
Map<Name, Integer> lengths = new HashMap<>();
142+
lengths.put(HMAC_MD5, 16);
143+
lengths.put(HMAC_SHA1, 20);
144+
lengths.put(HMAC_SHA224, 28);
145+
lengths.put(HMAC_SHA256, 32);
146+
lengths.put(HMAC_SHA384, 48);
147+
lengths.put(HMAC_SHA512, 64);
148+
lengths.put(HMAC_SHA256_128, 16);
149+
lengths.put(HMAC_SHA384_192, 24);
150+
lengths.put(HMAC_SHA512_256, 32);
151+
algLengthMap = Collections.unmodifiableMap(lengths);
73152
}
74153

75154
/**
@@ -84,29 +163,40 @@ public static Name algorithmToName(String alg) {
84163
throw new IllegalArgumentException("Null algorithm");
85164
}
86165

87-
// Special case. Allow "HMAC-MD5" as an alias
88-
// for the RFC name.
89-
if (alg.equalsIgnoreCase("HMAC-MD5") || alg.equalsIgnoreCase("HMAC-MD5.")) {
166+
// Handle Java algorithm names
167+
if (!alg.contains("-")) {
168+
Matcher m = javaAlgNamePattern.matcher(alg);
169+
if (m.matches()) {
170+
alg = "hmac-" + m.group("alg");
171+
String truncatedLength = m.group("length");
172+
if (truncatedLength != null) {
173+
alg += "-" + truncatedLength;
174+
}
175+
}
176+
}
177+
178+
if (!alg.endsWith(".")) {
179+
alg += ".";
180+
}
181+
182+
Name nameAlg;
183+
try {
184+
nameAlg = Name.fromString(alg);
185+
} catch (TextParseException e) {
186+
throw new RuntimeException(e);
187+
}
188+
189+
// Special case, allow "hmac-md5" as an alias for the RFC name.
190+
if (nameAlg.equals(Name.fromConstantString("hmac-md5."))) {
90191
return HMAC_MD5;
91192
}
92193

93-
// Search through the RFC Names in the map and match
94-
// if the algorithm name with or without the trailing dot.
95-
// The match is case-insensitive.
96-
return algMap.keySet().stream()
97-
.filter(n -> n.toString().equalsIgnoreCase(alg) || n.toString(true).equalsIgnoreCase(alg))
98-
.findAny()
99-
.orElseGet(
100-
() ->
101-
// Did not find an RFC name, so fall through
102-
// and try the java names in the value of each
103-
// entry. If not found after all this, then
104-
// throw an exception.
105-
algMap.entrySet().stream()
106-
.filter(e -> e.getValue().equalsIgnoreCase(alg))
107-
.map(Map.Entry::getKey)
108-
.findAny()
109-
.orElseThrow(() -> new IllegalArgumentException("Unknown algorithm: " + alg)));
194+
// Make sure we understand this name
195+
if (algMap.get(nameAlg) == null) {
196+
throw new IllegalArgumentException("Unknown algorithm: " + nameAlg);
197+
}
198+
199+
return nameAlg;
110200
}
111201

112202
/**
@@ -289,15 +379,19 @@ public TSIG(Name algorithm, String name, String key) {
289379
* <li>hmac-sha1.
290380
* <li>hmac-sha224.
291381
* <li>hmac-sha256.
382+
* <li>hmac-sha256-128.
292383
* <li>hmac-sha384.
384+
* <li>hmac-sha384-192.
293385
* <li>hmac-sha512.
386+
* <li>hmac-sha512-256.
294387
* </ul>
295388
* The trailing &quot;.&quot; can be omitted.
296389
* @param name The name of the shared key.
297390
* @param key The shared key's data represented as a base64 encoded string.
298391
* @throws IllegalArgumentException The key name is an invalid name
299392
* @throws IllegalArgumentException The key data is improperly encoded
300-
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8945">RFC8945</a>
393+
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8945">RFC 8945</a>
394+
* @apiNote Do NOT use the MD5 algorithms anymore.
301395
*/
302396
public TSIG(String algorithm, String name, String key) {
303397
this(algorithmToName(algorithm), name, key);
@@ -434,6 +528,9 @@ private TSIGRecord generate(
434528
log.trace(hexdump.dump("TSIG-HMAC variables", tsigVariables));
435529
}
436530
signature = hmac.doFinal(tsigVariables);
531+
if (signature.length > algLengthMap.get(alg)) {
532+
signature = Arrays.copyOfRange(signature, 0, algLengthMap.get(alg));
533+
}
437534
} else {
438535
signature = new byte[0];
439536
}
@@ -701,7 +798,7 @@ private static byte[] getTsigVariables(boolean fullSignature, TSIGRecord tsig) {
701798
return tsigVariables;
702799
}
703800

704-
private static int verifySignature(Mac hmac, byte[] signature) {
801+
private int verifySignature(Mac hmac, byte[] signature) {
705802
int digestLength = hmac.getMacLength();
706803

707804
// rfc4635#section-3.1, 4.:
@@ -721,6 +818,10 @@ private static int verifySignature(Mac hmac, byte[] signature) {
721818
return Rcode.BADSIG;
722819
} else {
723820
byte[] expectedSignature = hmac.doFinal();
821+
if (expectedSignature.length > algLengthMap.get(alg)) {
822+
expectedSignature = Arrays.copyOfRange(expectedSignature, 0, algLengthMap.get(alg));
823+
}
824+
724825
if (!verify(expectedSignature, signature)) {
725826
if (log.isDebugEnabled()) {
726827
log.debug(

src/test/java/org/xbill/DNS/TSIGTest.java

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// SPDX-License-Identifier: BSD-3-Clause
22
package org.xbill.DNS;
33

4+
import static org.assertj.core.api.Assertions.assertThat;
45
import static org.junit.jupiter.api.Assertions.assertEquals;
56
import static org.junit.jupiter.api.Assertions.assertFalse;
67
import static org.junit.jupiter.api.Assertions.assertNotEquals;
@@ -63,25 +64,30 @@ void signedQuery() throws IOException {
6364
}
6465

6566
/**
66-
* Check all of the string algorithm names defined in the javadoc. Confirm that java names also
67-
* allowed, even though undocumented. THis is to conserve backwards compatibility.
67+
* Check all the string algorithm names defined in the javadoc. Confirm that java names also
68+
* allowed, even though undocumented. This is to conserve backwards compatibility.
6869
*/
6970
@ParameterizedTest
70-
@ValueSource(
71-
strings = {
72-
"hmac-md5",
73-
"hmac-md5.sig-alg.reg.int.",
74-
"hmac-sha1",
75-
"hmac-sha224",
76-
"hmac-sha256",
77-
"hmac-sha256.",
78-
"hmac-sha384",
79-
"hmac-sha512",
80-
// Java names
81-
"HmacMD5",
82-
"HmacSHA256"
83-
})
84-
void queryStringAlg(String alg) throws IOException {
71+
@CsvSource({
72+
"hmac-md5,16",
73+
"hmac-md5.sig-alg.reg.int.,16",
74+
"hmac-sha1,20",
75+
"hmac-sha224,28",
76+
"hmac-sha256,32",
77+
"hmac-sha256.,32",
78+
"hmac-sha256-128,16",
79+
"hmac-sha384,48",
80+
"hmac-sha384-192,24",
81+
"hmac-sha512,64",
82+
"hmac-sha512-256,32",
83+
// Java names
84+
"HmacMD5,16",
85+
"HmacSHA1,20",
86+
"HmacSHA256,32",
87+
"HmacSHA256/128,16",
88+
"hmacsha256/128,16",
89+
})
90+
void queryStringAlg(String alg, int signatureLengthBytes) throws IOException {
8591
TSIG key = new TSIG(alg, "example.", "12345678");
8692

8793
Record rec = Record.newRecord(Name.fromString("www.example."), Type.A, DClass.IN);
@@ -95,6 +101,7 @@ void queryStringAlg(String alg) throws IOException {
95101
assertEquals(Rcode.NOERROR, result);
96102
assertTrue(parsed.isSigned());
97103
assertTrue(parsed.isVerified());
104+
assertThat(parsed.getTSIG().getSignature().length).isEqualTo(signatureLengthBytes);
98105
}
99106

100107
/** Confirm error thrown with illegal algorithm name. */

0 commit comments

Comments
 (0)