]> git.ipfire.org Git - thirdparty/gnutls.git/commitdiff
x509 CT: implement new public API
authorAnder Juaristi <a@juaristi.eus>
Mon, 15 Nov 2021 19:03:12 +0000 (20:03 +0100)
committerAnder Juaristi <a@juaristi.eus>
Sat, 4 Dec 2021 16:23:46 +0000 (17:23 +0100)
This commit implements import and export functions for the X.509
Certificate Transparency Signed Certificate Timestamp (SCT) extension
(RFC 6962).

A new constant GNUTLS_X509EXT_OID_CT_SCT is introduced
with the value "1.3.6.1.4.1.11129.2.4.2".

The following new public API functions are introduced:

    - gnutls_x509_ext_ct_scts_init
    - gnutls_x509_ext_ct_scts_deinit
    - gnutls_x509_ext_ct_import_scts
    - gnutls_x509_ext_ct_export_scts
    - gnutls_x509_ct_sct_get_version
    - gnutls_x509_ct_sct_get

Signed-off-by: Ander Juaristi <a@juaristi.eus>
lib/includes/gnutls/x509-ext.h
lib/includes/gnutls/x509.h
lib/x509/output.c
lib/x509/x509_ext.c

index b288c31a19d629da005e1eff0bc8e511aee25745..459c1e8b14045526d5d05b18f2d138af830b9c5e 100644 (file)
@@ -199,6 +199,22 @@ int gnutls_x509_ext_export_tlsfeatures(gnutls_x509_tlsfeatures_t f,
 
 int gnutls_x509_tlsfeatures_add(gnutls_x509_tlsfeatures_t f, unsigned int feature);
 
+typedef struct gnutls_x509_ct_scts_st *gnutls_x509_ct_scts_t;
+
+int gnutls_x509_ext_ct_scts_init(gnutls_x509_ct_scts_t * scts);
+void gnutls_x509_ext_ct_scts_deinit(gnutls_x509_ct_scts_t scts);
+int gnutls_x509_ext_ct_import_scts(const gnutls_datum_t * ext,
+                                  gnutls_x509_ct_scts_t scts, unsigned int flags);
+int gnutls_x509_ext_ct_export_scts(const gnutls_x509_ct_scts_t scts, gnutls_datum_t * ext);
+int gnutls_x509_ct_sct_get_version(const gnutls_x509_ct_scts_t scts, unsigned idx,
+                                  unsigned int *version_out);
+int gnutls_x509_ct_sct_get(const gnutls_x509_ct_scts_t scts,
+                          unsigned idx,
+                          time_t *timestamp,
+                          gnutls_datum_t *logid,
+                          gnutls_sign_algorithm_t *sigalg,
+                          gnutls_datum_t *signature);
+
 /* *INDENT-OFF* */
 #ifdef __cplusplus
 }
index 7953a30460401f113f58567541509bb976c59413..5ac601a0a990e9b3a6c6073a86f3119febc6b4fe 100644 (file)
@@ -107,6 +107,7 @@ extern "C" {
 #define GNUTLS_X509EXT_OID_AUTHORITY_INFO_ACCESS "1.3.6.1.5.5.7.1.1"
 #define GNUTLS_X509EXT_OID_PROXY_CRT_INFO "1.3.6.1.5.5.7.1.14"
 #define GNUTLS_X509EXT_OID_TLSFEATURES "1.3.6.1.5.5.7.1.24"
+#define GNUTLS_X509EXT_OID_CT_SCT_V1 "1.3.6.1.4.1.11129.2.4.2"
 
 #define GNUTLS_X509_OID_POLICY_ANY "2.5.29.54"
 
index 1e58c3ca922825a4c87d5265f7141beb03abe0de..30ca7f4664fc455ea122d92bd5ebab4614cc0b52 100644 (file)
@@ -430,6 +430,99 @@ static void print_ski(gnutls_buffer_st * str, gnutls_datum_t *der)
        gnutls_free(id.data);
 }
 
+static void print_time(gnutls_buffer_st *str, time_t timestamp)
+{
+       char s[42];
+       size_t max = sizeof(s);
+       struct tm t;
+
+       if (gmtime_r(&timestamp, &t) == NULL) {
+               addf(str, "error: gmtime_r (%lu)\n", timestamp);
+               return;
+       }
+
+       if (strftime(s, max, "%a, %b %d %H:%M:%S UTC %Y", &t) == 0)
+               addf(str, "error: strftime (%lu)\n", timestamp);
+       else
+               addf(str, "%s\n", s);
+}
+
+static void print_scts(gnutls_buffer_st * str, const gnutls_datum_t *der,
+                      const char *prefix)
+{
+       int retval;
+       unsigned int version;
+       time_t timestamp;
+       gnutls_datum_t logid = { NULL, 0 }, sig = { NULL, 0 };
+       gnutls_sign_algorithm_t sigalg;
+       gnutls_x509_ct_scts_t scts;
+
+       retval = gnutls_x509_ext_ct_scts_init(&scts);
+       if (retval < 0) {
+               addf(str, "error: gnutls_x509_ext_ct_scts_init(): %s\n",
+                    gnutls_strerror(retval));
+               return;
+       }
+
+       retval = gnutls_x509_ext_ct_import_scts(der, scts, 0);
+       if (retval < 0) {
+               addf(str, "error: gnutls_x509_ext_ct_import_scts(): %s\n",
+                    gnutls_strerror(retval));
+               goto cleanup;
+       }
+
+       for (int i = 0;; i++) {
+               retval = gnutls_x509_ct_sct_get_version(scts, i, &version);
+               if (retval == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
+                       break;
+
+               addf(str, _("%s\t\t\tSigned Certificate Timestamp %d:\n"),
+                    prefix, (i+1));
+
+               if (version != 1) {
+                       addf(str, _("%s\t\t\t\tVersion: %d (unknown SCT version)\n"),
+                            prefix, version);
+                       continue;
+               }
+
+               retval = gnutls_x509_ct_sct_get(scts, i,
+                                               &timestamp,
+                                               &logid,
+                                               &sigalg, &sig);
+               if (retval < 0) {
+                       addf(str, "error: could not get SCT info: %s\n",
+                            gnutls_strerror(retval));
+                       break;
+               }
+
+               addf(str, _("%s\t\t\t\tVersion: %d\n"),
+                    prefix, version);
+               addf(str, _("%s\t\t\t\tLog ID: "), prefix);
+               _gnutls_buffer_hexprint(str, logid.data, logid.size);
+               addf(str, "\n");
+               addf(str, _("%s\t\t\t\tTime: "), prefix);
+               print_time(str, timestamp);
+               addf(str, _("%s\t\t\t\tExtensions: none\n"), /* there are no extensions defined for v1 */
+                    prefix);
+               addf(str, _("%s\t\t\t\tSignature algorithm: %s\n"),
+                    prefix, gnutls_sign_get_name(sigalg));
+               addf(str, _("%s\t\t\t\tSignature: "), prefix);
+               _gnutls_buffer_hexprint(str, sig.data, sig.size);
+               addf(str, "\n");
+
+               _gnutls_free_datum(&sig);
+               _gnutls_free_datum(&logid);
+               sig.data = NULL;
+               logid.data = NULL;
+       }
+
+cleanup:
+       _gnutls_free_datum(&sig);
+       _gnutls_free_datum(&logid);
+       gnutls_x509_ext_ct_scts_deinit(scts);
+}
+
+
 #define TYPE_CRT 2
 #define TYPE_CRQ 3
 
@@ -1226,6 +1319,11 @@ static void print_extension(gnutls_buffer_st * str, const char *prefix,
                     critical ? _("critical") : _("not critical"));
 
                print_aia(str, der);
+       } else if (strcmp(oid, GNUTLS_X509EXT_OID_CT_SCT_V1) == 0) {
+               addf(str, _("%s\t\tCT Precertificate SCTs (%s):\n"),
+                    prefix, critical ? _("critical") : _("not critical"));
+
+               print_scts(str, der, prefix);
        } else if (strcmp(oid, "2.5.29.30") == 0) {
                if (idx->nc) {
                        addf(str,
index 41b38bd85b6bb23d9d87c9bf7961f41ffd9aeb85..eb131c24c05210d9a924b4113a9d50b720e43004 100644 (file)
@@ -3519,3 +3519,542 @@ int gnutls_x509_tlsfeatures_add(gnutls_x509_tlsfeatures_t f, unsigned int featur
 
        return 0;
 }
+
+#define SCT_V1_LOGID_SIZE 32
+struct ct_sct_st {
+       int version;
+       uint8_t logid[SCT_V1_LOGID_SIZE];
+       uint64_t timestamp;
+       gnutls_sign_algorithm_t sigalg;
+       gnutls_datum_t signature;
+};
+
+struct gnutls_x509_ct_scts_st {
+       struct ct_sct_st *scts;
+       size_t size;
+};
+
+static void _gnutls_free_scts(struct gnutls_x509_ct_scts_st *scts)
+{
+       for (unsigned i = 0; i < scts->size; i++)
+               _gnutls_free_datum(&scts->scts[i].signature);
+       gnutls_free(scts->scts);
+       scts->size = 0;
+}
+
+/**
+ * gnutls_x509_ext_ct_scts_init:
+ * @scts: The SCT list
+ *
+ * This function will initialize a Certificate Transparency SCT list.
+ *
+ * Returns: %GNUTLS_E_SUCCESS (0) on success, otherwise a negative error value.
+ **/
+int gnutls_x509_ext_ct_scts_init(gnutls_x509_ct_scts_t * scts)
+{
+       *scts = gnutls_calloc(1, sizeof(struct gnutls_x509_ct_scts_st));
+       if (*scts == NULL)
+               return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
+       return 0;
+}
+
+/**
+ * gnutls_x509_ext_ct_scts_deinit:
+ * @scts: The SCT list
+ *
+ * This function will deinitialize a Certificate Transparency SCT list.
+ **/
+void gnutls_x509_ext_ct_scts_deinit(gnutls_x509_ct_scts_t scts)
+{
+       _gnutls_free_scts(scts);
+       gnutls_free(scts);
+}
+
+struct sct_sign_algorithm_st {
+       uint8_t codepoint[2];
+       gnutls_sign_algorithm_t sign_algo;
+};
+
+static const struct sct_sign_algorithm_st algos[] = {
+       {
+               .codepoint = { 0x01, 0x01 },
+               .sign_algo = GNUTLS_SIGN_RSA_MD5
+       },
+       {
+               .codepoint = { 0x02, 0x01 },
+               .sign_algo = GNUTLS_SIGN_RSA_SHA1
+       },
+       {
+               .codepoint = { 0x03, 0x01 },
+               .sign_algo = GNUTLS_SIGN_RSA_SHA224
+       },
+       {
+               .codepoint = { 0x04, 0x01 },
+               .sign_algo = GNUTLS_SIGN_RSA_SHA256
+       },
+       {
+               .codepoint = { 0x05, 0x01 },
+               .sign_algo = GNUTLS_SIGN_RSA_SHA384
+       },
+       {
+               .codepoint = { 0x06, 0x01 },
+               .sign_algo = GNUTLS_SIGN_RSA_SHA512,
+       },
+       {
+               .codepoint = { 0x02, 0x02 },
+               .sign_algo = GNUTLS_SIGN_DSA_SHA1
+       },
+       {
+               .codepoint = { 0x03, 0x02 },
+               .sign_algo = GNUTLS_SIGN_DSA_SHA224
+       },
+       {
+               .codepoint = { 0x04, 0x02 },
+               .sign_algo = GNUTLS_SIGN_DSA_SHA256
+       },
+       {
+               .codepoint = { 0x05, 0x02 },
+               .sign_algo = GNUTLS_SIGN_DSA_SHA384
+       },
+       {
+               .codepoint = { 0x06, 0x02 },
+               .sign_algo = GNUTLS_SIGN_DSA_SHA512,
+       },
+       {
+               .codepoint = { 0x02, 0x03 },
+               .sign_algo = GNUTLS_SIGN_ECDSA_SHA1
+       },
+       {
+               .codepoint = { 0x03, 0x03 },
+               .sign_algo = GNUTLS_SIGN_ECDSA_SHA224
+       },
+       {
+               .codepoint = { 0x04, 0x03 },
+               .sign_algo = GNUTLS_SIGN_ECDSA_SHA256
+       },
+       {
+               .codepoint = { 0x05, 0x03 },
+               .sign_algo = GNUTLS_SIGN_ECDSA_SHA384
+       },
+       {
+               .codepoint = { 0x06, 0x03 },
+               .sign_algo = GNUTLS_SIGN_ECDSA_SHA512,
+       }
+};
+
+static gnutls_sign_algorithm_t get_sigalg(uint8_t hash_algo, uint8_t sig_algo)
+{
+       const struct sct_sign_algorithm_st *algo;
+       unsigned i, num_algos = sizeof(algos) / sizeof(algos[0]);
+
+       if (hash_algo == 0 || sig_algo == 0)
+               return GNUTLS_SIGN_UNKNOWN;
+
+       for (i = 0; i < num_algos; i++) {
+               algo = &algos[i];
+               if (algo->codepoint[0] == hash_algo && algo->codepoint[1] == sig_algo)
+                       break;
+       }
+
+       if (i == num_algos)
+               return GNUTLS_SIGN_UNKNOWN;
+
+       return algo->sign_algo;
+}
+
+static int write_sigalg(gnutls_sign_algorithm_t sigalg, uint8_t out[])
+{
+       const struct sct_sign_algorithm_st *algo;
+       unsigned i, num_algos = sizeof(algos) / sizeof(algos[0]);
+
+       for (i = 0; i < num_algos; i++) {
+               algo = &algos[i];
+               if (algo->sign_algo == sigalg)
+                       break;
+       }
+
+       if (i == num_algos)
+               return GNUTLS_E_UNSUPPORTED_SIGNATURE_ALGORITHM;
+
+       out[0] = algo->codepoint[0];
+       out[1] = algo->codepoint[1];
+       return 0;
+}
+
+static int _gnutls_parse_ct_sct(uint8_t *ptr, uint16_t length,
+                               struct ct_sct_st *sct)
+{
+       uint16_t sig_length;
+       uint8_t hash_algo, sig_algo;
+
+       sct->signature.size = 0;
+       sct->signature.data = NULL;
+
+       DECR_LENGTH_RET(length, 1, GNUTLS_E_PREMATURE_TERMINATION);
+       sct->version = (int) *ptr;
+       ptr++;
+
+       /* LogID
+        * In version 1, it has a fixed length of 32 bytes.
+        */
+       DECR_LENGTH_RET(length, SCT_V1_LOGID_SIZE, GNUTLS_E_PREMATURE_TERMINATION);
+       memcpy(sct->logid, ptr, SCT_V1_LOGID_SIZE);
+       ptr += SCT_V1_LOGID_SIZE;
+
+       /* Timestamp */
+       DECR_LENGTH_RET(length, sizeof(uint64_t), GNUTLS_E_PREMATURE_TERMINATION);
+       sct->timestamp = (uint64_t) _gnutls_read_uint64(ptr);
+       ptr += sizeof(uint64_t);
+
+       /*
+        * There are no extensions defined in SCT v1.
+        * Check that there are actually no extensions - the following two bytes should be zero.
+        */
+       DECR_LENGTH_RET(length, 2, GNUTLS_E_PREMATURE_TERMINATION);
+       if (*ptr != 0 || *(ptr+1) != 0)
+               return gnutls_assert_val(GNUTLS_E_UNEXPECTED_EXTENSIONS_LENGTH);
+       ptr += 2;
+
+       /*
+        * Hash and signature algorithms, modeled after
+        * SignatureAndHashAlgorithm structure, as defined in
+        * RFC 5246, section 7.4.1.4.1.
+        * We take both values separately (hash and signature),
+        * and return them as a gnutls_sign_algorithm_t enum value.
+        */
+       DECR_LENGTH_RET(length, 2, GNUTLS_E_PREMATURE_TERMINATION);
+       hash_algo = *ptr++;
+       sig_algo = *ptr++;
+
+       sct->sigalg = get_sigalg(hash_algo, sig_algo);
+       if (sct->sigalg == GNUTLS_SIGN_UNKNOWN)
+               return gnutls_assert_val(GNUTLS_E_UNSUPPORTED_SIGNATURE_ALGORITHM);
+
+       /* Signature, length and content */
+       DECR_LENGTH_RET(length, sizeof(uint16_t), GNUTLS_E_PREMATURE_TERMINATION);
+       sig_length = _gnutls_read_uint16(ptr);
+       ptr += sizeof(uint16_t);
+       if (sig_length == 0)
+               return gnutls_assert_val(GNUTLS_E_PREMATURE_TERMINATION);
+
+       /* Remaining length should be sig_length at this point.
+        * If not, that means there is more data than what the length field said it was,
+        * and hence we must treat this as an error. */
+       if (length != sig_length)
+               return gnutls_assert_val(GNUTLS_E_ASN1_DER_OVERFLOW);
+
+       if (_gnutls_set_datum(&sct->signature, ptr, sig_length) < 0)
+               return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
+
+       return 0;
+}
+
+static int _gnutls_ct_sct_add(struct ct_sct_st *sct,
+                             struct ct_sct_st **scts, size_t *size)
+{
+       struct ct_sct_st *new_scts;
+
+       new_scts = _gnutls_reallocarray(*scts, *size + 1, sizeof(struct ct_sct_st));
+       if (new_scts == NULL)
+               return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
+
+       memcpy(&new_scts[*size], sct, sizeof(struct ct_sct_st));
+       (*size)++;
+       *scts = new_scts;
+
+       return 0;
+}
+
+static int _gnutls_export_ct_v1_sct(gnutls_buffer_st *buf,
+                                   const struct ct_sct_st *sct, size_t base_size)
+{
+       int ret;
+       uint8_t tstamp_out[8], sigalg[2];
+       /* There are no extensions defined for v1 */
+       const uint8_t extensions[2] = { 0x00, 0x00 };
+
+       /*
+        * The caller of this function will allocate 'out' so that it will always have enough room
+        * to write the requested SCT. Hence we don't need to bounds-check 'out' here.
+        * Currently this function is only called at gnutls_x509_ext_ct_export_scts().
+        */
+
+       /* Length field */
+       if ((ret = _gnutls_buffer_append_prefix(buf, 16,
+                                               base_size + sct->signature.size - sizeof(uint16_t))) < 0)
+               return gnutls_assert_val(ret);
+
+       /* Version */
+       if ((ret = _gnutls_buffer_append_data(buf,
+                                             &sct->version, sizeof(uint8_t))) < 0)
+               return gnutls_assert_val(ret);
+
+       /* Log ID - has a fixed 32-byte size in version 1 */
+       if ((ret = _gnutls_buffer_append_data(buf,
+                                             sct->logid, SCT_V1_LOGID_SIZE)) < 0)
+               return gnutls_assert_val(ret);
+
+       /* Timestamp */
+       _gnutls_write_uint64(sct->timestamp, tstamp_out);
+       if ((ret = _gnutls_buffer_append_data(buf,
+                                             tstamp_out, sizeof(tstamp_out))) < 0)
+               return gnutls_assert_val(ret);
+
+       /* Extensions */
+       if ((ret = _gnutls_buffer_append_data(buf,
+                                             extensions, sizeof(extensions))) < 0)
+               return gnutls_assert_val(ret);
+
+       /* Hash and signature algorithms */
+       if ((ret = write_sigalg(sct->sigalg, sigalg)) < 0)
+               return gnutls_assert_val(ret);
+
+       if ((ret = _gnutls_buffer_append_data(buf,
+                                             sigalg, sizeof(sigalg))) < 0)
+               return gnutls_assert_val(ret);
+
+       /* Signature */
+       if ((ret = _gnutls_buffer_append_data_prefix(buf, 16,
+                                                    sct->signature.data, sct->signature.size)) < 0)
+               return gnutls_assert_val(ret);
+
+       return 0;
+}
+
+/**
+ * gnutls_x509_ext_ct_import_scts:
+ * @ext: a DER-encoded extension
+ * @scts: The SCT list
+ * @flags: should be zero
+ *
+ * This function will read a SignedCertificateTimestampList structure
+ * from the DER data of the X.509 Certificate Transparency SCT extension
+ * (OID 1.3.6.1.4.1.11129.2.4.2).
+ *
+ * The list of SCTs (Signed Certificate Timestamps) is placed on @scts,
+ * which must be previously initialized with gnutls_x509_ext_ct_scts_init().
+ *
+ * Returns: %GNUTLS_E_SUCCESS (0) on success or a negative error value.
+ **/
+int gnutls_x509_ext_ct_import_scts(const gnutls_datum_t *ext, gnutls_x509_ct_scts_t scts,
+                                  unsigned int flags)
+{
+       int retval;
+       uint8_t *ptr;
+       uint16_t length, sct_length;
+       struct ct_sct_st sct;
+       gnutls_datum_t scts_content;
+
+       if (flags != 0)
+               return gnutls_assert_val(GNUTLS_E_UNIMPLEMENTED_FEATURE);
+
+       retval =
+               _gnutls_x509_decode_string(ASN1_ETYPE_OCTET_STRING,
+                                          ext->data, ext->size, &scts_content,
+                                          0);
+       if (retval < 0)
+               return gnutls_assert_val(retval);
+
+       if (scts_content.size < 2) {
+               gnutls_free(scts_content.data);
+               return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE;
+       }
+
+       length = _gnutls_read_uint16(scts_content.data);
+       if (length < 4) {
+               gnutls_free(scts_content.data);
+               return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE;
+       }
+
+       ptr = &scts_content.data[2];
+       while (length > 0) {
+               if (length < 2)
+                       break;
+
+               sct_length = _gnutls_read_uint16(ptr);
+               if (sct_length == 0 || sct_length > length)
+                       break;
+
+               ptr += sizeof(uint16_t);
+               length -= sizeof(uint16_t);
+
+               /*
+                * _gnutls_parse_ct_sct() will try to read exactly sct_length bytes,
+                * returning an error if it can't
+                */
+               if (_gnutls_parse_ct_sct(ptr, sct_length, &sct) < 0)
+                       break;
+               if (_gnutls_ct_sct_add(&sct, &scts->scts, &scts->size) < 0)
+                       break;
+
+               ptr += sct_length;
+               length -= sct_length;
+       }
+
+       _gnutls_free_datum(&scts_content);
+
+       if (length > 0) {
+               gnutls_assert();
+               _gnutls_free_scts(scts);
+               return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE;
+       }
+
+       return GNUTLS_E_SUCCESS;
+}
+
+/**
+ * gnutls_x509_ext_ct_export_scts:
+ * @scts: An initialized SCT list
+ * @ext: The DER-encoded extension data; must be freed with gnutls_free()
+ *
+ * This function will convert the provided list of SCTs to a DER-encoded
+ * SignedCertificateTimestampList extension (1.3.6.1.4.1.11129.2.4.2).
+ * The output data in @ext will be allocated using gnutls_malloc().
+ *
+ * Returns: %GNUTLS_E_SUCCESS (0) on success or a negative error value.
+ **/
+int gnutls_x509_ext_ct_export_scts(const gnutls_x509_ct_scts_t scts, gnutls_datum_t *ext)
+{
+       int ret;
+       size_t ttl_size;
+       gnutls_buffer_st buf;
+
+       const size_t base_size = sizeof(uint16_t)  /* length of the whole part */
+                                + 1  /* version */
+                                + SCT_V1_LOGID_SIZE  /* Log ID */
+                                + sizeof(uint64_t)  /* Timestamp */
+                                + 2  /* Extensions */
+                                + 2  /* hash and signature algorithms */
+                                + sizeof(uint16_t);  /* Signature length */
+
+       ttl_size = 0;
+       for (unsigned i = 0; i < scts->size; i++)
+               ttl_size += base_size + scts->scts[i].signature.size;
+
+       _gnutls_buffer_init(&buf);
+
+       /* Start with the length of the whole string */
+       _gnutls_buffer_append_prefix(&buf, 16, ttl_size);
+
+       for (unsigned i = 0; i < scts->size; i++) {
+               if ((ret = _gnutls_export_ct_v1_sct(&buf,
+                                                   &scts->scts[i], base_size)) < 0) {
+                       gnutls_assert();
+                       goto cleanup;
+               }
+       }
+
+       /* DER-encode the whole thing as an opaque OCTET STRING, as the spec mandates */
+       ret = _gnutls_x509_encode_string(
+               ASN1_ETYPE_OCTET_STRING,
+               buf.data, buf.length,
+               ext);
+       if (ret < 0) {
+               gnutls_assert();
+               goto cleanup;
+       }
+
+       ret = GNUTLS_E_SUCCESS;
+
+cleanup:
+       _gnutls_buffer_clear(&buf);
+       return ret;
+}
+
+/**
+ * gnutls_x509_ct_sct_get_version:
+ * @scts: A list of SCTs
+ * @idx: The index of the target SCT in the list
+ * @version_out: The version of the target SCT.
+ *
+ * This function obtains the version of the SCT at the given position
+ * in the SCT list.
+ *
+ * The version of that SCT will be placed on @version_out.
+ *
+ * Return : %GNUTLS_E_SUCCESS (0) is returned on success,
+ *   %GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE if @idx exceeds the number of SCTs in the list
+ *   and %GNUTLS_E_INVALID_REQUEST if the SCT's version is different than 1, as that's currently
+ *   the only defined version.
+ **/
+int gnutls_x509_ct_sct_get_version(gnutls_x509_ct_scts_t scts, unsigned idx,
+                                  unsigned int *version_out)
+{
+       int version;
+
+       if (idx >= scts->size)
+               return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE;
+
+       /*
+        * Currently, only version 1 SCTs are defined (RFC 6962).
+        * A version 1 SCT has actually the value 0 in the 'version' field.
+        */
+       version = scts->scts[idx].version;
+       if (version != 0 || version_out == NULL)
+               return GNUTLS_E_INVALID_REQUEST;
+
+       *version_out = 1;
+       return GNUTLS_E_SUCCESS;
+}
+
+/**
+ * gnutls_x509_ct_sct_get:
+ * @scts: A list of SCTs
+ * @idx: The index of the target SCT in the list
+ * @timestamp: The timestamp of the SCT
+ * @logid: The LogID field of the SCT; must be freed with gnutls_free()
+ * @sigalg: The signature algorithm
+ * @signature: The signature of the SCT; must be freed with gnutls_free()
+ *
+ * This function will return a specific SCT (Signed Certificate Timestamp)
+ * stored in the SCT list @scts.
+ *
+ * The datums holding the SCT's LogId and signature will be allocated
+ * using gnutls_malloc().
+ *
+ * Returns: %GNUTLS_E_SUCCESS (0) will be returned on success,
+ * %GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE if @idx exceeds the number of SCTs in the list
+ * or a negative error value.
+ **/
+int gnutls_x509_ct_sct_get(const gnutls_x509_ct_scts_t scts, unsigned idx,
+                          time_t *timestamp,
+                          gnutls_datum_t *logid,
+                          gnutls_sign_algorithm_t *sigalg,
+                          gnutls_datum_t *signature)
+{
+       int retval = 0;
+       struct ct_sct_st *sct;
+
+       if (idx >= scts->size)
+               return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE;
+
+       sct = &scts->scts[idx];
+       if (sct->version != 0)
+               return GNUTLS_E_INVALID_REQUEST;
+
+       if (signature) {
+               retval = _gnutls_set_datum(signature,
+                                          sct->signature.data,
+                                          sct->signature.size);
+               if (retval < 0)
+                       return retval;
+       }
+
+       if (logid) {
+               retval = _gnutls_set_datum(logid,
+                                          sct->logid,
+                                          SCT_V1_LOGID_SIZE);
+               if (retval < 0) {
+                       _gnutls_free_datum(signature);
+                       return retval;
+               }
+       }
+
+       if (timestamp)
+               *timestamp = sct->timestamp / 1000;
+
+       if (sigalg)
+               *sigalg = sct->sigalg;
+
+       return GNUTLS_E_SUCCESS;
+}