swtpm_cert: Add support for serial numbers up to 20 bytes long

x509 certificate serial numbers can be up to 20 bytes long.
Support this via gmp library.

A serial number must not have its most significant bit set, which
would indicate a negative number. If this is the case, insert '0'
as the first byte.

Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>
diff --git a/src/swtpm_cert/Makefile.am b/src/swtpm_cert/Makefile.am
index 8d76180..281f785 100644
--- a/src/swtpm_cert/Makefile.am
+++ b/src/swtpm_cert/Makefile.am
@@ -23,7 +23,8 @@
 	-I$(top_builddir)/include \
 	-I$(top_srcdir)/include \
 	$(MY_CFLAGS) \
-	$(CFLAGS)
+	$(CFLAGS) \
+	$(GMP_CFLAGS)
 
 swtpm_cert_LDFLAGS = \
 	$(MY_LDFLAGS) \
@@ -33,7 +34,8 @@
 
 swtpm_cert_LDADD = \
 	$(LIBTASN1_LIBS) \
-	$(GNUTLS_LIBS)
+	$(GNUTLS_LIBS) \
+	$(GMP_LIBS)
 
 tpm_asn1.h : tpm.asn
 	asn1Parser -o $@ $^ 
diff --git a/src/swtpm_cert/ek-cert.c b/src/swtpm_cert/ek-cert.c
index fa14e70..6be44da 100644
--- a/src/swtpm_cert/ek-cert.c
+++ b/src/swtpm_cert/ek-cert.c
@@ -58,6 +58,8 @@
 #include <gnutls/abstract.h>
 #include <gnutls/gnutls.h>
 
+#include <gmp.h>
+
 #include "sys_dependencies.h"
 #include "tpm_asn1.h"
 #include "swtpm.h"
@@ -1017,7 +1019,7 @@
     const char *ecc_curveid = NULL;
     gnutls_datum_t datum = { NULL, 0},  out = { NULL, 0};
     gnutls_digest_algorithm_t hashAlgo = GNUTLS_DIG_SHA1;
-    unsigned long long serial = 1;
+    mpz_t serial;
     time_t now;
     int err;
     int cert_file_fd;
@@ -1027,7 +1029,8 @@
     time_t exp_time;
     char *sigkeypass = NULL;
     char *parentkeypass = NULL;
-    uint64_t ser_number;
+    unsigned char ser_number[21];
+    size_t ser_number_len;
     long int exponent = 0x10001;
     bool write_pem = false;
     uint8_t id[512];
@@ -1085,7 +1088,9 @@
         {NULL, 0, NULL, 0},
     };
     int opt, option_index = 0;
-    char *endptr;
+
+    mpz_init(serial);
+    mpz_set_ui(serial, 1);
 
 #ifdef __NetBSD__
     while ((opt = getopt_long(argc, argv,
@@ -1175,9 +1180,7 @@
             days = atoi(optarg);
             break;
         case 'r': /* --serial */
-            errno = 0;
-            serial = strtoull(optarg, &endptr, 10);
-            if (errno != 0 || optarg == endptr || *endptr != 0) {
+            if (gmp_sscanf(optarg, "%Zd", serial) != 1) {
                 fprintf(stderr, "Serial number is invalid.\n");
                 goto cleanup;
             }
@@ -1261,7 +1264,21 @@
     if (flags & CERT_TYPE_TPM2_F)
         hashAlgo = GNUTLS_DIG_SHA256;
 
-    ser_number = htobe64(serial);
+    if ((mpz_sizeinbase(serial, 2) + 7) / 8 > sizeof(ser_number) - 1) {
+        fprintf(stderr, "Serial number is too large.\n");
+        goto cleanup;
+    }
+    mpz_export(ser_number, &ser_number_len, 1, 1, 1, 0, serial);
+    if (ser_number_len > sizeof(ser_number) - 1) {
+        fprintf(stderr, "Serial number is too large.\n");
+        goto cleanup;
+    }
+    /* serial number's highest bit must not indicate negative number */
+    if (ser_number[0] & 0x7f) {
+        memmove(&ser_number[1], &ser_number[0], ser_number_len);
+        ser_number[0] = 0;
+        ser_number_len++;
+    }
 
     if (modulus_bin && (ecc_x_bin || ecc_y_bin)) {
         fprintf(stderr, "RSA modulus and ECC parameters cannot both be "
@@ -1448,7 +1465,7 @@
                        gnutls_strerror(err))
 
     /* 3.5.2 Serial Number */
-    err = gnutls_x509_crt_set_serial(crt, &ser_number, sizeof(ser_number));
+    err = gnutls_x509_crt_set_serial(crt, ser_number, ser_number_len);
     CHECK_GNUTLS_ERROR(err, "Could not set serial on CRT: %s\n",
                        gnutls_strerror(err))
 
@@ -1745,6 +1762,8 @@
     ret = 0;
 
 cleanup:
+    mpz_clear(serial);
+
     gnutls_free(out.data);
 
     gnutls_x509_crt_deinit(crt);