@@ -119,6 +119,8 @@ static njs_int_t njs_ext_export_key(njs_vm_t *vm, njs_value_t *args,
119
119
njs_uint_t nargs , njs_index_t unused , njs_value_t * retval );
120
120
static njs_int_t njs_ext_generate_key (njs_vm_t * vm , njs_value_t * args ,
121
121
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 );
122
124
static njs_int_t njs_ext_import_key (njs_vm_t * vm , njs_value_t * args ,
123
125
njs_uint_t nargs , njs_index_t unused , njs_value_t * retval );
124
126
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[] = {
519
521
}
520
522
},
521
523
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
+
522
535
{
523
536
.flags = NJS_EXTERN_METHOD ,
524
537
.name .string = njs_str ("importKey" ),
@@ -2891,6 +2904,331 @@ njs_ext_generate_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
2891
2904
}
2892
2905
2893
2906
2907
+ static njs_int_t
2908
+ njs_ext_generate_certificate (njs_vm_t * vm , njs_value_t * args , njs_uint_t nargs ,
2909
+ njs_index_t unused , njs_value_t * retval )
2910
+ {
2911
+ njs_int_t ret ;
2912
+ njs_value_t * options , * keyPair , * privKey , * pubKey , * val ;
2913
+ njs_opaque_value_t result , lvalue ;
2914
+ njs_webcrypto_key_t * privateKey , * publicKey ;
2915
+ njs_str_t subject , issuer , serialNumber , cert_data ;
2916
+ X509 * cert = NULL ;
2917
+ X509_NAME * name = NULL ;
2918
+ ASN1_INTEGER * serial_asn1 = NULL ;
2919
+ EVP_PKEY * pkey = NULL ;
2920
+ BIGNUM * serial_bn = NULL ;
2921
+ u_char * cert_buf = NULL ;
2922
+ int cert_len ;
2923
+ int64_t notBefore , notAfter ;
2924
+
2925
+ static const njs_str_t string_subject = njs_str ("subject" );
2926
+ static const njs_str_t string_issuer = njs_str ("issuer" );
2927
+ static const njs_str_t string_serialNumber = njs_str ("serialNumber" );
2928
+ static const njs_str_t string_notBefore = njs_str ("notBefore" );
2929
+ static const njs_str_t string_notAfter = njs_str ("notAfter" );
2930
+ static const njs_str_t string_privateKey = njs_str ("privateKey" );
2931
+ static const njs_str_t string_publicKey = njs_str ("publicKey" );
2932
+
2933
+ if (nargs < 3 ) {
2934
+ njs_vm_type_error (vm , "generateCertificate requires at least 2 arguments" );
2935
+ return NJS_ERROR ;
2936
+ }
2937
+
2938
+ options = njs_arg (args , nargs , 1 );
2939
+ keyPair = njs_arg (args , nargs , 2 );
2940
+
2941
+ if (!njs_value_is_object (options )) {
2942
+ njs_vm_type_error (vm , "generateCertificate options must be an object" );
2943
+ return NJS_ERROR ;
2944
+ }
2945
+
2946
+ if (!njs_value_is_object (keyPair )) {
2947
+ njs_vm_type_error (vm , "generateCertificate keyPair must be an object" );
2948
+ return NJS_ERROR ;
2949
+ }
2950
+
2951
+ /* Get private key from keyPair */
2952
+ privKey = njs_vm_object_prop (vm , keyPair , & string_privateKey , & lvalue );
2953
+ if (njs_slow_path (privKey == NULL )) {
2954
+ njs_vm_type_error (vm , "keyPair.privateKey is required" );
2955
+ return NJS_ERROR ;
2956
+ }
2957
+
2958
+ privateKey = njs_vm_external (vm , njs_webcrypto_crypto_key_proto_id , privKey );
2959
+ if (njs_slow_path (privateKey == NULL )) {
2960
+ njs_vm_type_error (vm , "keyPair.privateKey is not a CryptoKey object" );
2961
+ return NJS_ERROR ;
2962
+ }
2963
+
2964
+ /* Get public key from keyPair */
2965
+ pubKey = njs_vm_object_prop (vm , keyPair , & string_publicKey , & lvalue );
2966
+ if (njs_slow_path (pubKey == NULL )) {
2967
+ njs_vm_type_error (vm , "keyPair.publicKey is required" );
2968
+ return NJS_ERROR ;
2969
+ }
2970
+
2971
+ publicKey = njs_vm_external (vm , njs_webcrypto_crypto_key_proto_id , pubKey );
2972
+ if (njs_slow_path (publicKey == NULL )) {
2973
+ njs_vm_type_error (vm , "keyPair.publicKey is not a CryptoKey object" );
2974
+ return NJS_ERROR ;
2975
+ }
2976
+
2977
+ /* Extract subject */
2978
+ val = njs_vm_object_prop (vm , options , & string_subject , & lvalue );
2979
+ if (njs_slow_path (val == NULL )) {
2980
+ njs_vm_type_error (vm , "certificate subject is required" );
2981
+ return NJS_ERROR ;
2982
+ }
2983
+
2984
+ ret = njs_vm_value_to_bytes (vm , & subject , val );
2985
+ if (njs_slow_path (ret != NJS_OK )) {
2986
+ return NJS_ERROR ;
2987
+ }
2988
+
2989
+ /* Extract issuer (optional, defaults to subject for self-signed) */
2990
+ val = njs_vm_object_prop (vm , options , & string_issuer , & lvalue );
2991
+ if (val != NULL ) {
2992
+ ret = njs_vm_value_to_bytes (vm , & issuer , val );
2993
+ if (njs_slow_path (ret != NJS_OK )) {
2994
+ return NJS_ERROR ;
2995
+ }
2996
+ } else {
2997
+ issuer = subject ; /* Self-signed certificate */
2998
+ }
2999
+
3000
+ /* Extract serial number (optional, defaults to "1") */
3001
+ val = njs_vm_object_prop (vm , options , & string_serialNumber , & lvalue );
3002
+ if (val != NULL ) {
3003
+ ret = njs_vm_value_to_bytes (vm , & serialNumber , val );
3004
+ if (njs_slow_path (ret != NJS_OK )) {
3005
+ return NJS_ERROR ;
3006
+ }
3007
+ } else {
3008
+ serialNumber .start = NULL ;
3009
+ serialNumber .length = 0 ;
3010
+ }
3011
+
3012
+ /* Extract validity period */
3013
+ val = njs_vm_object_prop (vm , options , & string_notBefore , & lvalue );
3014
+ if (val != NULL ) {
3015
+ ret = njs_value_to_integer (vm , val , & notBefore );
3016
+ if (njs_slow_path (ret != NJS_OK )) {
3017
+ return NJS_ERROR ;
3018
+ }
3019
+ } else {
3020
+ notBefore = 0 ; /* Now */
3021
+ }
3022
+
3023
+ val = njs_vm_object_prop (vm , options , & string_notAfter , & lvalue );
3024
+ if (val != NULL ) {
3025
+ ret = njs_value_to_integer (vm , val , & notAfter );
3026
+ if (njs_slow_path (ret != NJS_OK )) {
3027
+ return NJS_ERROR ;
3028
+ }
3029
+ } else {
3030
+ notAfter = 365LL * 24 * 60 * 60 * 1000 ; /* 1 year from now */
3031
+ }
3032
+
3033
+ /* Create X509 certificate */
3034
+ cert = X509_new ();
3035
+ if (njs_slow_path (cert == NULL )) {
3036
+ njs_webcrypto_error (vm , "X509_new() failed" );
3037
+ goto fail ;
3038
+ }
3039
+
3040
+ /* Set version (X509v3) */
3041
+ if (X509_set_version (cert , 2 ) != 1 ) {
3042
+ njs_webcrypto_error (vm , "X509_set_version() failed" );
3043
+ goto fail ;
3044
+ }
3045
+
3046
+ /* Set serial number */
3047
+ serial_asn1 = ASN1_INTEGER_new ();
3048
+ if (njs_slow_path (serial_asn1 == NULL )) {
3049
+ njs_webcrypto_error (vm , "ASN1_INTEGER_new() failed" );
3050
+ goto fail ;
3051
+ }
3052
+
3053
+ if (serialNumber .start != NULL && serialNumber .length > 0 ) {
3054
+ /* Convert serialNumber to BIGNUM */
3055
+ serial_bn = BN_new ();
3056
+ if (njs_slow_path (serial_bn == NULL )) {
3057
+ njs_webcrypto_error (vm , "BN_new() failed" );
3058
+ goto fail ;
3059
+ }
3060
+
3061
+ if (serialNumber .length == 1 && serialNumber .start [0 ] >= '0' &&
3062
+ serialNumber .start [0 ] <= '9' ) {
3063
+ /* Handle single digit decimal serial numbers */
3064
+ char digit_str [2 ];
3065
+ digit_str [0 ] = serialNumber .start [0 ];
3066
+ digit_str [1 ] = '\0' ;
3067
+
3068
+ if (BN_dec2bn (& serial_bn , digit_str ) == 0 ) {
3069
+ njs_webcrypto_error (vm , "BN_dec2bn() failed" );
3070
+ goto fail ;
3071
+ }
3072
+ } else {
3073
+ /* Try to parse as decimal string first */
3074
+
3075
+ /* Create null-terminated string for parsing */
3076
+ char * serial_str = njs_mp_alloc (njs_vm_memory_pool (vm ),
3077
+ serialNumber .length + 1 );
3078
+ if (njs_slow_path (serial_str == NULL )) {
3079
+ njs_webcrypto_error (vm , "memory allocation failed" );
3080
+ goto fail ;
3081
+ }
3082
+ memcpy (serial_str , serialNumber .start , serialNumber .length );
3083
+ serial_str [serialNumber .length ] = '\0' ;
3084
+
3085
+ if (BN_dec2bn (& serial_bn , serial_str ) == 0 ) {
3086
+ /* If decimal parsing failed, try hex */
3087
+ BN_free (serial_bn );
3088
+ serial_bn = BN_new ();
3089
+ if (njs_slow_path (serial_bn == NULL )) {
3090
+ njs_webcrypto_error (vm , "BN_new() failed" );
3091
+ njs_mp_free (njs_vm_memory_pool (vm ), serial_str );
3092
+ goto fail ;
3093
+ }
3094
+
3095
+ if (BN_hex2bn (& serial_bn , serial_str ) == 0 ) {
3096
+ /* If both failed, fallback to treating as raw bytes */
3097
+ BN_free (serial_bn );
3098
+ serial_bn = BN_bin2bn (serialNumber .start , serialNumber .length , NULL );
3099
+ if (njs_slow_path (serial_bn == NULL )) {
3100
+ njs_mp_free (njs_vm_memory_pool (vm ), serial_str );
3101
+ njs_webcrypto_error (vm , "BN_bin2bn() failed" );
3102
+ goto fail ;
3103
+ }
3104
+ }
3105
+ }
3106
+
3107
+ njs_mp_free (njs_vm_memory_pool (vm ), serial_str );
3108
+ }
3109
+
3110
+ if (BN_to_ASN1_INTEGER (serial_bn , serial_asn1 ) == NULL ) {
3111
+ njs_webcrypto_error (vm , "BN_to_ASN1_INTEGER() failed" );
3112
+ goto fail ;
3113
+ }
3114
+ } else {
3115
+ /* Default to serial number 1 */
3116
+ if (ASN1_INTEGER_set_uint64 (serial_asn1 , 1 ) != 1 ) {
3117
+ njs_webcrypto_error (vm , "ASN1_INTEGER_set_uint64() failed" );
3118
+ goto fail ;
3119
+ }
3120
+ }
3121
+
3122
+ if (X509_set_serialNumber (cert , serial_asn1 ) != 1 ) {
3123
+ njs_webcrypto_error (vm , "X509_set_serialNumber() failed" );
3124
+ goto fail ;
3125
+ }
3126
+
3127
+ /* Set validity period */
3128
+ if (X509_gmtime_adj (X509_getm_notBefore (cert ), notBefore / 1000 ) == NULL ) {
3129
+ njs_webcrypto_error (vm , "X509_gmtime_adj(notBefore) failed" );
3130
+ goto fail ;
3131
+ }
3132
+
3133
+ if (X509_gmtime_adj (X509_getm_notAfter (cert ), notAfter / 1000 ) == NULL ) {
3134
+ njs_webcrypto_error (vm , "X509_gmtime_adj(notAfter) failed" );
3135
+ goto fail ;
3136
+ }
3137
+
3138
+ /* Set subject name */
3139
+ name = X509_NAME_new ();
3140
+ if (njs_slow_path (name == NULL )) {
3141
+ njs_webcrypto_error (vm , "X509_NAME_new() failed" );
3142
+ goto fail ;
3143
+ }
3144
+
3145
+ if (X509_NAME_add_entry_by_txt (name , "CN" , MBSTRING_ASC ,
3146
+ subject .start , subject .length , -1 , 0 ) != 1 ) {
3147
+ njs_webcrypto_error (vm , "X509_NAME_add_entry_by_txt(subject) failed" );
3148
+ goto fail ;
3149
+ }
3150
+
3151
+ if (X509_set_subject_name (cert , name ) != 1 ) {
3152
+ njs_webcrypto_error (vm , "X509_set_subject_name() failed" );
3153
+ goto fail ;
3154
+ }
3155
+
3156
+ /* Set issuer name */
3157
+ if (X509_set_issuer_name (cert , name ) != 1 ) {
3158
+ njs_webcrypto_error (vm , "X509_set_issuer_name() failed" );
3159
+ goto fail ;
3160
+ }
3161
+
3162
+ /* Set public key */
3163
+ pkey = privateKey -> u .a .pkey ;
3164
+ if (X509_set_pubkey (cert , pkey ) != 1 ) {
3165
+ njs_webcrypto_error (vm , "X509_set_pubkey() failed" );
3166
+ goto fail ;
3167
+ }
3168
+
3169
+ /* Sign the certificate with the private key */
3170
+ if (X509_sign (cert , pkey , EVP_sha256 ()) == 0 ) {
3171
+ njs_webcrypto_error (vm , "X509_sign() failed" );
3172
+ goto fail ;
3173
+ }
3174
+
3175
+ /* Convert certificate to DER format */
3176
+ cert_len = i2d_X509 (cert , & cert_buf );
3177
+ if (njs_slow_path (cert_len <= 0 )) {
3178
+ njs_webcrypto_error (vm , "i2d_X509() failed" );
3179
+ goto fail ;
3180
+ }
3181
+
3182
+ /* Create result array buffer */
3183
+ cert_data .start = cert_buf ;
3184
+ cert_data .length = cert_len ;
3185
+
3186
+ ret = njs_webcrypto_array_buffer (vm , njs_value_arg (& result ),
3187
+ cert_data .start , cert_data .length );
3188
+ if (njs_slow_path (ret != NJS_OK )) {
3189
+ goto fail ;
3190
+ }
3191
+
3192
+ /* Cleanup */
3193
+ if (serial_bn != NULL ) {
3194
+ BN_free (serial_bn );
3195
+ }
3196
+ if (serial_asn1 != NULL ) {
3197
+ ASN1_INTEGER_free (serial_asn1 );
3198
+ }
3199
+ if (name != NULL ) {
3200
+ X509_NAME_free (name );
3201
+ }
3202
+ if (cert != NULL ) {
3203
+ X509_free (cert );
3204
+ }
3205
+ if (cert_buf != NULL ) {
3206
+ OPENSSL_free (cert_buf );
3207
+ }
3208
+
3209
+ return njs_webcrypto_result (vm , & result , NJS_OK , retval );
3210
+
3211
+ fail :
3212
+ if (serial_bn != NULL ) {
3213
+ BN_free (serial_bn );
3214
+ }
3215
+ if (serial_asn1 != NULL ) {
3216
+ ASN1_INTEGER_free (serial_asn1 );
3217
+ }
3218
+ if (name != NULL ) {
3219
+ X509_NAME_free (name );
3220
+ }
3221
+ if (cert != NULL ) {
3222
+ X509_free (cert );
3223
+ }
3224
+ if (cert_buf != NULL ) {
3225
+ OPENSSL_free (cert_buf );
3226
+ }
3227
+
3228
+ return njs_webcrypto_result (vm , NULL , NJS_ERROR , retval );
3229
+ }
3230
+
3231
+
2894
3232
static BIGNUM *
2895
3233
njs_import_base64url_bignum (njs_vm_t * vm , njs_opaque_value_t * value )
2896
3234
{
0 commit comments