@@ -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,366 @@ 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_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
+
2894
3267
static BIGNUM *
2895
3268
njs_import_base64url_bignum (njs_vm_t * vm , njs_opaque_value_t * value )
2896
3269
{
0 commit comments