]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
Bring in posix time conversion functions, originally from BoringSSL
authorBob Beck <beck@openssl.org>
Fri, 3 Oct 2025 12:16:33 +0000 (06:16 -0600)
committerNeil Horman <nhorman@openssl.org>
Tue, 21 Oct 2025 16:18:56 +0000 (12:18 -0400)
This is effectively a Julien date computation, but done as seconds since
the POSIX epoch, all checked for overflow and limited to dates from year
0000 to 9999.

This is advantageous as it removed the need to use the operating system provided
timegm() and gmtime() functions which are only semi-standardized in any case and
when they aren't there or don't work other nastiness needs to be tried to do
the same thing. Even when they are there, you need to worry about the size of
time_t and if the bad idea bears of unsigned time_t have visited this platform.

By simply doing the conversion ourselves, to and from and int64, this can be
done the same everywhere, and bounds checked when being put into a time_t.

This adds public API that is already added in libre and boring in
<openssl/posix_time.h>:

These are added in the forks due to noticing a fair bit of software needing
to perform similar error-prone conversions themselves when dealing with
ASN1 times (such as what we have to do in the tests). While I intend to
use some of this API for further simplification in a follow on changes in
several places (such as ocsp and X509) For now we only use it to simplify
the test helpers and clean that up a bunch

Reviewed-by: Neil Horman <nhorman@openssl.org>
Reviewed-by: Saša Nedvědický <sashan@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/28748)

crypto/asn1/a_time_posix.c [new file with mode: 0644]
crypto/asn1/asn1_local.h
crypto/asn1/build.info
crypto/build.info
crypto/o_time.c [deleted file]
doc/man3/OPENSSL_gmtime.pod
include/openssl/posix_time.h [new file with mode: 0644]
providers/fips.module.sources
util/libcrypto.num

diff --git a/crypto/asn1/a_time_posix.c b/crypto/asn1/a_time_posix.c
new file mode 100644 (file)
index 0000000..05f2c85
--- /dev/null
@@ -0,0 +1,279 @@
+/*
+ * Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License").  You may not use
+ * this file except in compliance with the License.  You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+/*
+ * Time conversion to/from POSIX time_t and struct tm, with no support
+ * for time zones other than UTC
+ */
+
+#include <inttypes.h>
+#include <limits.h>
+#include <stdint.h>
+#include <string.h>
+#include <time.h>
+
+#include <openssl/asn1.h>
+#include <openssl/posix_time.h>
+
+#include "asn1_local.h"
+
+#define SECS_PER_HOUR (int64_t)(60 * 60)
+#define SECS_PER_DAY (int64_t)(24 * SECS_PER_HOUR)
+
+/*
+ * Is a year/month/day combination valid, in the range from year 0000
+ * to 9999?
+ */
+static int is_valid_date(int64_t year, int64_t month, int64_t day)
+{
+    int days_in_month;
+
+    if (day < 1 || year < 0 || year > 9999)
+        return 0;
+    switch (month) {
+    case 1:
+    case 3:
+    case 5:
+    case 7:
+    case 8:
+    case 10:
+    case 12:
+        days_in_month = 31;
+        break;
+    case 4:
+    case 6:
+    case 9:
+    case 11:
+        days_in_month = 30;
+        break;
+    case 2:
+        if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
+            days_in_month = 29;
+        else
+            days_in_month = 28;
+        break;
+    default:
+        return 0;
+    }
+    return day <= days_in_month;
+}
+
+/*
+ * Is a time valid? Leap seconds of 60 are not considered valid, as
+ * the POSIX time in seconds does not include them.
+ */
+static int is_valid_time(int64_t hours, int64_t minutes, int64_t seconds)
+{
+    return hours >= 0 && minutes >= 0 && seconds >= 0 && hours <= 23 &&
+        minutes <= 59 && seconds <= 59;
+}
+
+/* 0000-01-01 00:00:00 UTC */
+#define MIN_POSIX_TIME INT64_C(-62167219200)
+/* 9999-12-31 23:59:59 UTC */
+#define MAX_POSIX_TIME INT64_C(253402300799)
+
+/* Is a int64 time representing a time within our expected range? */
+static int is_valid_posix_time(int64_t time)
+{
+    return MIN_POSIX_TIME <= time && time <= MAX_POSIX_TIME;
+}
+
+/*
+ * Inspired by algorithms presented in
+ * https://howardhinnant.github.io/date_algorithms.html
+ * (Public Domain)
+ */
+static int posix_time_from_utc(int64_t year, int64_t month, int64_t day,
+                               int64_t hours, int64_t minutes, int64_t seconds,
+                               int64_t *out_time)
+{
+    int64_t era, year_of_era, day_of_year, day_of_era, posix_days;
+
+    if (!is_valid_date(year, month, day) ||
+        !is_valid_time(hours, minutes, seconds))
+        return 0;
+    if (month <= 2)
+        year--;  /* Start years on Mar 1, so leap days end a year. */
+
+    /* At this point year will be in the range -1 and 9999. */
+    era = (year >= 0 ? year : year - 399) / 400;
+    year_of_era = year - era * 400;
+    day_of_year = (153 * (month > 2 ? month - 3 : month + 9) + 2) /
+        5 + day - 1;
+    day_of_era = year_of_era * 365 + year_of_era / 4 - year_of_era /
+        100 + day_of_year;
+    posix_days = era * 146097 + day_of_era - 719468;
+    *out_time = posix_days * SECS_PER_DAY + hours * SECS_PER_HOUR +
+        minutes * 60 + seconds;
+
+    return 1;
+}
+
+/*
+ * Inspired by algorithms presented in
+ * https://howardhinnant.github.io/date_algorithms.html
+ * (Public Domain)
+ */
+static int utc_from_posix_time(int64_t time, int *out_year, int *out_month,
+                               int *out_day, int *out_hours, int *out_minutes,
+                               int *out_seconds)
+{
+    int64_t days, leftover_seconds, era, day_of_era, year_of_era, day_of_year;
+    int64_t month_of_year;
+
+    if (!is_valid_posix_time(time))
+        return 0;
+
+    days = time / SECS_PER_DAY;
+    leftover_seconds = time % SECS_PER_DAY;
+    if (leftover_seconds < 0) {
+        days--;
+        leftover_seconds += SECS_PER_DAY;
+    }
+    days += 719468;  /*  Shift to starting epoch of Mar 1 0000. */
+
+    /* At this point, days will be in the range -61 and 3652364. */
+    era = (days > 0 ? days : days - 146096) / 146097;
+    day_of_era = days - era * 146097;
+    year_of_era = (day_of_era - day_of_era / 1460 + day_of_era / 36524 -
+                   day_of_era / 146096) / 365;
+    *out_year = (int)(year_of_era + era * 400);  /* Year starts on Mar 1 */
+    day_of_year = day_of_era - (365 * year_of_era + year_of_era / 4 -
+                                year_of_era / 100);
+    month_of_year = (5 * day_of_year + 2) / 153;
+    *out_month = (int) (month_of_year < 10 ? month_of_year + 3 :
+                  month_of_year - 9);
+    if (*out_month <= 2)
+        (*out_year)++;  /* Adjust year back to Jan 1 start of year. */
+
+    *out_day = (int)(day_of_year - (153 * month_of_year + 2) / 5 + 1);
+    *out_hours = (int) leftover_seconds / SECS_PER_HOUR;
+    leftover_seconds %= SECS_PER_HOUR;
+    *out_minutes = (int) leftover_seconds / 60;
+    *out_seconds = (int) leftover_seconds % 60;
+
+    return 1;
+}
+
+int OPENSSL_tm_to_posix(const struct tm *tm, int64_t *out)
+{
+    return posix_time_from_utc(tm->tm_year + (int64_t)1900,
+                               tm->tm_mon + (int64_t)1, tm->tm_mday,
+                               tm->tm_hour, tm->tm_min, tm->tm_sec, out);
+}
+
+int OPENSSL_posix_to_tm(int64_t time, struct tm *out_tm)
+{
+    struct tm tmp_tm = {0};
+
+    memset(out_tm, 0, sizeof(*out_tm));
+
+    if (!utc_from_posix_time(time, &tmp_tm.tm_year, &tmp_tm.tm_mon,
+                             &tmp_tm.tm_mday, &tmp_tm.tm_hour,
+                             &tmp_tm.tm_min, &tmp_tm.tm_sec))
+        return 0;
+
+    tmp_tm.tm_year -= 1900;
+    tmp_tm.tm_mon -= 1;
+
+    *out_tm = tmp_tm;
+
+    return 1;
+}
+
+int ossl_asn1_time_tm_to_time_t(const struct tm *tm, time_t *out)
+{
+    int64_t posix_time;
+    time_t test_t = -1;
+    int bad_idea_bears = (test_t > 0); /* time_t is unsigned */
+
+    if (!OPENSSL_tm_to_posix(tm, &posix_time))
+        return 0;
+
+    if (sizeof(time_t) == sizeof(int32_t)
+        && ((!bad_idea_bears && (posix_time > INT32_MAX
+                                     || posix_time < INT32_MIN))
+            || (bad_idea_bears && (posix_time > UINT32_MAX
+                                   || posix_time < 0))))
+        return 0;
+
+    *out = posix_time;
+    return 1;
+}
+
+int ossl_asn1_time_time_t_to_tm(const time_t *time, struct tm *out_tm)
+{
+    int64_t posix_time = *time;
+
+    return OPENSSL_posix_to_tm(posix_time, out_tm);
+}
+
+int OPENSSL_timegm(const struct tm *tm, time_t *out)
+{
+    return ossl_asn1_time_tm_to_time_t(tm, out);
+}
+
+struct tm * OPENSSL_gmtime(const time_t *time, struct tm *out_tm)
+{
+    if (!ossl_asn1_time_time_t_to_tm(time, out_tm))
+        return NULL;
+    return out_tm;
+}
+
+/* LibreSSL and BoringSSL use int64_t instead of long. */
+int OPENSSL_gmtime_adj(struct tm *tm, int offset_day, long offset_sec)
+{
+    int64_t posix_time;
+
+    if (!OPENSSL_tm_to_posix(tm, &posix_time))
+        return 0;
+
+    OPENSSL_assert(INT_MAX <= INT64_MAX / SECS_PER_DAY);
+    OPENSSL_assert(MAX_POSIX_TIME <= INT64_MAX - INT_MAX * SECS_PER_DAY);
+    OPENSSL_assert(MIN_POSIX_TIME >= INT64_MIN - INT_MIN * SECS_PER_DAY);
+
+    posix_time += offset_day * SECS_PER_DAY;
+
+    if (posix_time > 0 && offset_sec > INT64_MAX - posix_time)
+        return 0;
+    if (posix_time < 0 && offset_sec < INT64_MIN - posix_time)
+        return 0;
+    posix_time += offset_sec;
+
+    if (!OPENSSL_posix_to_tm(posix_time, tm))
+        return 0;
+
+    return 1;
+}
+
+int OPENSSL_gmtime_diff(int *out_days, int *out_secs, const struct tm *from,
+                        const struct tm *to)
+{
+    int64_t time_to, time_from, timediff, daydiff;
+
+    if (!OPENSSL_tm_to_posix(to, &time_to) ||
+        !OPENSSL_tm_to_posix(from, &time_from))
+        return 0;
+
+    /* Times are in range, so these calculations cannot overflow. */
+    OPENSSL_assert(SECS_PER_DAY <= INT_MAX);
+    OPENSSL_assert((MAX_POSIX_TIME - MIN_POSIX_TIME) / SECS_PER_DAY <= INT_MAX);
+
+    timediff = time_to - time_from;
+    daydiff = timediff / SECS_PER_DAY;
+    timediff %= SECS_PER_DAY;
+
+    *out_secs = (int) timediff;
+    *out_days = (int) daydiff;
+
+    return 1;
+}
+
+
index 587c58d840e1b7bff45be706f37b44566792d22f..f92c1c5a3f973406c34a617968c19b150a13101b 100644 (file)
@@ -94,3 +94,5 @@ ASN1_TIME *ossl_asn1_time_from_tm(ASN1_TIME *s, struct tm *ts, int type);
 
 int ossl_asn1_item_ex_new_intern(ASN1_VALUE **pval, const ASN1_ITEM *it,
                                  OSSL_LIB_CTX *libctx, const char *propq);
+int ossl_asn1_time_time_t_to_tm(const time_t *time, struct tm *out_tm);
+int ossl_asn1_time_tm_to_time_t(const struct tm *tm, time_t *out);
index 33b86fdd31f521dc898fbf099b585c9237322025..4256f80e10b2fd7bdd57cb6735e0e3f1d6cf4484 100644 (file)
@@ -14,7 +14,7 @@ SOURCE[../../libcrypto]=\
         asn1_gen.c asn1_parse.c asn1_lib.c asn1_err.c a_strnid.c \
         evp_asn1.c asn_pack.c p5_pbe.c p5_pbev2.c p5_scrypt.c p8_pkey.c \
         asn_moid.c asn_mstbl.c asn1_item_list.c \
-        d2i_param.c
+        d2i_param.c a_time_posix.c
 IF[{- !$disabled{'rsa'} and !$disabled{'rc4'} -}]
   SOURCE[../../libcrypto]=n_pkey.c
 ENDIF
index 1f522a729f8929d8a71b675f983998b48bf4ca93..8fed477fef6782d52e8c4eaa5fea777e7fb83a7e 100644 (file)
@@ -104,7 +104,7 @@ $UTIL_COMMON=\
 
 SOURCE[../libcrypto]=$UTIL_COMMON \
         mem.c mem_sec.c \
-        comp_methods.c cversion.c info.c cpt_err.c ebcdic.c uid.c o_time.c \
+        comp_methods.c cversion.c info.c cpt_err.c ebcdic.c uid.c \
         o_dir.c o_fopen.c getenv.c o_init.c init.c trace.c provider.c \
         provider_child.c punycode.c passphrase.c sleep.c \
         quic_vlint.c time.c defaults.c ssl_err.c
diff --git a/crypto/o_time.c b/crypto/o_time.c
deleted file mode 100644 (file)
index 23ffe16..0000000
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Copyright 2001-2021 The OpenSSL Project Authors. All Rights Reserved.
- *
- * Licensed under the Apache License 2.0 (the "License").  You may not use
- * this file except in compliance with the License.  You can obtain a copy
- * in the file LICENSE in the source distribution or at
- * https://www.openssl.org/source/license.html
- */
-
-#include <openssl/e_os2.h>
-#include <string.h>
-#include <openssl/crypto.h>
-
-struct tm *OPENSSL_gmtime(const time_t *timer, struct tm *result)
-{
-    struct tm *ts = NULL;
-
-#if defined(OPENSSL_THREADS) && defined(OPENSSL_SYS_VMS)
-    {
-        /*
-         * On VMS, gmtime_r() takes a 32-bit pointer as second argument.
-         * Since we can't know that |result| is in a space that can easily
-         * translate to a 32-bit pointer, we must store temporarily on stack
-         * and copy the result.  The stack is always reachable with 32-bit
-         * pointers.
-         */
-#if defined(OPENSSL_SYS_VMS) && __INITIAL_POINTER_SIZE
-# pragma pointer_size save
-# pragma pointer_size 32
-#endif
-        struct tm data, *ts2 = &data;
-#if defined OPENSSL_SYS_VMS && __INITIAL_POINTER_SIZE
-# pragma pointer_size restore
-#endif
-        if (gmtime_r(timer, ts2) == NULL)
-            return NULL;
-        memcpy(result, ts2, sizeof(struct tm));
-        ts = result;
-    }
-#elif defined(OPENSSL_THREADS) && !defined(OPENSSL_SYS_WIN32) && !defined(OPENSSL_SYS_MACOSX)
-    if (gmtime_r(timer, result) == NULL)
-        return NULL;
-    ts = result;
-#elif defined (OPENSSL_SYS_WINDOWS) && defined(_MSC_VER) && _MSC_VER >= 1400 && !defined(_WIN32_WCE)
-    if (gmtime_s(result, timer))
-        return NULL;
-    ts = result;
-#else
-    ts = gmtime(timer);
-    if (ts == NULL)
-        return NULL;
-
-    memcpy(result, ts, sizeof(struct tm));
-    ts = result;
-#endif
-    return ts;
-}
-
-/*
- * Take a tm structure and add an offset to it. This avoids any OS issues
- * with restricted date types and overflows which cause the year 2038
- * problem.
- */
-
-#define SECS_PER_DAY (24 * 60 * 60)
-
-static long date_to_julian(int y, int m, int d);
-static void julian_to_date(long jd, int *y, int *m, int *d);
-static int julian_adj(const struct tm *tm, int off_day, long offset_sec,
-                      long *pday, int *psec);
-
-int OPENSSL_gmtime_adj(struct tm *tm, int off_day, long offset_sec)
-{
-    int time_sec, time_year, time_month, time_day;
-    long time_jd;
-
-    /* Convert time and offset into Julian day and seconds */
-    if (!julian_adj(tm, off_day, offset_sec, &time_jd, &time_sec))
-        return 0;
-
-    /* Convert Julian day back to date */
-
-    julian_to_date(time_jd, &time_year, &time_month, &time_day);
-
-    if (time_year < 1900 || time_year > 9999)
-        return 0;
-
-    /* Update tm structure */
-
-    tm->tm_year = time_year - 1900;
-    tm->tm_mon = time_month - 1;
-    tm->tm_mday = time_day;
-
-    tm->tm_hour = time_sec / 3600;
-    tm->tm_min = (time_sec / 60) % 60;
-    tm->tm_sec = time_sec % 60;
-
-    return 1;
-
-}
-
-int OPENSSL_gmtime_diff(int *pday, int *psec,
-                        const struct tm *from, const struct tm *to)
-{
-    int from_sec, to_sec, diff_sec;
-    long from_jd, to_jd, diff_day;
-    if (!julian_adj(from, 0, 0, &from_jd, &from_sec))
-        return 0;
-    if (!julian_adj(to, 0, 0, &to_jd, &to_sec))
-        return 0;
-    diff_day = to_jd - from_jd;
-    diff_sec = to_sec - from_sec;
-    /* Adjust differences so both positive or both negative */
-    if (diff_day > 0 && diff_sec < 0) {
-        diff_day--;
-        diff_sec += SECS_PER_DAY;
-    }
-    if (diff_day < 0 && diff_sec > 0) {
-        diff_day++;
-        diff_sec -= SECS_PER_DAY;
-    }
-
-    if (pday)
-        *pday = (int)diff_day;
-    if (psec)
-        *psec = diff_sec;
-
-    return 1;
-
-}
-
-/* Convert tm structure and offset into julian day and seconds */
-static int julian_adj(const struct tm *tm, int off_day, long offset_sec,
-                      long *pday, int *psec)
-{
-    int offset_hms;
-    long offset_day, time_jd;
-    int time_year, time_month, time_day;
-    /* split offset into days and day seconds */
-    offset_day = offset_sec / SECS_PER_DAY;
-    /* Avoid sign issues with % operator */
-    offset_hms = offset_sec - (offset_day * SECS_PER_DAY);
-    offset_day += off_day;
-    /* Add current time seconds to offset */
-    offset_hms += tm->tm_hour * 3600 + tm->tm_min * 60 + tm->tm_sec;
-    /* Adjust day seconds if overflow */
-    if (offset_hms >= SECS_PER_DAY) {
-        offset_day++;
-        offset_hms -= SECS_PER_DAY;
-    } else if (offset_hms < 0) {
-        offset_day--;
-        offset_hms += SECS_PER_DAY;
-    }
-
-    /*
-     * Convert date of time structure into a Julian day number.
-     */
-
-    time_year = tm->tm_year + 1900;
-    time_month = tm->tm_mon + 1;
-    time_day = tm->tm_mday;
-
-    time_jd = date_to_julian(time_year, time_month, time_day);
-
-    /* Work out Julian day of new date */
-    time_jd += offset_day;
-
-    if (time_jd < 0)
-        return 0;
-
-    *pday = time_jd;
-    *psec = offset_hms;
-    return 1;
-}
-
-/*
- * Convert date to and from julian day Uses Fliegel & Van Flandern algorithm
- */
-static long date_to_julian(int y, int m, int d)
-{
-    return (1461 * (y + 4800 + (m - 14) / 12)) / 4 +
-        (367 * (m - 2 - 12 * ((m - 14) / 12))) / 12 -
-        (3 * ((y + 4900 + (m - 14) / 12) / 100)) / 4 + d - 32075;
-}
-
-static void julian_to_date(long jd, int *y, int *m, int *d)
-{
-    long L = jd + 68569;
-    long n = (4 * L) / 146097;
-    long i, j;
-
-    L = L - (146097 * n + 3) / 4;
-    i = (4000 * (L + 1)) / 1461001;
-    L = L - (1461 * i) / 4 + 31;
-    j = (80 * L) / 2447;
-    *d = L - (2447 * j) / 80;
-    L = j / 11;
-    *m = j + 2 - (12 * L);
-    *y = 100 * (n - 49) + i + L;
-}
index b86557f3ca9ec5e0020ed9e66fd95262e8ebc51e..0c7fd4b80dbd1480a5914aacfd35d1627ea114f1 100644 (file)
@@ -4,7 +4,10 @@
 
 OPENSSL_gmtime,
 OPENSSL_gmtime_adj,
-OPENSSL_gmtime_diff - platform-agnostic OpenSSL time routines
+OPENSSL_gmtime_diff,
+OPENSSL_timegm,
+OPENSSL_posix_to_tm,
+OPENSSL_tm_to_posix - platform-agnostic OpenSSL time routines
 
 =head1 SYNOPSIS
 
@@ -15,15 +18,35 @@ OPENSSL_gmtime_diff - platform-agnostic OpenSSL time routines
  int OPENSSL_gmtime_diff(int *pday, int *psec,
                         const struct tm *from, const struct tm *to);
 
+ #include <openssl/posix_time.h>
+ int OPENSSL_timegm(const struct tm *tm, time_t *out_time);
+ int OPENSSL_posix_to_tm(int64_t time struct tm *out_tm);
+ int OPENSSL_tm_to_posix(struct tm t_tm int64_t *out);
+
 =head1 DESCRIPTION
 
-OPENSSL_gmtime() returns the UTC time specified by I<timer> into the provided
-I<result> argument.
+OPENSSL_gmtime() returns the UTC time specified by I<timer> into the
+provided I<result> argument. |timer| must be in the range of year 0 to
+9999.  Only the fields I<tm_year>, I<tm_mon>, I<tm_mday>, I<tm_hour>,
+I<tm_min>, and I<tm_sec> will be updated  in I<result> on success,
+all other fields will be zeroed.
 
 OPENSSL_gmtime_adj() adds the offsets in I<offset_day> and I<offset_sec> to I<tm>.
 
 OPENSSL_gmtime_diff() calculates the difference between I<from> and I<to>.
 
+OPENSSL_timegm() converts a time structure in UTC time in I<tm> to a time_t value in
+I<out_time>.
+
+OPENSSL_posix_to_tm() converts a int64_t POSIX time value in I<time>,
+which must be in the range of year 0 to 9999, to a broken out time
+value in I<tm>. Only the fields I<tm_year>, I<tm_mon>, I<tm_mday>,
+I<tm_hour>, I<tm_min>, and I<tm_sec> will be updated in I<tm>
+on success, all other fields will be zeroed.
+
+OPENSSL_tm_to_posix() converts a time value between the years 0 and 9999 in I<tm>
+to a POSIX time value in I<out>.
+
 =head1 NOTES
 
 It is an error to call OPENSSL_gmtime() with I<result> equal to NULL. The
@@ -41,7 +64,17 @@ than the number of seconds per day (3600). Leap seconds are not considered.
 
 =head1 RETURN VALUES
 
-OPENSSL_gmtime() returns NULL on error, or I<result> on success.
+OPENSSL_gmtime returns I<result> on success or NULL for failure.
+It can fail if the time is not representable in a struct tm,
+or if the year is less than 0 or more than 9999.
+
+OPENSSL_timegm() returns 1 for success or 0 for failure.
+It can fail if the time is not representable in a time_t,
+or if the year is less than 0 or more than 9999.
+
+OPENSSL_posix_to_tm() and OPENSSL_tm_to_posix()
+return 1 for success or 0 on failure.
+It is a failure if the year is less than 0 or more than 9999.
 
 OPENSSL_gmtime_adj() and OPENSSL_gmtime_diff() return 0 on error, and 1 on success.
 
@@ -49,6 +82,8 @@ OPENSSL_gmtime_adj() and OPENSSL_gmtime_diff() return 0 on error, and 1 on succe
 
 OPENSSL_gmtime(), OPENSSL_gmtime_adj() and OPENSSL_gmtime_diff() have been
 in OpenSSL since 1.0.0.
+OPENSSL_timegm(), OPENSSL_posix_to_tm() and OPENSSL_tm_to_posix() were originally
+from BoringSSL and have been in OpenSSL since 4.0.0
 
 =head1 COPYRIGHT
 
diff --git a/include/openssl/posix_time.h b/include/openssl/posix_time.h
new file mode 100644 (file)
index 0000000..43ca29c
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License").  You may not use
+ * this file except in compliance with the License.  You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#ifndef OPENSSL_HEADER_POSIX_TIME_H
+# define OPENSSL_HEADER_POSIX_TIME_H
+# include <stdint.h>
+# include <time.h>
+
+# if defined(__cplusplus)
+extern "C" {
+# endif
+
+    /*
+     * OPENSSL_posix_to_tm converts a int64_t POSIX time value in
+     * |time|, which must be in the range of year 0000 to 9999, to a
+     * broken out time value in |tm|. It returns one on success and
+     * zero on error.
+     */
+    int OPENSSL_posix_to_tm(int64_t time, struct tm *out_tm);
+
+    /*
+     * OPENSSL_tm_to_posix converts a time value between the years 0
+     * and 9999 in |tm| to a POSIX time value in |out|. One is
+     * returned on success, zero is returned on failure. It is a
+     * failure if |tm| contains out of range values.
+     */
+    int OPENSSL_tm_to_posix(const struct tm *tm, int64_t *out);
+
+    /*
+     * OPENSSL_timegm converts a time value between the years 0 and
+     * 9999 in |tm| to a time_t value in |out|. One is returned on
+     * success, zero is returned on failure. It is a failure if the
+     * converted time can not be represented in a time_t, or if the tm
+     * contains out of range values.
+     */
+    int OPENSSL_timegm(const struct tm *tm, time_t *out);
+
+# if defined(__cplusplus)
+}  /* extern C */
+# endif
+
+#endif  /* OPENSSL_HEADER_POSIX_TIME_H */
index 6bac8d40420bb22f5e642d76904b22f6f9866dd7..333af461e5af82d14b049a9d7c117b40780a83a8 100644 (file)
@@ -569,6 +569,7 @@ include/openssl/pem.h
 include/openssl/pemerr.h
 include/openssl/pkcs7.h.in
 include/openssl/pkcs7err.h
+include/openssl/posix_time.h
 include/openssl/prov_ssl.h
 include/openssl/proverr.h
 include/openssl/provider.h
index c1f4432ec9f1c80e6d94cf8ffbf0bd87acf07064..327f9ca8924dfe07020395e8438e25cd98569460 100644 (file)
@@ -5947,3 +5947,6 @@ CRYPTO_realloc_array                    ? 4_0_0   EXIST::FUNCTION:
 CRYPTO_clear_realloc_array              ?      4_0_0   EXIST::FUNCTION:
 CRYPTO_secure_malloc_array              ?      4_0_0   EXIST::FUNCTION:
 CRYPTO_secure_calloc                    ?      4_0_0   EXIST::FUNCTION:
+OPENSSL_posix_to_tm                     ?      4_0_0   EXIST::FUNCTION:
+OPENSSL_tm_to_posix                     ?      4_0_0   EXIST::FUNCTION:
+OPENSSL_timegm                          ?      4_0_0   EXIST::FUNCTION: