mirror of
https://github.com/vacp2p/nim-libp2p.git
synced 2026-01-09 02:38:19 -05:00
feat(cert): add certificate signing request (CSR) generation (#1355)
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -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
|
||||
|
||||
@@ -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".}
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user