swtpm_localca: Add support for up to 20 bytes serial numbers

Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>
diff --git a/man/man5/swtpm-localca.conf.pod b/man/man5/swtpm-localca.conf.pod
index 10933dc..cb472c2 100644
--- a/man/man5/swtpm-localca.conf.pod
+++ b/man/man5/swtpm-localca.conf.pod
@@ -41,6 +41,10 @@
 
 The name of file containing the serial number for the next certificate.
 
+The serial number must be a decimal number and must be representable
+with 20 bytes or less. Once 21 bytes are used a new random serial
+number with 20 decimal digits will be created.
+
 =item B<TSS_TCSD_HOSTNAME>
 
 This variable can be set to the host where B<tcsd> is running on in case
diff --git a/src/swtpm_localca/Makefile.am b/src/swtpm_localca/Makefile.am
index 285d4fc..74532a8 100644
--- a/src/swtpm_localca/Makefile.am
+++ b/src/swtpm_localca/Makefile.am
@@ -20,6 +20,7 @@
 	$(MY_CFLAGS) \
 	$(CFLAGS) \
 	$(GLIB_CFLAGS) \
+	$(GMP_CFLAGS) \
 	$(HARDENING_CFLAGS)
 
 swtpm_localca_DEPENDENCIES = \
@@ -32,6 +33,7 @@
 	-L$(top_builddir)/src/utils -lswtpm_utils \
 	$(MY_LDFLAGS) \
 	$(GLIB_LIBS) \
+	$(GMP_LIBS) \
 	$(HARDENING_LDFLAGS)
 
 swtpm_localca_SOURCES = \
diff --git a/src/swtpm_localca/swtpm_localca.c b/src/swtpm_localca/swtpm_localca.c
index eb75b83..1d620bf 100644
--- a/src/swtpm_localca/swtpm_localca.c
+++ b/src/swtpm_localca/swtpm_localca.c
@@ -24,6 +24,8 @@
 
 #include <glib.h>
 
+#include <gmp.h>
+
 #include "swtpm_conf.h"
 #include "swtpm_utils.h"
 #include "swtpm_localca_utils.h"
@@ -282,16 +284,36 @@
     return ret;
 }
 
+/* Create a random ASCII decimal number of given length.
+ * The buffer is not NUL terminated.
+ */
+static void get_random_serial(char *buffer, size_t length)
+{
+    GRand *grand = g_rand_new();
+    size_t i;
+
+    buffer[0] = '0' + g_rand_int_range(grand, 1, 9);
+    for (i = 1; i < length; i++)
+        buffer[i] = '0' + g_rand_int_range(grand, 0, 9);
+
+    g_rand_free(grand);
+}
+
 /* Get the next serial number from the certserial file; if it contains
- * a non-numeric content start over with serial number '1'.
+ * a non-numeric content start over with a random 20 digit serial number.
+ * Up to 20 bytes of serial number are supported. The max.
+ * serial number is decimal: 1461501637330902918203684832716283019655932542975
+ * This decimal number is 49 digits long.
+ * This function will write back the used serial number that the next
+ * caller must increase by '1' to be allowed to use it.
  */
 static int get_next_serial(const gchar *certserial, const gchar *lockfile,
                            gchar **serial_str)
 {
     g_autofree gchar *buffer = NULL;
+    char serialbuffer[50];
     size_t buffer_len;
-    unsigned long long serial, serial_n;
-    char *endptr = NULL;
+    mpz_t serial;
     int lockfd;
     int ret = 1;
 
@@ -299,25 +321,43 @@
     if (lockfd < 0)
         return 1;
 
-    if (access(certserial, R_OK) != 0)
-        write_file(certserial, (unsigned char *)"1", 1);
+    if (access(certserial, R_OK) != 0) {
+        get_random_serial(serialbuffer, 20);
+        write_file(certserial, (unsigned char *)serialbuffer, 20);
+    }
     if (read_file(certserial, &buffer, &buffer_len) != 0)
         goto error;
 
-    if (buffer_len > 0) {
-        serial = strtoull(buffer, &endptr, 10);
-        if (*endptr == '\0') {
-            serial_n = serial + 1;
-        } else {
-            serial_n = 1;
-        }
+    mpz_init(serial);
+
+    if (buffer_len > 0 && buffer_len <= 49) {
+        memcpy(serialbuffer, buffer, buffer_len);
+        serialbuffer[buffer_len] = 0;
+
+        if (gmp_sscanf(serialbuffer, "%Zu", serial) != 1)
+            goto new_serial;
+        mpz_add_ui(serial, serial, 1);
+
+        if ((mpz_sizeinbase(serial, 2) + 7) / 8 > 20)
+            goto new_serial;
+
+        if (gmp_snprintf(serialbuffer,
+                         sizeof(serialbuffer),
+                         "%Zu", serial) >= (int)sizeof(serialbuffer))
+            goto new_serial;
     } else {
-        serial_n = 1;
+new_serial:
+        /* start with random serial number */
+        buffer_len = 20;
+        get_random_serial(serialbuffer, buffer_len);
+        serialbuffer[buffer_len] = 0;
     }
-    *serial_str = g_strdup_printf("%llu", serial_n);
+    *serial_str = g_strdup(serialbuffer);
     write_file(certserial, (unsigned char *)*serial_str, strlen(*serial_str));
     ret = 0;
 
+    mpz_clear(serial);
+
 error:
     unlock_file(lockfd);