@@ -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 );
120120static 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 );
122124static 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 );
124126static 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,372 @@ 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_new ();
3118+ if (njs_slow_path (serial_bn == NULL )) {
3119+ njs_webcrypto_error (vm , "BN_new() failed" );
3120+ goto fail ;
3121+ }
3122+
3123+ serial_bn = BN_bin2bn (serialNumber .start , serialNumber .length , NULL );
3124+ if (njs_slow_path (serial_bn == NULL )) {
3125+ njs_webcrypto_error (vm , "BN_bin2bn() failed" );
3126+ goto fail ;
3127+ }
3128+
3129+ if (BN_to_ASN1_INTEGER (serial_bn , serial_asn1 ) == NULL ) {
3130+ njs_webcrypto_error (vm , "BN_to_ASN1_INTEGER() failed" );
3131+ goto fail ;
3132+ }
3133+ } else {
3134+ /* Default to serial number 1 */
3135+ if (ASN1_INTEGER_set_uint64 (serial_asn1 , 1 ) != 1 ) {
3136+ njs_webcrypto_error (vm , "ASN1_INTEGER_set_uint64() failed" );
3137+ goto fail ;
3138+ }
3139+ }
3140+
3141+ if (X509_set_serialNumber (cert , serial_asn1 ) != 1 ) {
3142+ njs_webcrypto_error (vm , "X509_set_serialNumber() failed" );
3143+ goto fail ;
3144+ }
3145+
3146+ /* Set validity period */
3147+ if (X509_gmtime_adj (X509_getm_notBefore (cert ), notBefore / 1000 ) == NULL ) {
3148+ njs_webcrypto_error (vm , "X509_gmtime_adj(notBefore) failed" );
3149+ goto fail ;
3150+ }
3151+
3152+ if (X509_gmtime_adj (X509_getm_notAfter (cert ), notAfter / 1000 ) == NULL ) {
3153+ njs_webcrypto_error (vm , "X509_gmtime_adj(notAfter) failed" );
3154+ goto fail ;
3155+ }
3156+
3157+ /* Set subject name */
3158+ name = X509_NAME_new ();
3159+ if (njs_slow_path (name == NULL )) {
3160+ njs_webcrypto_error (vm , "X509_NAME_new() failed" );
3161+ goto fail ;
3162+ }
3163+
3164+ ret = njs_add_name_attributes (vm , name , subject_attrs );
3165+ if (njs_slow_path (ret != NJS_OK )) {
3166+ goto fail ;
3167+ }
3168+
3169+ if (X509_set_subject_name (cert , name ) != 1 ) {
3170+ njs_webcrypto_error (vm , "X509_set_subject_name() failed" );
3171+ goto fail ;
3172+ }
3173+
3174+ if (issuer_is_subject ) {
3175+ /* Set issuer name (same as subject for self-signed) */
3176+ if (X509_set_issuer_name (cert , name ) != 1 ) {
3177+ njs_webcrypto_error (vm , "X509_set_issuer_name() failed" );
3178+ goto fail ;
3179+ }
3180+ } else {
3181+ /* Create separate issuer name */
3182+ X509_NAME * issuer_name = X509_NAME_new ();
3183+ if (njs_slow_path (issuer_name == NULL )) {
3184+ njs_webcrypto_error (vm , "X509_NAME_new() failed" );
3185+ goto fail ;
3186+ }
3187+
3188+ ret = njs_add_name_attributes (vm , issuer_name , issuer_attrs );
3189+ if (njs_slow_path (ret != NJS_OK )) {
3190+ X509_NAME_free (issuer_name );
3191+ goto fail ;
3192+ }
3193+
3194+ if (X509_set_issuer_name (cert , issuer_name ) != 1 ) {
3195+ njs_webcrypto_error (vm , "X509_set_issuer_name() failed" );
3196+ X509_NAME_free (issuer_name );
3197+ goto fail ;
3198+ }
3199+
3200+ X509_NAME_free (issuer_name );
3201+ }
3202+
3203+ /* Set public key */
3204+ pkey = privateKey -> u .a .pkey ;
3205+ if (X509_set_pubkey (cert , pkey ) != 1 ) {
3206+ njs_webcrypto_error (vm , "X509_set_pubkey() failed" );
3207+ goto fail ;
3208+ }
3209+
3210+ /* Sign the certificate with the private key */
3211+ if (X509_sign (cert , pkey , EVP_sha256 ()) == 0 ) {
3212+ njs_webcrypto_error (vm , "X509_sign() failed" );
3213+ goto fail ;
3214+ }
3215+
3216+ /* Convert certificate to DER format */
3217+ cert_len = i2d_X509 (cert , & cert_buf );
3218+ if (njs_slow_path (cert_len <= 0 )) {
3219+ njs_webcrypto_error (vm , "i2d_X509() failed" );
3220+ goto fail ;
3221+ }
3222+
3223+ /* Create result array buffer */
3224+ cert_data .start = cert_buf ;
3225+ cert_data .length = cert_len ;
3226+
3227+ ret = njs_webcrypto_array_buffer (vm , njs_value_arg (& result ),
3228+ cert_data .start , cert_data .length );
3229+ if (njs_slow_path (ret != NJS_OK )) {
3230+ goto fail ;
3231+ }
3232+
3233+ /* Cleanup */
3234+ if (serial_bn != NULL ) {
3235+ BN_free (serial_bn );
3236+ }
3237+ if (serial_asn1 != NULL ) {
3238+ ASN1_INTEGER_free (serial_asn1 );
3239+ }
3240+ if (name != NULL ) {
3241+ X509_NAME_free (name );
3242+ }
3243+ if (cert != NULL ) {
3244+ X509_free (cert );
3245+ }
3246+ if (cert_buf != NULL ) {
3247+ OPENSSL_free (cert_buf );
3248+ }
3249+
3250+ return njs_webcrypto_result (vm , & result , NJS_OK , retval );
3251+
3252+ fail :
3253+ if (serial_bn != NULL ) {
3254+ BN_free (serial_bn );
3255+ }
3256+ if (serial_asn1 != NULL ) {
3257+ ASN1_INTEGER_free (serial_asn1 );
3258+ }
3259+ if (name != NULL ) {
3260+ X509_NAME_free (name );
3261+ }
3262+ if (cert != NULL ) {
3263+ X509_free (cert );
3264+ }
3265+ if (cert_buf != NULL ) {
3266+ OPENSSL_free (cert_buf );
3267+ }
3268+
3269+ return njs_webcrypto_result (vm , NULL , NJS_ERROR , retval );
3270+ }
3271+
3272+
28943273static BIGNUM *
28953274njs_import_base64url_bignum (njs_vm_t * vm , njs_opaque_value_t * value )
28963275{
0 commit comments