Skip to content

Commit ff5badf

Browse files
Introduced crypto.subtle.generateCertificate().
It fixes _766 on GitHub.
1 parent aa38086 commit ff5badf

File tree

6 files changed

+2521
-0
lines changed

6 files changed

+2521
-0
lines changed

external/njs_webcrypto_module.c

Lines changed: 373 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ static njs_int_t njs_ext_export_key(njs_vm_t *vm, njs_value_t *args,
119119
njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
120120
static njs_int_t njs_ext_generate_key(njs_vm_t *vm, njs_value_t *args,
121121
njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
122+
static njs_int_t njs_ext_generate_certificate(njs_vm_t *vm, njs_value_t *args,
123+
njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
122124
static njs_int_t njs_ext_import_key(njs_vm_t *vm, njs_value_t *args,
123125
njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
124126
static njs_int_t njs_ext_sign(njs_vm_t *vm, njs_value_t *args,
@@ -519,6 +521,17 @@ static njs_external_t njs_ext_subtle_webcrypto[] = {
519521
}
520522
},
521523

524+
{
525+
.flags = NJS_EXTERN_METHOD,
526+
.name.string = njs_str("generateCertificate"),
527+
.writable = 1,
528+
.configurable = 1,
529+
.enumerable = 1,
530+
.u.method = {
531+
.native = njs_ext_generate_certificate,
532+
}
533+
},
534+
522535
{
523536
.flags = NJS_EXTERN_METHOD,
524537
.name.string = njs_str("importKey"),
@@ -2891,6 +2904,366 @@ njs_ext_generate_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
28912904
}
28922905

28932906

2907+
static njs_int_t
2908+
njs_add_name_attributes(njs_vm_t *vm, X509_NAME *name, njs_value_t *attrs)
2909+
{
2910+
njs_int_t ret;
2911+
njs_value_t *val;
2912+
njs_str_t attr_value;
2913+
njs_opaque_value_t lvalue;
2914+
2915+
static const njs_str_t string_CN = njs_str("CN");
2916+
static const njs_str_t string_C = njs_str("C");
2917+
static const njs_str_t string_ST = njs_str("ST");
2918+
static const njs_str_t string_L = njs_str("L");
2919+
static const njs_str_t string_O = njs_str("O");
2920+
static const njs_str_t string_OU = njs_str("OU");
2921+
static const njs_str_t string_E = njs_str("E");
2922+
2923+
/* Define attribute mapping */
2924+
struct {
2925+
const njs_str_t *key;
2926+
const char *openssl_name;
2927+
} attr_map[] = {
2928+
{ &string_CN, "CN" },
2929+
{ &string_C, "C" },
2930+
{ &string_ST, "ST" },
2931+
{ &string_L, "L" },
2932+
{ &string_O, "O" },
2933+
{ &string_OU, "OU" },
2934+
{ &string_E, "E" }
2935+
};
2936+
2937+
int attr_count = 0;
2938+
2939+
for (size_t i = 0; i < sizeof(attr_map) / sizeof(attr_map[0]); i++) {
2940+
val = njs_vm_object_prop(vm, attrs, attr_map[i].key, &lvalue);
2941+
if (val != NULL) {
2942+
ret = njs_vm_value_to_bytes(vm, &attr_value, val);
2943+
if (njs_slow_path(ret != NJS_OK)) {
2944+
return NJS_ERROR;
2945+
}
2946+
2947+
if (X509_NAME_add_entry_by_txt(name, attr_map[i].openssl_name, MBSTRING_ASC,
2948+
attr_value.start, attr_value.length, -1, 0) != 1) {
2949+
njs_webcrypto_error(vm, "X509_NAME_add_entry_by_txt() failed");
2950+
return NJS_ERROR;
2951+
}
2952+
attr_count++;
2953+
}
2954+
}
2955+
2956+
if (attr_count == 0) {
2957+
njs_webcrypto_error(vm, "X509_NAME_add_entry_by_txt() failed");
2958+
return NJS_ERROR;
2959+
}
2960+
2961+
return NJS_OK;
2962+
}
2963+
2964+
static njs_int_t
2965+
njs_ext_generate_certificate(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
2966+
njs_index_t unused, njs_value_t *retval)
2967+
{
2968+
njs_int_t ret;
2969+
njs_value_t *options, *keyPair, *privKey, *pubKey, *val;
2970+
njs_opaque_value_t result, lvalue;
2971+
njs_webcrypto_key_t *privateKey, *publicKey;
2972+
njs_str_t serialNumber, cert_data;
2973+
X509 *cert = NULL;
2974+
X509_NAME *name = NULL;
2975+
ASN1_INTEGER *serial_asn1 = NULL;
2976+
EVP_PKEY *pkey = NULL;
2977+
BIGNUM *serial_bn = NULL;
2978+
u_char *cert_buf = NULL;
2979+
int cert_len;
2980+
int64_t notBefore, notAfter;
2981+
int issuer_is_subject = 0;
2982+
2983+
static const njs_str_t string_subject = njs_str("subject");
2984+
static const njs_str_t string_issuer = njs_str("issuer");
2985+
static const njs_str_t string_serialNumber = njs_str("serialNumber");
2986+
static const njs_str_t string_notBefore = njs_str("notBefore");
2987+
static const njs_str_t string_notAfter = njs_str("notAfter");
2988+
static const njs_str_t string_privateKey = njs_str("privateKey");
2989+
static const njs_str_t string_publicKey = njs_str("publicKey");
2990+
2991+
if (nargs < 3) {
2992+
njs_vm_type_error(vm, "generateCertificate requires at least 2 arguments");
2993+
return NJS_ERROR;
2994+
}
2995+
2996+
options = njs_arg(args, nargs, 1);
2997+
keyPair = njs_arg(args, nargs, 2);
2998+
2999+
if (!njs_value_is_object(options)) {
3000+
njs_vm_type_error(vm, "generateCertificate options must be an object");
3001+
return NJS_ERROR;
3002+
}
3003+
3004+
if (!njs_value_is_object(keyPair)) {
3005+
njs_vm_type_error(vm, "generateCertificate keyPair must be an object");
3006+
return NJS_ERROR;
3007+
}
3008+
3009+
/* Get private key from keyPair */
3010+
privKey = njs_vm_object_prop(vm, keyPair, &string_privateKey, &lvalue);
3011+
if (njs_slow_path(privKey == NULL)) {
3012+
njs_vm_type_error(vm, "keyPair.privateKey is required");
3013+
return NJS_ERROR;
3014+
}
3015+
3016+
privateKey = njs_vm_external(vm, njs_webcrypto_crypto_key_proto_id, privKey);
3017+
if (njs_slow_path(privateKey == NULL)) {
3018+
njs_vm_type_error(vm, "keyPair.privateKey is not a CryptoKey object");
3019+
return NJS_ERROR;
3020+
}
3021+
3022+
/* Get public key from keyPair */
3023+
pubKey = njs_vm_object_prop(vm, keyPair, &string_publicKey, &lvalue);
3024+
if (njs_slow_path(pubKey == NULL)) {
3025+
njs_vm_type_error(vm, "keyPair.publicKey is required");
3026+
return NJS_ERROR;
3027+
}
3028+
3029+
publicKey = njs_vm_external(vm, njs_webcrypto_crypto_key_proto_id, pubKey);
3030+
if (njs_slow_path(publicKey == NULL)) {
3031+
njs_vm_type_error(vm, "keyPair.publicKey is not a CryptoKey object");
3032+
return NJS_ERROR;
3033+
}
3034+
3035+
/* Extract subject */
3036+
njs_opaque_value_t subject_lvalue, issuer_lvalue;
3037+
njs_value_t *subject_val = njs_vm_object_prop(vm, options, &string_subject, &subject_lvalue);
3038+
if (njs_slow_path(subject_val == NULL)) {
3039+
njs_vm_type_error(vm, "certificate subject is required");
3040+
return NJS_ERROR;
3041+
}
3042+
3043+
if (!njs_value_is_object(subject_val)) {
3044+
njs_vm_type_error(vm, "certificate subject must be an object");
3045+
return NJS_ERROR;
3046+
}
3047+
njs_value_t *subject_attrs = subject_val;
3048+
3049+
/* Extract issuer (optional, defaults to subject for self-signed) */
3050+
njs_value_t *issuer_val = njs_vm_object_prop(vm, options, &string_issuer, &issuer_lvalue);
3051+
njs_value_t *issuer_attrs = NULL;
3052+
if (issuer_val != NULL) {
3053+
if (!njs_value_is_object(issuer_val)) {
3054+
njs_vm_type_error(vm, "certificate issuer must be an object");
3055+
return NJS_ERROR;
3056+
}
3057+
issuer_attrs = issuer_val;
3058+
} else {
3059+
issuer_is_subject = 1; /* Self-signed certificate */
3060+
}
3061+
3062+
/* Extract serial number (optional, defaults to "1") */
3063+
val = njs_vm_object_prop(vm, options, &string_serialNumber, &lvalue);
3064+
if (val != NULL) {
3065+
ret = njs_vm_value_to_bytes(vm, &serialNumber, val);
3066+
if (njs_slow_path(ret != NJS_OK)) {
3067+
return NJS_ERROR;
3068+
}
3069+
} else {
3070+
serialNumber.start = NULL;
3071+
serialNumber.length = 0;
3072+
}
3073+
3074+
/* Extract validity period */
3075+
val = njs_vm_object_prop(vm, options, &string_notBefore, &lvalue);
3076+
if (val != NULL) {
3077+
ret = njs_value_to_integer(vm, val, &notBefore);
3078+
if (njs_slow_path(ret != NJS_OK)) {
3079+
return NJS_ERROR;
3080+
}
3081+
} else {
3082+
notBefore = 0; /* Now */
3083+
}
3084+
3085+
val = njs_vm_object_prop(vm, options, &string_notAfter, &lvalue);
3086+
if (val != NULL) {
3087+
ret = njs_value_to_integer(vm, val, &notAfter);
3088+
if (njs_slow_path(ret != NJS_OK)) {
3089+
return NJS_ERROR;
3090+
}
3091+
} else {
3092+
notAfter = 365LL * 24 * 60 * 60 * 1000; /* 1 year from now */
3093+
}
3094+
3095+
/* Create X509 certificate */
3096+
cert = X509_new();
3097+
if (njs_slow_path(cert == NULL)) {
3098+
njs_webcrypto_error(vm, "X509_new() failed");
3099+
goto fail;
3100+
}
3101+
3102+
/* Set version (X509v3) */
3103+
if (X509_set_version(cert, 2) != 1) {
3104+
njs_webcrypto_error(vm, "X509_set_version() failed");
3105+
goto fail;
3106+
}
3107+
3108+
/* Set serial number */
3109+
serial_asn1 = ASN1_INTEGER_new();
3110+
if (njs_slow_path(serial_asn1 == NULL)) {
3111+
njs_webcrypto_error(vm, "ASN1_INTEGER_new() failed");
3112+
goto fail;
3113+
}
3114+
3115+
if (serialNumber.start != NULL && serialNumber.length > 0) {
3116+
/* Convert serialNumber to BIGNUM */
3117+
serial_bn = BN_bin2bn(serialNumber.start, serialNumber.length, NULL);
3118+
if (njs_slow_path(serial_bn == NULL)) {
3119+
njs_webcrypto_error(vm, "BN_bin2bn() failed");
3120+
goto fail;
3121+
}
3122+
3123+
if (BN_to_ASN1_INTEGER(serial_bn, serial_asn1) == NULL) {
3124+
njs_webcrypto_error(vm, "BN_to_ASN1_INTEGER() failed");
3125+
goto fail;
3126+
}
3127+
} else {
3128+
/* Default to serial number 1 */
3129+
if (ASN1_INTEGER_set_uint64(serial_asn1, 1) != 1) {
3130+
njs_webcrypto_error(vm, "ASN1_INTEGER_set_uint64() failed");
3131+
goto fail;
3132+
}
3133+
}
3134+
3135+
if (X509_set_serialNumber(cert, serial_asn1) != 1) {
3136+
njs_webcrypto_error(vm, "X509_set_serialNumber() failed");
3137+
goto fail;
3138+
}
3139+
3140+
/* Set validity period */
3141+
if (X509_gmtime_adj(X509_getm_notBefore(cert), notBefore / 1000) == NULL) {
3142+
njs_webcrypto_error(vm, "X509_gmtime_adj(notBefore) failed");
3143+
goto fail;
3144+
}
3145+
3146+
if (X509_gmtime_adj(X509_getm_notAfter(cert), notAfter / 1000) == NULL) {
3147+
njs_webcrypto_error(vm, "X509_gmtime_adj(notAfter) failed");
3148+
goto fail;
3149+
}
3150+
3151+
/* Set subject name */
3152+
name = X509_NAME_new();
3153+
if (njs_slow_path(name == NULL)) {
3154+
njs_webcrypto_error(vm, "X509_NAME_new() failed");
3155+
goto fail;
3156+
}
3157+
3158+
ret = njs_add_name_attributes(vm, name, subject_attrs);
3159+
if (njs_slow_path(ret != NJS_OK)) {
3160+
goto fail;
3161+
}
3162+
3163+
if (X509_set_subject_name(cert, name) != 1) {
3164+
njs_webcrypto_error(vm, "X509_set_subject_name() failed");
3165+
goto fail;
3166+
}
3167+
3168+
if (issuer_is_subject) {
3169+
/* Set issuer name (same as subject for self-signed) */
3170+
if (X509_set_issuer_name(cert, name) != 1) {
3171+
njs_webcrypto_error(vm, "X509_set_issuer_name() failed");
3172+
goto fail;
3173+
}
3174+
} else {
3175+
/* Create separate issuer name */
3176+
X509_NAME *issuer_name = X509_NAME_new();
3177+
if (njs_slow_path(issuer_name == NULL)) {
3178+
njs_webcrypto_error(vm, "X509_NAME_new() failed");
3179+
goto fail;
3180+
}
3181+
3182+
ret = njs_add_name_attributes(vm, issuer_name, issuer_attrs);
3183+
if (njs_slow_path(ret != NJS_OK)) {
3184+
X509_NAME_free(issuer_name);
3185+
goto fail;
3186+
}
3187+
3188+
if (X509_set_issuer_name(cert, issuer_name) != 1) {
3189+
njs_webcrypto_error(vm, "X509_set_issuer_name() failed");
3190+
X509_NAME_free(issuer_name);
3191+
goto fail;
3192+
}
3193+
3194+
X509_NAME_free(issuer_name);
3195+
}
3196+
3197+
/* Set public key */
3198+
pkey = privateKey->u.a.pkey;
3199+
if (X509_set_pubkey(cert, pkey) != 1) {
3200+
njs_webcrypto_error(vm, "X509_set_pubkey() failed");
3201+
goto fail;
3202+
}
3203+
3204+
/* Sign the certificate with the private key */
3205+
if (X509_sign(cert, pkey, EVP_sha256()) == 0) {
3206+
njs_webcrypto_error(vm, "X509_sign() failed");
3207+
goto fail;
3208+
}
3209+
3210+
/* Convert certificate to DER format */
3211+
cert_len = i2d_X509(cert, &cert_buf);
3212+
if (njs_slow_path(cert_len <= 0)) {
3213+
njs_webcrypto_error(vm, "i2d_X509() failed");
3214+
goto fail;
3215+
}
3216+
3217+
/* Create result array buffer */
3218+
cert_data.start = cert_buf;
3219+
cert_data.length = cert_len;
3220+
3221+
ret = njs_webcrypto_array_buffer(vm, njs_value_arg(&result),
3222+
cert_data.start, cert_data.length);
3223+
if (njs_slow_path(ret != NJS_OK)) {
3224+
goto fail;
3225+
}
3226+
3227+
/* Cleanup */
3228+
if (serial_bn != NULL) {
3229+
BN_free(serial_bn);
3230+
}
3231+
if (serial_asn1 != NULL) {
3232+
ASN1_INTEGER_free(serial_asn1);
3233+
}
3234+
if (name != NULL) {
3235+
X509_NAME_free(name);
3236+
}
3237+
if (cert != NULL) {
3238+
X509_free(cert);
3239+
}
3240+
if (cert_buf != NULL) {
3241+
OPENSSL_free(cert_buf);
3242+
}
3243+
3244+
return njs_webcrypto_result(vm, &result, NJS_OK, retval);
3245+
3246+
fail:
3247+
if (serial_bn != NULL) {
3248+
BN_free(serial_bn);
3249+
}
3250+
if (serial_asn1 != NULL) {
3251+
ASN1_INTEGER_free(serial_asn1);
3252+
}
3253+
if (name != NULL) {
3254+
X509_NAME_free(name);
3255+
}
3256+
if (cert != NULL) {
3257+
X509_free(cert);
3258+
}
3259+
if (cert_buf != NULL) {
3260+
OPENSSL_free(cert_buf);
3261+
}
3262+
3263+
return njs_webcrypto_result(vm, NULL, NJS_ERROR, retval);
3264+
}
3265+
3266+
28943267
static BIGNUM *
28953268
njs_import_base64url_bignum(njs_vm_t *vm, njs_opaque_value_t *value)
28963269
{

0 commit comments

Comments
 (0)