[Git][NTPsec/ntpsec][master] 2 commits: libntp: compute the IPv6 refid without OpenSSL
Matt Selsky (@selsky)
gitlab at mg.gitlab.com
Sat Oct 10 02:22:31 UTC 2026
Matt Selsky pushed to branch master at NTPsec / ntpsec
Commits:
e78bab66 by Mark Atwood at 2026-10-10T02:11:53+00:00
libntp: compute the IPv6 refid without OpenSSL
RFC 5905 makes the refid for an IPv6 peer from the first four
octets of the MD5 hash of its address. It is an identifier, not a
security function, but addr2refid() asked OpenSSL for MD5 and called
exit(1) when it could not get it.
Since "Load OpenSSL 3.x default provider in libntp/macencrypt.c
addr2refid() function" the fetch uses a private library context, so
it works as long as libcrypto's built-in default provider has MD5.
Where that provider is replaced by one without MD5, as in FIPS builds
that make their FIPS provider the default, ntpd still exits at
startup with "MAC: MD5 init failed" on any host with an IPv6
address, ::1 included.
Add a small MD5 (RFC 1321) in libntp/md5.c and use it for the refid
only. The refid no longer depends on the OpenSSL configuration and
the exit paths are gone. The value does not change: the existing
IPv6AddressToRefId vector still passes, and a new test checks md5.c
against the RFC 1321 test suite.
- - - - -
46a2f546 by Mark Atwood at 2026-10-10T02:11:53+00:00
libntp/authreadkeys.c: skip keys OpenSSL can't run
EVP_get_digestbyname() and EVP_get_cipherbyname() return a method
for every algorithm libcrypto knows by name, even when no loaded
provider implements it. With a FIPS-only provider configuration an
MD5 key loads, then every packet logs "MAC: encrypt: digest init
failed" and the association never authenticates.
With OpenSSL 3, fetch the digest or cipher when the key is read, as
the FIXMEs in try_hmac() and try_cmac() asked. A key whose algorithm
is not available is skipped with a log line that points to AES-128
CMAC (RFC 8573) or NTS.
- - - - -
6 changed files:
- include/ntp_stdlib.h
- libntp/authreadkeys.c
- libntp/macencrypt.c
- + libntp/md5.c
- libntp/wscript
- tests/libntp/macencrypt.c
Changes:
=====================================
include/ntp_stdlib.h
=====================================
@@ -63,6 +63,10 @@ int ntp_getopt_long(int argc, char* const argv[], const char *optstring,
/* mac_md5encrypt.c */
extern uint32_t addr2refid (sockaddr_u *);
+/* md5.c */
+#define NTP_MD5_LENGTH 16
+extern void ntp_md5 (const void *, size_t, uint8_t *);
+
/* emalloc.c */
#ifndef EREALLOC_CALLSITE /* ntp_malloc.h defines */
extern void * ereallocz (void *, size_t, size_t, int);
=====================================
libntp/authreadkeys.c
=====================================
@@ -93,7 +93,6 @@ try_hmac(const char *upcased, char* namebuf) {
if (EVP_get_digestbyname(namebuf) == NULL) {
return NULL;
}
- /* FIXME: 3.0 needs a Fetch to be sure it really exists. */
return namebuf;
}
@@ -109,7 +108,6 @@ try_cmac(const char *upcased, char* namebuf) {
if (EVP_get_cipherbyname(namebuf) == NULL) {
return NULL;
}
- /* FIXME: 3.0 needs a Fetch to be sure it really exists. */
return namebuf;
}
@@ -132,6 +130,40 @@ try_digest(char *upcased, char *namebuf) {
return NULL;
}
+/*
+ * EVP_get_digestbyname() and EVP_get_cipherbyname() know every name
+ * libcrypto was built with, even when no loaded provider implements
+ * it, as with MD5 under a FIPS provider. Fetch to be sure.
+ */
+static bool
+auth_available(
+ AUTH_Type type,
+ const char *name
+ )
+{
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+ if (AUTH_CMAC == type) {
+ EVP_CIPHER *cipher = EVP_CIPHER_fetch(NULL, name, NULL);
+
+ if (NULL == cipher) {
+ return false;
+ }
+ EVP_CIPHER_free(cipher);
+ } else {
+ EVP_MD *md = EVP_MD_fetch(NULL, name, NULL);
+
+ if (NULL == md) {
+ return false;
+ }
+ EVP_MD_free(md);
+ }
+#else
+ UNUSED_ARG(type);
+ UNUSED_ARG(name);
+#endif
+ return true;
+}
+
static void
check_digest_mac_length(
keyid_t keyno,
@@ -483,6 +515,13 @@ msyslog(LOG_ERR, "AUTH: authreadkeys: reading %s", file);
keyno, token);
continue;
}
+ if (!auth_available(type, name)) {
+ msyslog(LOG_ERR,
+ "AUTH: authreadkeys: key %u: %s not available "
+ "from OpenSSL, skipped; use AES-128 CMAC "
+ "(RFC 8573) or NTS", keyno, token);
+ continue;
+ }
=====================================
libntp/macencrypt.c
=====================================
@@ -51,7 +51,6 @@
#include <openssl/err.h>
#include <openssl/evp.h> /* provides OpenSSL digest API */
-#include <openssl/md5.h>
#include "hack-ancient-openssl.h"
#include "ntp_fp.h"
@@ -420,57 +419,13 @@ digest_decrypt(
uint32_t
addr2refid(sockaddr_u *addr)
{
- uint8_t digest[MD5_DIGEST_LENGTH];
+ uint8_t digest[NTP_MD5_LENGTH];
uint32_t addr_refid;
- static EVP_MD_CTX *ctx;
- unsigned int len;
if (IS_IPV4(addr))
return (NSRCADR(addr));
-#if OPENSSL_VERSION_NUMBER >= 0x30000000L
- static OSSL_LIB_CTX *libctx;
- EVP_MD *md5 = 0;
- if(libctx == NULL) {
- libctx = OSSL_LIB_CTX_new();
- if(libctx == NULL) {
- msyslog(LOG_ERR, "MAC: MD5 init failed");
- exit(1);
- }
- }
- if(ctx == NULL) {
- ctx = EVP_MD_CTX_new();
- if(ctx == NULL) {
- msyslog(LOG_ERR, "MAC: MD5 init failed");
- exit(1);
- }
- }
- /* See section FIPS Provider:
- * https://www.openssl.org/docs/man3.0/man7/crypto.html
- * for property query strings
- */
- md5 = EVP_MD_fetch(libctx, "MD5", "fips=no");
- if(!EVP_DigestInit_ex(ctx, md5, NULL)) {
- msyslog(LOG_ERR, "MAC: MD5 init failed");
- exit(1);
- }
- EVP_MD_free(md5);
-#else
- if(ctx == NULL) {
- ctx = EVP_MD_CTX_new();
- }
-#ifdef EVP_MD_CTX_FLAG_NON_FIPS_ALLOW
- /* MD5 is not used as a crypto hash here. */
- EVP_MD_CTX_set_flags(ctx, EVP_MD_CTX_FLAG_NON_FIPS_ALLOW);
-#endif
- if (!EVP_DigestInit_ex(ctx, EVP_md5(), NULL)) {
- msyslog(LOG_ERR, "MAC: MD5 init failed");
- exit(1);
- }
-#endif
- EVP_DigestUpdate(ctx, (uint8_t *)PSOCK_ADDR6(addr),
- sizeof(struct in6_addr));
- EVP_DigestFinal_ex(ctx, digest, &len);
+ ntp_md5(PSOCK_ADDR6(addr), sizeof(struct in6_addr), digest);
memcpy(&addr_refid, digest, sizeof(addr_refid));
return (addr_refid);
}
=====================================
libntp/md5.c
=====================================
@@ -0,0 +1,116 @@
+/*
+ * Copyright the NTPsec project contributors
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * MD5 (RFC 1321), only for the IPv6 refid. RFC 5905 fixes the
+ * algorithm there and it protects nothing, so it is computed here
+ * rather than asked of OpenSSL, whose providers may not offer MD5
+ * (FIPS). Do not use this for anything that needs a secure hash.
+ */
+
+#include "config.h"
+
+#include <stdint.h>
+#include <string.h>
+
+#include "ntp_stdlib.h"
+
+static const uint32_t md5_k[64] = {
+ 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
+ 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
+ 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
+ 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
+ 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
+ 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
+ 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
+ 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
+ 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
+ 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
+ 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
+ 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
+ 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
+ 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
+ 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
+ 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
+};
+
+static const uint8_t md5_s[64] = {
+ 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
+ 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
+ 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
+ 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21
+};
+
+static void
+md5_block(
+ uint32_t h[4],
+ const uint8_t *p
+ )
+{
+ uint32_t m[16];
+ uint32_t a = h[0], b = h[1], c = h[2], d = h[3];
+ uint32_t f, t;
+ unsigned int i, g;
+
+ for (i = 0; i < 16; i++) {
+ m[i] = (uint32_t)p[4*i] | (uint32_t)p[4*i + 1] << 8 |
+ (uint32_t)p[4*i + 2] << 16 | (uint32_t)p[4*i + 3] << 24;
+ }
+ for (i = 0; i < 64; i++) {
+ if (i < 16) {
+ f = (b & c) | (~b & d);
+ g = i;
+ } else if (i < 32) {
+ f = (d & b) | (~d & c);
+ g = (5*i + 1) % 16;
+ } else if (i < 48) {
+ f = b ^ c ^ d;
+ g = (3*i + 5) % 16;
+ } else {
+ f = c ^ (b | ~d);
+ g = (7*i) % 16;
+ }
+ t = a + f + md5_k[i] + m[g];
+ a = d;
+ d = c;
+ c = b;
+ b += (t << md5_s[i]) | (t >> (32 - md5_s[i]));
+ }
+ h[0] += a;
+ h[1] += b;
+ h[2] += c;
+ h[3] += d;
+}
+
+void
+ntp_md5(
+ const void *data,
+ size_t len,
+ uint8_t *digest
+ )
+{
+ uint32_t h[4] = { 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 };
+ const uint8_t *p = data;
+ uint64_t bits = (uint64_t)len * 8;
+ uint8_t tail[128];
+ size_t tlen;
+ unsigned int i;
+
+ for (; len >= 64; len -= 64, p += 64) {
+ md5_block(h, p);
+ }
+ memset(tail, 0, sizeof(tail));
+ memcpy(tail, p, len);
+ tail[len] = 0x80;
+ tlen = (len < 56) ? 64 : 128;
+ for (i = 0; i < 8; i++) {
+ tail[tlen - 8 + i] = (uint8_t)(bits >> (8*i));
+ }
+ md5_block(h, tail);
+ if (128 == tlen) {
+ md5_block(h, tail + 64);
+ }
+ for (i = 0; i < NTP_MD5_LENGTH; i++) {
+ digest[i] = (uint8_t)(h[i/4] >> (8*(i%4)));
+ }
+}
=====================================
libntp/wscript
=====================================
@@ -21,6 +21,7 @@ def build(ctx):
"isc_interfaceiter.c",
"isc_net.c",
"macencrypt.c",
+ "md5.c",
"ntp_endian.c",
"numtoa.c",
"refidsmear.c",
=====================================
tests/libntp/macencrypt.c
=====================================
@@ -2,7 +2,8 @@
#include "ntp_stdlib.h"
#include "ntp_auth.h"
-// Note that NTP doesn't use RFCs 1321 or 4631
+// RFC 1321: The MD5 Message-Digest Algorithm, used for IPv6 refids
+// https://www.rfc-editor.org/rfc/rfc1321
// RFC 4493: The AES-CMAC Algorithm
// https://www.rfc-editor.org/rfc/rfc4493
@@ -235,6 +236,34 @@ TEST(macencrypt, IPv6AddressToRefId) {
TEST_ASSERT_EQUAL(expected, addr2refid(&addr));
}
+TEST(macencrypt, MD5_RFC1321) {
+ /* RFC 1321 Appendix A.5 test suite */
+ static const struct {
+ const char *msg;
+ const char *hex;
+ } v[] = {
+ { "", "d41d8cd98f00b204e9800998ecf8427e" },
+ { "a", "0cc175b9c0f1b6a831c399e269772661" },
+ { "abc", "900150983cd24fb0d6963f7d28e17f72" },
+ { "message digest", "f96b697d7cb7938d525a2f31aaf161d0" },
+ { "abcdefghijklmnopqrstuvwxyz", "c3fcd3d76192e4007dfb496cca67e13b" },
+ { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
+ "d174ab98d277d9f5a5611c2c9f419d9f" },
+ { "12345678901234567890123456789012345678901234567890123456789012345678901234567890",
+ "57edf4a22be3c955ac49da2e2107b67a" },
+ };
+ uint8_t digest[NTP_MD5_LENGTH];
+ char hex[2*NTP_MD5_LENGTH + 1];
+
+ for (size_t i = 0; i < sizeof(v)/sizeof(v[0]); i++) {
+ ntp_md5(v[i].msg, strlen(v[i].msg), digest);
+ for (size_t j = 0; j < NTP_MD5_LENGTH; j++) {
+ snprintf(&hex[2*j], 3, "%02x", digest[j]);
+ }
+ TEST_ASSERT_EQUAL_STRING(v[i].hex, hex);
+ }
+}
+
TEST(macencrypt, null_trunc) {
unsigned char key[] = {
0x0f, 0xd2, 0x28, 0x7c, 0x1e, 0x97, 0xa5, 0x0c,
@@ -383,6 +412,7 @@ TEST_GROUP_RUNNER(macencrypt) {
RUN_TEST_CASE(macencrypt, DecryptInvalidHMAC);
RUN_TEST_CASE(macencrypt, IPv4AddressToRefId);
RUN_TEST_CASE(macencrypt, IPv6AddressToRefId);
+ RUN_TEST_CASE(macencrypt, MD5_RFC1321);
RUN_TEST_CASE(macencrypt, null_trunc);
RUN_TEST_CASE(macencrypt, CMAC_TestVectors);
}
View it on GitLab: https://gitlab.com/NTPsec/ntpsec/-/compare/41aab4b342457f76e700232f01e2d3a1b7bebbc5...46a2f546fb0ff50efac3570655187134e7e70fee
--
View it on GitLab: https://gitlab.com/NTPsec/ntpsec/-/compare/41aab4b342457f76e700232f01e2d3a1b7bebbc5...46a2f546fb0ff50efac3570655187134e7e70fee
You're receiving this email because of your account on gitlab.com. Manage all notifications: https://gitlab.com/-/profile/notifications | Help: https://gitlab.com/help
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.ntpsec.org/pipermail/vc/attachments/20261010/d5793eab/attachment-0001.htm>
More information about the vc
mailing list