summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Sucha <anty.sk+git@gmail.com>2018-05-17 12:31:01 +0200
committerNikos Mavrogiannopoulos <nmav@gnutls.org>2018-05-19 11:55:59 +0200
commitff3c40d9cedcf1531d76d67aa11794a028b499b1 (patch)
tree114219a2bb67ca914efa14617285b2bca70d5c53
parent4d7b69cc5e9e740d7a27ca1e4be359fff8797e3b (diff)
downloadgnutls-ff3c40d9cedcf1531d76d67aa11794a028b499b1.tar.gz
certtool: use larger serial and CRL numbers
Serial/CRL numbers can be up to 20 octets in length as per RFC 5280, so it should be possible to use such numbers as input to certtool. certtool only allowed to specify 63-bit numbers in template file or interactively (even though it generated larger numbers in batch mode by default). This patch allows large numbers to be specified as a hexadecimal string. Parsing of decimal numbers larger than native integers would require adding dependency on libgmp directly to certtool or extending the API exposed by GnuTLS library with parsing functions. Since most tools (including GnuTLS) display serial numbers in hexadecimal, it is not worth the trouble to support large decimal numbers. Default values are unified between batch mode and interactive input and their size is extended. CA/Browser forum recommends CAs to include at least 64 bits of random data in the certificate serial numbers in Baseline Requirements[1] section 7.1, but gnutls adds only 32 bits. Some other implementations generate default serial numbers with more entropy as well, here is the current state as of May 2018: +----------------+-------------------------------+ | Implementation | Random bits in default serial | +----------------+-------------------------------+ | OpenSSL [2] | 159 | | CFSSL [3] | 159 | | wolfSSL [4] | 128 | | GnuTLS | 32 | | Mbed TLS [5] | 0 (defaults to 1) | +----------------+-------------------------------+ The 20 octet field size can fit numbers up to 159 bits since the most significant bit must be zero as numbers in DER encoding are in two's complement and the serial and CRL numbers must be positive. Default serial numbers are extended to full 159 bits allowed by the field size and are completely random, which matches other implementations. CRL numbers have the same size requirements, but also need to be monotonic (RFC 5280, section 5.2.3). That's why timestamp is used in them. The timestamp portion is extended from 31 bits to 39 bits as 31 bits will overflow in year 2038. The rest of the available space up to 159 bits allowed in the 20 octet limit is filled with random bits. Since the new CRL numbers are larger, the requirement for them to be monotonically increasing is preserved when upgrading to a newer version. This does not hold the other way around though, so after using a newer version of certtool to generate a CRL with default number and publishing it, it's not possible to use older version anymore to generate subsequent CRLs. Unfortunately, there is no easy workaround for users of older certtool, since it is not possible to specify CRL numbers greater than 63 bits manually prior to this change. Users intending to downgrade to older version later are advised to specify the CRL numbers in new version of certtool manually with values they are smaller than what would get generated by default in the old version. grep does not recognize CRLF line endings generated in tests using MinGW, so we need to convert those to LF endings for $ in the regex to match test output correctly. datefudge 1.21 that is present in Fedora 26 image trims the timestamp to 32 bits. That bug was fixed in datefudge 1.22 available in the Debian image, so we check if datefudge behaves correctly and skip the test that uses more than 32 bits if datefudge is broken. [1] https://cabforum.org/wp-content/uploads/CA-Browser-Forum-BR-1.4.2.pdf [2] https://github.com/openssl/openssl/blob/6ebb49f3f9c9333611192561979bb799fa1eb76d/apps/apps.c#L1513 [3] https://github.com/cloudflare/cfssl/blob/5d63dbd981b5c408effbb58c442d54761ff94fbd/signer/local/local.go#L295 [4] https://github.com/wolfSSL/wolfssl/blob/d60b16c5b8c19cc61db4a5c3f5e085a7a158cd28/wolfcrypt/src/asn.c#L9791 [5] https://github.com/ARMmbed/mbedtls/blob/84a1107818aaddfd2abe4c5a3478cf84ab2e26b4/programs/x509/cert_write.c#L81 Signed-off-by: Martin Sucha <anty.sk+git@gmail.com>
-rw-r--r--src/certtool-cfg.c294
-rw-r--r--src/certtool-cfg.h1
-rw-r--r--src/certtool-common.h2
-rw-r--r--src/certtool.c4
-rwxr-xr-xtests/cert-tests/crl86
-rw-r--r--tests/cert-tests/data/long-serial.pem25
-rwxr-xr-xtests/cert-tests/template-test19
-rw-r--r--tests/cert-tests/templates/template-long-serial.tmpl99
8 files changed, 465 insertions, 65 deletions
diff --git a/src/certtool-cfg.c b/src/certtool-cfg.c
index 7f552c08c7..577bccf8b7 100644
--- a/src/certtool-cfg.c
+++ b/src/certtool-cfg.c
@@ -39,7 +39,6 @@
#include <intprops.h>
#include <gnutls/crypto.h>
#include <libtasn1.h>
-#include <assert.h>
/* for inet_pton */
#include <sys/types.h>
@@ -127,10 +126,10 @@ static struct cfg_options available_options[] = {
{ .name = "inhibit_anypolicy_skip_certs", .type = OPTION_NUMERIC },
{ .name = "pkcs12_key_name", .type = OPTION_STRING },
{ .name = "proxy_policy_language", .type = OPTION_STRING },
- { .name = "serial", .type = OPTION_NUMERIC },
+ { .name = "serial", .type = OPTION_STRING },
{ .name = "expiration_days", .type = OPTION_NUMERIC },
{ .name = "crl_next_update", .type = OPTION_NUMERIC },
- { .name = "crl_number", .type = OPTION_NUMERIC },
+ { .name = "crl_number", .type = OPTION_STRING },
{ .name = "path_len", .type = OPTION_NUMERIC },
{ .name = "ca", .type = OPTION_BOOLEAN },
{ .name = "honor_crq_extensions", .type = OPTION_BOOLEAN },
@@ -197,7 +196,8 @@ typedef struct _cfg_ctx {
char *revocation_date;
char *this_update_date;
char *next_update_date;
- int64_t serial;
+ uint8_t *serial;
+ unsigned serial_size;
int expiration_days;
int skip_certs; /* from inhibit anypolicy */
int ca;
@@ -218,7 +218,8 @@ typedef struct _cfg_ctx {
int ipsec_ike_key;
char **key_purpose_oids;
int crl_next_update;
- int64_t crl_number;
+ uint8_t *crl_number;
+ unsigned crl_number_size;
int honor_crq_extensions;
char *proxy_policy_language;
char **exts_to_honor;
@@ -233,8 +234,6 @@ void cfg_init(void)
{
memset(&cfg, 0, sizeof(cfg));
cfg.path_len = -1;
- cfg.crl_number = -1;
- cfg.serial = -1;
cfg.skip_certs = -1;
}
@@ -306,12 +305,6 @@ void cfg_init(void)
}
/* READ_NUMERIC only returns a long */
-#define CHECK_LONG_OVERFLOW(x) \
- if (x == LONG_MAX) { \
- fprintf(stderr, "overflow in number\n"); \
- exit(1); \
- }
-
#define READ_NUMERIC(name, s_name) \
val = optionGetValue(pov, name); \
if (val != NULL) \
@@ -335,6 +328,19 @@ void cfg_init(void)
output_size = _output.size; \
}
+#define SERIAL_DECODE(input, output, output_size) \
+ { \
+ gnutls_datum_t _output; \
+ ret = serial_decode(input, &_output); \
+ if (ret < 0) { \
+ fprintf(stderr, "error parsing number: %s\n", input); \
+ exit(1); \
+ } \
+ output = _output.data; \
+ output_size = _output.size; \
+ }
+
+
static int handle_option(const tOptionValue* val)
{
unsigned j;
@@ -518,13 +524,16 @@ int template_parse(const char *template)
cfg.pkcs12_key_name = strdup(val->v.strVal);
- READ_NUMERIC("serial", cfg.serial);
- CHECK_LONG_OVERFLOW(cfg.serial);
+ val = optionGetValue(pov, "serial");
+ if (val != NULL && val->valType == OPARG_TYPE_STRING)
+ SERIAL_DECODE(val->v.strVal, cfg.serial, cfg.serial_size);
READ_NUMERIC("expiration_days", cfg.expiration_days);
READ_NUMERIC("crl_next_update", cfg.crl_next_update);
- READ_NUMERIC("crl_number", cfg.crl_number);
- CHECK_LONG_OVERFLOW(cfg.crl_number);
+
+ val = optionGetValue(pov, "crl_number");
+ if (val != NULL && val->valType == OPARG_TYPE_STRING)
+ SERIAL_DECODE(val->v.strVal, cfg.crl_number, cfg.crl_number_size);
READ_NUMERIC("path_len", cfg.path_len);
@@ -707,6 +716,56 @@ int64_t read_int(const char *input_str)
return read_int_with_default(input_str, 0);
}
+int serial_decode(const char *input, gnutls_datum_t *output)
+{
+ int i;
+ int64_t value;
+ char *endptr;
+ int64_t value_limit;
+ gnutls_datum_t input_datum;
+
+ if (input[0] == '0' && input[1] == 'x') {
+ input_datum.data = (void *) (input + 2);
+ input_datum.size = strlen(input + 2);
+ if (input_datum.size == 0) {
+ return GNUTLS_E_PARSING_ERROR;
+ }
+ return gnutls_hex_decode2(&input_datum, output);
+ }
+
+#if SIZEOF_LONG < 8
+ value = strtol(input, &endptr, 10);
+ value_limit = LONG_MAX;
+#else
+ value = strtoll(input, &endptr, 10);
+ value_limit = LLONG_MAX;
+#endif
+
+ if (*endptr != '\0') {
+ fprintf(stderr, "Trailing garbage: `%s'\n", endptr);
+ return GNUTLS_E_PARSING_ERROR;
+ }
+
+ if (value <= 0 || value >= value_limit) {
+ fprintf(stderr, "Integer out of range: `%s' (min: 1, max: %lu)\n", input, value_limit-1);
+ return GNUTLS_E_PARSING_ERROR;
+ }
+
+ output->size = sizeof(int64_t);
+ output->data = gnutls_malloc(output->size);
+ if (output->data == NULL) {
+ output->size = 0;
+ return GNUTLS_E_MEMORY_ERROR;
+ }
+
+ for (i = output->size - 1; i >= 0; i--) {
+ output->data[i] = value & 0xff;
+ value = value >> 8;
+ }
+
+ return 0;
+}
+
const char *read_str(const char *input_str)
{
static char input[MAX_INPUT_SIZE];
@@ -1453,75 +1512,183 @@ void get_pkcs9_email_crt_set(gnutls_x509_crt_t crt)
static
-void get_rand_int_value(unsigned char* serial, size_t * size, int64_t cfg_val, const char *msg)
+int default_crl_number(unsigned char* serial, size_t *size)
{
struct timespec ts;
- uint32_t default_serial[2];
+ time_t tv_sec_tmp;
+ int i;
/* default format:
- * | 4 b | 4 b | 4b
+ * | 5 b | 4 b | 11b
* | secs | nsecs | rnd |
*/
gettime(&ts);
- if (*size < 12) {
- fprintf(stderr, "error in get_serial()!\n");
+ if (*size < 20) {
+ return GNUTLS_E_SHORT_MEMORY_BUFFER;
+ }
+
+ tv_sec_tmp = ts.tv_sec;
+ for (i = 4; i >= 0; i--) {
+ serial[i] = tv_sec_tmp & 0xff;
+ tv_sec_tmp = tv_sec_tmp >> 8;
+ }
+ serial[5] = (ts.tv_nsec >> 24) & 0xff;
+ serial[6] = (ts.tv_nsec >> 16) & 0xff;
+ serial[7] = (ts.tv_nsec >> 8) & 0xff;
+ serial[8] = (ts.tv_nsec) & 0xff;
+ // crl number must be positive and max 20 octets
+ // so we must zero the most significant bit (with MSB set, the DER encoding
+ // would be 21 octets long). See RFC 5280, section 5.2.3.
+ serial[0] &= 0x7F;
+ *size = 20;
+ return gnutls_rnd(GNUTLS_RND_NONCE, &serial[9], 11);
+}
+
+/**
+ * strip_trailing_newlines:
+ * @str: zero-terminated string that will be modified in-place, must not be NULL
+ *
+ * This function will remove trailing CR or LF characters.
+ **/
+static void strip_trailing_newlines(char *str)
+{
+ char *end;
+
+ end = str;
+ while (*end != '\0') end++;
+ end--;
+ while (end >= str && (*end == '\r' || *end == '\n')) {
+ *end = '\0';
+ end--;
+ }
+}
+
+/**
+ * read_serial_value:
+ * @serial: pointer to buffer with serial number
+ * @size: pointer to actual size of data in buffer
+ * @max_size: capacity of the buffer
+ * @label: user-facing description of the field we are reading value for
+ *
+ * This function will read a serial number from the user. It takes a buffer
+ * that contains a default value that will be displayed to the user and
+ * maximum size of the buffer that it can fill. When the function
+ * returns, either the buffer is not modified to use the default value
+ * or it's contents are changed to reflect the user-entered value.
+ **/
+static
+void read_serial_value(unsigned char *serial, size_t *size, size_t max_size,
+ const char *label)
+{
+ static char input[MAX_INPUT_SIZE];
+ int ret;
+ gnutls_datum_t decoded;
+ gnutls_datum_t serial_datum;
+ gnutls_datum_t encoded_default;
+
+ serial_datum.data = serial;
+ serial_datum.size = *size;
+
+ ret = gnutls_hex_encode2(&serial_datum, &encoded_default);
+ if (ret < 0) {
+ fprintf(stderr, "error encoding default to hex: %d\n", ret);
exit(1);
}
- if (batch && cfg_val < 0) {
- serial[0] = (ts.tv_sec >> 24) & 0xff;
- serial[1] = (ts.tv_sec >> 16) & 0xff;
- serial[2] = (ts.tv_sec >> 8) & 0xff;
- serial[3] = (ts.tv_sec) & 0xff;
- serial[4] = (ts.tv_nsec >> 24) & 0xff;
- serial[5] = (ts.tv_nsec >> 16) & 0xff;
- serial[6] = (ts.tv_nsec >> 8) & 0xff;
- serial[7] = (ts.tv_nsec) & 0xff;
- serial[0] &= 0x7F;
- assert(gnutls_rnd(GNUTLS_RND_NONCE, &serial[8], 4) >= 0);
- *size = 12;
+ fprintf(stderr, "Enter the certificate's %s in decimal "
+ "(123) or hex (0xabcd) (default 0x%s): ",
+ label, encoded_default.data);
+ gnutls_free(encoded_default.data);
+
+ if (fgets(input, sizeof(input), stdin) == NULL)
+ return;
+
+ strip_trailing_newlines(input);
+
+ if (strlen(input) == 0)
return;
+
+ ret = serial_decode(input, &decoded);
+ if (ret < 0) {
+ fprintf(stderr, "error parsing %s: %s\n", label, input);
+ exit(1);
}
- if (batch) {
- default_serial[0] = cfg_val >> 32;
- default_serial[1] = cfg_val;
+ if (decoded.size > max_size) {
+ fprintf(stderr, "maximum %zu octets allowed for %s\n", max_size, label);
+ gnutls_free(decoded.data);
+ exit(1);
+ }
+
+
+ memcpy(serial, decoded.data, decoded.size);
+ *size = decoded.size;
+ gnutls_free(decoded.data);
+}
+
+static
+void get_serial_value(unsigned char *serial, size_t *size,
+ const unsigned char *config, size_t config_size,
+ int (create_default)(unsigned char *, size_t *),
+ const char *label, const char *rfc_section)
+{
+ size_t max_size = *size;
+ int ret;
+
+ if (batch && config != NULL) {
+ if (config_size > max_size) {
+ fprintf(stderr, "maximum %zu octets allowed for %s!\n",
+ max_size, label);
+ exit(1);
+ }
+ memcpy(serial, config, config_size);
+ *size = config_size;
} else {
- uint64_t default_serial_int;
- char tmsg[256];
+ ret = create_default(serial, size);
+ if (ret < 0) {
+ fprintf(stderr, "error generating default %s: %s\n",
+ label, gnutls_strerror(ret));
+ exit(1);
+ }
+ }
-#if SIZEOF_LONG < 8
- default_serial_int = ts.tv_sec;
- snprintf(tmsg, sizeof(tmsg), "%s (default: %" PRIu64"): ", msg, default_serial_int);
-#else
- default_serial_int = (ts.tv_sec << 32) | ts.tv_nsec;
- snprintf(tmsg, sizeof(tmsg), "%s (default: %lu): ", msg, default_serial_int);
-#endif
- default_serial_int = read_int_with_default(tmsg, (long)default_serial_int);
+ if (!batch)
+ read_serial_value(serial, size, max_size, label);
- default_serial[0] = default_serial_int >> 32;
- default_serial[1] = default_serial_int;
+ if ((*size == SERIAL_MAX_BYTES && serial[0] & 0x80) || *size > SERIAL_MAX_BYTES) {
+ // TODO allow creating larger values and convert this to warning?
+ fprintf(stderr, "%s would be encoded in more than 20 bytes,"
+ "see RFC 5280, section %s\n", label, rfc_section);
+ exit(1);
}
+}
- serial[0] = (default_serial[0] >> 24) & 0xff;
- serial[1] = (default_serial[0] >> 16) & 0xff;
- serial[2] = (default_serial[0] >> 8) & 0xff;
- serial[3] = (default_serial[0]) & 0xff;
- serial[4] = (default_serial[1] >> 24) & 0xff;
- serial[5] = (default_serial[1] >> 16) & 0xff;
- serial[6] = (default_serial[1] >> 8) & 0xff;
- serial[7] = (default_serial[1]) & 0xff;
- serial[0] &= 0x7F;
+static
+int default_serial(unsigned char *serial, size_t *size)
+{
+ int ret;
- *size = 8;
+ if (*size < SERIAL_MAX_BYTES)
+ return GNUTLS_E_SHORT_MEMORY_BUFFER;
- return;
+ ret = gnutls_rnd(GNUTLS_RND_NONCE, serial, SERIAL_MAX_BYTES);
+ if (ret < 0)
+ return ret;
+
+ // serial must be positive and max 20 octets
+ // so we must zero the most significant bit (with MSB set, the DER encoding
+ // would be 21 octets long). See RFC 5280, section 4.1.2.2.
+ serial[0] &= 0x7F;
+ *size = SERIAL_MAX_BYTES;
+
+ return 0;
}
-void get_serial(unsigned char* serial, size_t * size)
+void get_serial(unsigned char *serial, size_t *size)
{
- get_rand_int_value(serial, size, cfg.serial, "Enter the certificate's serial number in decimal");
+ get_serial_value(serial, size, cfg.serial, cfg.serial_size,
+ default_serial, "serial number", "4.1.2.2");
}
static
@@ -1655,7 +1822,8 @@ int get_crq_extensions_status(void)
void get_crl_number(unsigned char* serial, size_t * size)
{
- get_rand_int_value(serial, size, cfg.crl_number, "CRL Number");
+ get_serial_value(serial, size, cfg.crl_number, cfg.crl_number_size,
+ default_crl_number, "CRL number", "5.2.3");
}
int get_path_len(void)
diff --git a/src/certtool-cfg.h b/src/certtool-cfg.h
index e3fc56ce99..ea4a2dbc88 100644
--- a/src/certtool-cfg.h
+++ b/src/certtool-cfg.h
@@ -32,6 +32,7 @@ void read_crt_set(gnutls_x509_crt_t crt, const char *input_str,
void read_crq_set(gnutls_x509_crq_t crq, const char *input_str,
const char *oid);
int64_t read_int(const char *input_str);
+int serial_decode(const char *input, gnutls_datum_t *output);
const char *read_str(const char *input_str);
int read_yesno(const char *input_str, int def);
diff --git a/src/certtool-common.h b/src/certtool-common.h
index ed6de4a677..11cbc8a23d 100644
--- a/src/certtool-common.h
+++ b/src/certtool-common.h
@@ -29,6 +29,8 @@
#define TYPE_CRT 1
#define TYPE_CRQ 2
+#define SERIAL_MAX_BYTES 20
+
void certtool_version(void);
#include <gnutls/x509.h>
diff --git a/src/certtool.c b/src/certtool.c
index 6664d21c3a..5bac31f12c 100644
--- a/src/certtool.c
+++ b/src/certtool.c
@@ -397,7 +397,7 @@ generate_certificate(gnutls_privkey_t * ret_key,
{
size_t serial_size;
- unsigned char serial[16];
+ unsigned char serial[SERIAL_MAX_BYTES];
serial_size = sizeof(serial);
@@ -829,7 +829,7 @@ generate_crl(gnutls_x509_crt_t ca_crt, common_info_st * cinfo)
{
size_t serial_size;
- unsigned char serial[16];
+ unsigned char serial[SERIAL_MAX_BYTES];
serial_size = sizeof(serial);
diff --git a/tests/cert-tests/crl b/tests/cert-tests/crl
index e9f1b68e28..8f819feffc 100755
--- a/tests/cert-tests/crl
+++ b/tests/cert-tests/crl
@@ -25,6 +25,7 @@ export TZ="UTC"
srcdir="${srcdir:-.}"
CERTTOOL="${CERTTOOL:-../../src/certtool${EXEEXT}}"
DIFF="${DIFF:-diff}"
+ac_cv_sizeof_unsigned_long_int="${ac_cv_sizeof_unsigned_long_int:-8}"
if ! test -x "${CERTTOOL}"; then
exit 77
@@ -59,6 +60,13 @@ if test "$?" != "0"; then
exit 1
fi
+sed 's/\r$//' <"${INFOFILE}" | grep "CRL Number (not critical): 07$" >/dev/null 2>&1
+if test "$?" != "0"; then
+ echo "CRL generation didn't succeed as expected (2)"
+ grep "CRL Number (not critical):" "${INFOFILE}"
+ exit 1
+fi
+
# check appending a certificate
${VALGRIND} "${CERTTOOL}" --generate-crl --load-ca-privkey "${srcdir}/data/template-test.key" --load-ca-certificate \
@@ -120,6 +128,84 @@ if test "$?" != "0"; then
exit 1
fi
+# Check hex serial number
+echo "crl_next_update = 43" >$TMPFILE
+echo "crl_number = 0x1234567890abcdef1234567890abcdef12345678" >>$TMPFILE
+
+${VALGRIND} "${CERTTOOL}" --generate-crl --load-ca-privkey "${srcdir}/data/template-test.key" --load-ca-certificate \
+ "${srcdir}/data/template-test.pem" --load-certificate "${srcdir}/data/ca-certs.pem" --template \
+ "${TMPFILE}" >${OUTFILE} 2>${INFOFILE}
+rc=$?
+
+# We're done.
+if test "${rc}" != "0"; then
+ echo "CRL hex number failed"
+ exit ${rc}
+fi
+
+sed 's/\r$//' <"${INFOFILE}" | grep "CRL Number (not critical): 1234567890abcdef1234567890abcdef12345678$" >/dev/null 2>&1
+if test "$?" != "0"; then
+ echo "CRL hex number didn't succeed as expected"
+ grep "CRL Number (not critical):" "${INFOFILE}"
+ exit 1
+fi
+
+# Check default CRL number
+echo "crl_next_update = 43" >$TMPFILE
+
+export TZ="UTC"
+
+. ${srcdir}/../scripts/common.sh
+
+check_for_datefudge
+
+datefudge -s "2020-01-20 10:00:00" ${VALGRIND} \
+ "${CERTTOOL}" --generate-crl --load-ca-privkey "${srcdir}/data/template-test.key" \
+ --load-ca-certificate "${srcdir}/data/template-test.pem" \
+ --load-certificate "${srcdir}/data/ca-certs.pem" --template \
+ "${TMPFILE}" >${OUTFILE} 2>${INFOFILE}
+rc=$?
+
+# We're done.
+if test "${rc}" != "0"; then
+ echo "CRL default number failed"
+ exit ${rc}
+fi
+
+sed 's/\r$//' <"${INFOFILE}" | grep "CRL Number (not critical): 5e257a20[0-9a-f]\{30\}$" >/dev/null 2>&1
+if test "$?" != "0"; then
+ echo "CRL default number didn't succeed as expected"
+ grep "CRL Number (not critical):" "${INFOFILE}"
+ exit 1
+fi
+
+# datefudge <= 1.21 (present in Fedora 26 image) trims the date to 32 bits
+datefudge -s "2138-01-20 10:00:00" date | grep "Mon Jan 20 10:00:00 UTC 2138" >/dev/null 2>&1
+datefudge_ok="$?"
+
+if test "${ac_cv_sizeof_unsigned_long_int}" = 8 && test "${datefudge_ok}" = 0;then
+ # we should test that on systems which have 64-bit time_t
+ datefudge -s "2138-01-20 10:00:00" ${VALGRIND} \
+ "${CERTTOOL}" --generate-crl --load-ca-privkey "${srcdir}/data/template-test.key" \
+ --load-ca-certificate "${srcdir}/data/template-test.pem" \
+ --load-certificate "${srcdir}/data/ca-certs.pem" --template \
+ "${TMPFILE}" >${OUTFILE} 2>${INFOFILE}
+ rc=$?
+
+ # We're done.
+ if test "${rc}" != "0"; then
+ echo "CRL default number 2 failed"
+ exit ${rc}
+ fi
+
+ sed 's/\r$//' <"${INFOFILE}" | grep "CRL Number (not critical): 013c1972a0[0-9a-f]\{30\}$" >/dev/null 2>&1
+ if test "$?" != "0"; then
+ echo "CRL default number 2 didn't succeed as expected"
+ grep "CRL Number (not critical):" "${INFOFILE}"
+ exit 1
+ fi
+fi
+
rm -f "${OUTFILE}"
rm -f "${INFOFILE}"
rm -f "${OUTFILE2}"
diff --git a/tests/cert-tests/data/long-serial.pem b/tests/cert-tests/data/long-serial.pem
new file mode 100644
index 0000000000..289b3f31c0
--- /dev/null
+++ b/tests/cert-tests/data/long-serial.pem
@@ -0,0 +1,25 @@
+-----BEGIN CERTIFICATE-----
+MIIENDCCA52gAwIBAgIUEjRWeJCrze/+3LoJh2VDIavN7xIwDQYJKoZIhvcNAQEL
+BQAwgbgxFTATBgNVBAMTDENpbmR5IExhdXBlcjEXMBUGCgmSJomT8ixkAQETB2Ns
+YXVwZXIxFzAVBgNVBAsTDnNsZWVwaW5nIGRlcHQuMRIwEAYDVQQKEwlLb2tvIGlu
+Yy4xDzANBgNVBAgTBkF0dGlraTELMAkGA1UEBhMCR1IxDDAKBgNVBAwTA0RyLjEP
+MA0GA1UEQRMGamFja2FsMRwwGgYJKoZIhvcNAQkBFg1ub25lQG5vbmUub3JnMB4X
+DTA3MDQyMjAwMDAwMFoXDTE0MDUyNTAwMDAwMFowgbgxFTATBgNVBAMTDENpbmR5
+IExhdXBlcjEXMBUGCgmSJomT8ixkAQETB2NsYXVwZXIxFzAVBgNVBAsTDnNsZWVw
+aW5nIGRlcHQuMRIwEAYDVQQKEwlLb2tvIGluYy4xDzANBgNVBAgTBkF0dGlraTEL
+MAkGA1UEBhMCR1IxDDAKBgNVBAwTA0RyLjEPMA0GA1UEQRMGamFja2FsMRwwGgYJ
+KoZIhvcNAQkBFg1ub25lQG5vbmUub3JnMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB
+iQKBgQClxs51Q4S/ZJ4CJxPxA1n3eS2S7XwvUKQD8S15uYaLBX46u0Sqr4TPE5ge
+HEo49zMtep9y1GttJrAxN3AQ+0Lp2J0YZX4ZSfwFlgRogx53hr/t9eUSOxP+Mxic
+Gnodaa9HAmB6H7noz9vINDBRlj2MllwAvGHeCA+xNiF/qQDjBQIDAQABo4IBNzCC
+ATMwDwYDVR0TAQH/BAUwAwEB/zBqBgNVHREEYzBhggx3d3cubm9uZS5vcmeCE3d3
+dy5tb3JldGhhbm9uZS5vcmeCF3d3dy5ldmVubW9yZXRoYW5vbmUub3JnhwTAqAEB
+gQ1ub25lQG5vbmUub3JngQ53aGVyZUBub25lLm9yZzATBgNVHSUEDDAKBggrBgEF
+BQcDCTAPBgNVHQ8BAf8EBQMDBwQAMB0GA1UdDgQWBBRdQK3wzpRAlYt+mZQdklQi
+ynI2XzBvBgNVHR8EaDBmMGSgYqBghh5odHRwOi8vd3d3LmdldGNybC5jcmwvZ2V0
+Y3JsMS+GHmh0dHA6Ly93d3cuZ2V0Y3JsLmNybC9nZXRjcmwyL4YeaHR0cDovL3d3
+dy5nZXRjcmwuY3JsL2dldGNybDMvMA0GCSqGSIb3DQEBCwUAA4GBAB9UxZeBoXQ7
+LChiAWCRxfw7eDkQzprXArfFMcUHQlmX/rOmgmNRtvPOvrdTaECMWV87bhZjm5OY
+x3vFgNLgwEIOd50rPwFlR0imNafpbgwQD35vJ5CEnIt6gFDfViJ+cjsyl0tnV8x+
+mrab87Cjzb0a1Uwdk0P2k7QOhrQVBx1q
+-----END CERTIFICATE-----
diff --git a/tests/cert-tests/template-test b/tests/cert-tests/template-test
index d2a5f9d683..3d32b78c90 100755
--- a/tests/cert-tests/template-test
+++ b/tests/cert-tests/template-test
@@ -239,4 +239,23 @@ fi
rm -f ${TMPFILE}
+# Test generation with larger serial number
+
+datefudge -s "2007-04-22" \
+ "${CERTTOOL}" --generate-self-signed \
+ --load-privkey "${srcdir}/data/template-test.key" \
+ --template "${srcdir}/templates/template-long-serial.tmpl" \
+ --outfile ${TMPFILE} 2>/dev/null
+
+${DIFF} "${srcdir}/data/long-serial.pem" ${TMPFILE} >/dev/null 2>&1
+rc=$?
+
+# We're done.
+if test "${rc}" != "0"; then
+ echo "Test 11 (long serial) failed"
+ exit ${rc}
+fi
+
+rm -f ${TMPFILE}
+
exit 0
diff --git a/tests/cert-tests/templates/template-long-serial.tmpl b/tests/cert-tests/templates/template-long-serial.tmpl
new file mode 100644
index 0000000000..0352586490
--- /dev/null
+++ b/tests/cert-tests/templates/template-long-serial.tmpl
@@ -0,0 +1,99 @@
+# X.509 Certificate options
+#
+# DN options
+
+# The organization of the subject.
+organization = "Koko inc."
+
+# The organizational unit of the subject.
+unit = "sleeping dept."
+
+# The locality of the subject.
+# locality =
+
+# The state of the certificate owner.
+state = "Attiki"
+
+# The country of the subject. Two letter code.
+country = GR
+
+# The common name of the certificate owner.
+cn = "Cindy Lauper"
+
+# A user id of the certificate owner.
+uid = "clauper"
+
+# If the supported DN OIDs are not adequate you can set
+# any OID here.
+# For example set the X.520 Title and the X.520 Pseudonym
+# by using OID and string pairs.
+dn_oid = 2.5.4.12 Dr.
+dn_oid = 2.5.4.65 jackal
+
+# This is deprecated and should not be used in new
+# certificates.
+pkcs9_email = "none@none.org"
+
+# The serial number of the certificate
+serial = 0x1234567890abcdeffedcba0987654321abcdef12
+
+# In how many days, counting from today, this certificate will expire.
+expiration_days = 2590
+
+# X.509 v3 extensions
+
+# A dnsname in case of a WWW server.
+dns_name = "www.none.org"
+dns_name = "www.morethanone.org"
+
+# An IP address in case of a server.
+ip_address = "192.168.1.1"
+
+dns_name = "www.evenmorethanone.org"
+
+# An email in case of a person
+email = "none@none.org"
+
+# An URL that has CRLs (certificate revocation lists)
+# available. Needed in CA certificates.
+crl_dist_points = "http://www.getcrl.crl/getcrl1/"
+crl_dist_points = "http://www.getcrl.crl/getcrl2/"
+crl_dist_points = "http://www.getcrl.crl/getcrl3/"
+
+email = "where@none.org"
+
+# Whether this is a CA certificate or not
+ca
+
+# Whether this certificate will be used for a TLS client
+#tls_www_client
+
+# Whether this certificate will be used for a TLS server
+#tls_www_server
+
+# Whether this certificate will be used to sign data (needed
+# in TLS DHE ciphersuites).
+signing_key
+
+# Whether this certificate will be used to encrypt data (needed
+# in TLS RSA ciphersuites). Note that it is preferred to use different
+# keys for encryption and signing.
+#encryption_key
+
+# Whether this key will be used to sign other certificates.
+cert_signing_key
+
+# Whether this key will be used to sign CRLs.
+#crl_signing_key
+
+# Whether this key will be used to sign code.
+#code_signing_key
+
+# Whether this key will be used to sign OCSP data.
+ocsp_signing_key
+
+# Whether this key will be used for time stamping.
+#time_stamping_key
+
+# Whether this key will be used for IPsec IKE operations.
+#ipsec_ike_key