1212import java .util .HashMap ;
1313import java .util .Map ;
1414import java .util .Objects ;
15+ import java .util .TreeMap ;
16+ import java .util .regex .Matcher ;
17+ import java .util .regex .Pattern ;
1518import javax .crypto .Mac ;
1619import javax .crypto .SecretKey ;
1720import javax .crypto .spec .SecretKeySpec ;
3134public 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 "." 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 (
0 commit comments