feat(cert): add certificate signing request (CSR) generation (#1355)

This commit is contained in:
Gabriel Cruz
2025-05-06 15:56:51 -03:00
committed by GitHub
parent 5cb493439d
commit ccb24b5f1f
5 changed files with 206 additions and 3 deletions

View File

@@ -959,4 +959,133 @@ void cert_free_key(cert_key_t key) {
struct cert_key_s *k = (struct cert_key_s *)key;
EVP_PKEY_free(k->pkey);
free(k);
}
// Function to check if a Common Name is correct
// each label should have <= 63 characters
// the whole CN should have <= 253 characters
cert_error_t check_cn(const char *cn) {
cert_error_t ret_code = CERT_SUCCESS;
if (!cn || strlen(cn) == 0) {
return CERT_ERROR_CN_EMPTY;
}
if (strlen(cn) > 253) {
return CERT_ERROR_CN_TOO_LONG;
}
char *cn_copy = strdup(cn);
char *cn_copy_orig = cn_copy;
// trim trailing dot if any before checking
size_t len = strlen(cn_copy);
if (len > 0 && cn_copy[len - 1] == '.') {
cn_copy[len - 1] = '\0';
}
char *label;
char *last = NULL;
char *ptr = cn_copy;
while ((label = strtok(ptr, ".")) != NULL) {
if (last && last + strlen(last) + 1 != label) {
// empty label (e.g., "example..com")
ret_code = CERT_ERROR_CN_EMPTY_LABEL;
break;
}
if (strlen(label) > 63) {
ret_code = CERT_ERROR_CN_LABEL_TOO_LONG;
break;
}
last = label;
ptr = NULL;
}
free(cn_copy_orig);
return ret_code;
}
cert_error_t cert_signing_req(const char *cn, cert_key_t key, cert_buffer **csr_buffer) {
cert_error_t ret_code = CERT_SUCCESS;
X509_REQ *x509_req = NULL;
X509_NAME *name = NULL;
X509_EXTENSION *ext = NULL;
X509V3_CTX ctx;
STACK_OF(X509_EXTENSION) *exts = NULL;
unsigned char *der = NULL;
size_t der_len = 0;
ret_code = check_cn(cn);
if (ret_code != CERT_SUCCESS) {
goto cleanup;
}
if (!key || !(key->pkey)) {
ret_code = CERT_ERROR_NO_PUBKEY;
goto cleanup;
}
EVP_PKEY *pkey = key->pkey;
x509_req = X509_REQ_new();
if (!x509_req) {
ret_code = CERT_ERROR_X509_REQ_GEN;
goto cleanup;
}
if (!X509_REQ_set_pubkey(x509_req, pkey)) {
ret_code = CERT_ERROR_PUBKEY_SET;
goto cleanup;
}
// Build SAN extension
X509V3_set_ctx(&ctx, NULL, NULL, x509_req, NULL, 0);
char san_str[258]; // max of 253 from cn + 4 "DNS:" + \0
snprintf(san_str, sizeof(san_str), "DNS:%s", cn);
ext = X509V3_EXT_conf_nid(NULL, &ctx, NID_subject_alt_name, san_str);
if (!ext) {
ret_code = CERT_ERROR_X509_SAN;
goto cleanup;
}
exts = sk_X509_EXTENSION_new_null();
if (!exts || !sk_X509_EXTENSION_push(exts, ext)) {
ret_code = CERT_ERROR_X509_SAN;
goto cleanup;
}
if (!X509_REQ_add_extensions(x509_req, exts)) {
ret_code = CERT_ERROR_X509_SAN;
goto cleanup;
}
if (!X509_REQ_sign(x509_req, pkey, EVP_sha256())) {
ret_code = CERT_ERROR_SIGN;
goto cleanup;
}
der_len = i2d_X509_REQ(x509_req, &der);
if (der_len < 0) {
ret_code = CERT_ERROR_X509_REQ_DER;
goto cleanup;
}
ret_code = init_cert_buffer(csr_buffer, der, der_len);
if (ret_code < 0) {
goto cleanup;
}
cleanup:
if (exts)
sk_X509_EXTENSION_pop_free(exts, X509_EXTENSION_free);
if (x509_req)
X509_REQ_free(x509_req);
if (der)
OPENSSL_free(der);
if (ret_code != CERT_SUCCESS && csr_buffer) {
cert_free_buffer(*csr_buffer);
*csr_buffer = NULL;
}
return ret_code;
}

View File

@@ -54,6 +54,14 @@ typedef int32_t cert_error_t;
#define CERT_ERROR_PUBKEY_DER_CONV -41
#define CERT_ERROR_INIT_KEYGEN -42
#define CERT_ERROR_SET_CURVE -43
#define CERT_ERROR_X509_REQ_GEN -44
#define CERT_ERROR_X509_REQ_DER -45
#define CERT_ERROR_NO_PUBKEY -46
#define CERT_ERROR_X509_SAN -47
#define CERT_ERROR_CN_TOO_LONG -48
#define CERT_ERROR_CN_LABEL_TOO_LONG -49
#define CERT_ERROR_CN_EMPTY_LABEL -50
#define CERT_ERROR_CN_EMPTY -51
typedef enum { CERT_FORMAT_DER = 0, CERT_FORMAT_PEM = 1 } cert_format_t;
@@ -184,4 +192,15 @@ void cert_free_key(cert_key_t key);
*/
void cert_free_buffer(cert_buffer *buffer);
/**
* Create a X.509 certificate request
*
* @param cn Domain for which we're requesting the certificate
* @param key Public key of the requesting client
* @param csr_buffer Pointer to the buffer that will be set to the CSR in DER format
*
* @return CERT_SUCCESS on successful execution, an error code otherwise
*/
cert_error_t cert_signing_req(const char *cn, cert_key_t key, cert_buffer **csr_buffer);
#endif /* LIBP2P_CERT_H */

View File

@@ -55,10 +55,10 @@ type EncodingFormat* = enum
proc cert_format_t(self: EncodingFormat): cert_format_t =
if self == EncodingFormat.DER: CERT_FORMAT_DER else: CERT_FORMAT_PEM
proc toCertBuffer(self: seq[uint8]): cert_buffer =
proc toCertBuffer*(self: seq[uint8]): cert_buffer =
cert_buffer(data: self[0].unsafeAddr, length: self.len.csize_t)
proc toSeq(self: ptr cert_buffer): seq[byte] =
proc toSeq*(self: ptr cert_buffer): seq[byte] =
toOpenArray(cast[ptr UncheckedArray[byte]](self.data), 0, self.length.int - 1).toSeq()
# Initialize entropy and DRBG contexts at the module level

View File

@@ -79,3 +79,7 @@ proc cert_free_buffer*(
proc cert_free_parsed*(
cert: ptr cert_parsed
): void {.cdecl, importc: "cert_free_parsed".}
proc cert_signing_req*(
cn: cstring, key: cert_key_t, csr_buffer: ptr ptr cert_buffer
): cert_error_t {.cdecl, importc: "cert_signing_req".}

View File

@@ -1,7 +1,8 @@
import unittest2
import times
import times, base64
import ../../../libp2p/transports/tls/certificate
import ../../../libp2p/transports/tls/certificate_ffi
import ../../../libp2p/crypto/crypto
import ../../../libp2p/peerid
@@ -107,6 +108,56 @@ suite "Test vectors":
# should not verify
check not cert.verify()
test "CSR generation":
var certKey: cert_key_t
var certCtx: cert_context_t
var derCSR: ptr cert_buffer = nil
check cert_init_drbg("seed".cstring, 4, certCtx.addr) == CERT_SUCCESS
check cert_generate_key(certCtx, certKey.addr) == CERT_SUCCESS
check cert_signing_req("my.domain.string".cstring, certKey, derCSR.addr) ==
CERT_SUCCESS
check cert_signing_req(
"my.big.domain.string.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaa".cstring,
# 253 characters, no labels longer than 63, okay
certKey,
derCSR.addr,
) == CERT_SUCCESS
check cert_signing_req(
"my.domain.".cstring, # domain ending in ".", okay
certKey,
derCSR.addr,
) == CERT_SUCCESS
check cert_signing_req(
"my.big.domain.string.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".cstring,
# 254 characters, too long
certKey,
derCSR.addr,
) == -48 # CERT_ERROR_CN_TOO_LONG
check cert_signing_req(
"my.big.domain.string.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".cstring,
# 64 character label, too long
certKey,
derCSR.addr,
) == -49 # CERT_ERROR_CN_LABEL_TOO_LONG
check cert_signing_req(
"my..empty.label.domain".cstring, # domain with empty label
certKey,
derCSR.addr,
) == -50 # CERT_ERROR_CN_EMPTY_LABEL
check cert_signing_req(
"".cstring, # domain with empty cn
certKey,
derCSR.addr,
) == -51 # CERT_ERROR_CN_EMPTY
suite "utilities test":
test "parseCertTime":
var dt = parseCertTime("Mar 19 11:54:31 2025 GMT")