Complete key derivation support.
authorDr. Stephen Henson <steve@openssl.org>
Thu, 13 Apr 2006 20:16:56 +0000 (20:16 +0000)
committerDr. Stephen Henson <steve@openssl.org>
Thu, 13 Apr 2006 20:16:56 +0000 (20:16 +0000)
CHANGES
apps/pkeyutl.c
crypto/dh/dh.h
crypto/dh/dh_ameth.c
crypto/dh/dh_err.c
crypto/dh/dh_pmeth.c
crypto/evp/evp.h
crypto/evp/evp_err.c
crypto/evp/pmeth_fn.c
crypto/evp/pmeth_lib.c

diff --git a/CHANGES b/CHANGES
index 2a07f91..3ba2d71 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -4,6 +4,10 @@
 
  Changes between 0.9.8a and 0.9.9  [xx XXX xxxx]
 
+  *) Add support for key derivation (agreement) in the API, DH method and
+     pkeyutl.
+     [Steve Henson]
+
   *) Add DSA pkey method and DH pkey methods, extend DH ASN1 method to support
      public and private key formats. As a side effect these add additional 
      command line functionality not previously available: DSA signatures can be
index 522656d..a3e55f5 100644 (file)
@@ -74,7 +74,10 @@ static void usage(void);
 
 static EVP_PKEY_CTX *init_ctx(int *pkeysize,
                                char *keyfile, int keyform, int key_type,
-                               char *passargin, int pkey_op, char *engine);
+                               char *passargin, int pkey_op, ENGINE *e);
+
+static int setup_peer(BIO *err, EVP_PKEY_CTX *ctx, int peerform,
+                                                       const char *file);
 
 int MAIN(int argc, char **);
 
@@ -82,9 +85,9 @@ int MAIN(int argc, char **argv)
 {
        BIO *in = NULL, *out = NULL;
        char *infile = NULL, *outfile = NULL, *sigfile = NULL;
-       char *engine = NULL;
+       ENGINE *e = NULL;
        int pkey_op = EVP_PKEY_OP_SIGN, key_type = KEY_PRIVKEY;
-       int keyform = FORMAT_PEM;
+       int keyform = FORMAT_PEM, peerform = FORMAT_PEM;
        char badarg = 0, rev = 0;
        char hexdump = 0, asn1parse = 0;
        EVP_PKEY_CTX *ctx = NULL;
@@ -131,7 +134,7 @@ int MAIN(int argc, char **argv)
                                {
                                ctx = init_ctx(&keysize,
                                                *(++argv), keyform, key_type,
-                                               passargin, pkey_op, engine);
+                                               passargin, pkey_op, e);
                                if (!ctx)
                                        {
                                        BIO_puts(bio_err,
@@ -141,11 +144,23 @@ int MAIN(int argc, char **argv)
                                        }
                                }
                        }
+               else if (!strcmp(*argv,"-peerkey"))
+                       {
+                       if (--argc < 1)
+                               badarg = 1;
+                       else if (!setup_peer(bio_err, ctx, peerform, *(++argv)))
+                               badarg = 1;
+                       }
                else if (!strcmp(*argv,"-passin"))
                        {
                        if (--argc < 1) badarg = 1;
                        passargin= *(++argv);
                        }
+               else if (strcmp(*argv,"-peerform") == 0)
+                       {
+                       if (--argc < 1) badarg = 1;
+                       peerform=str2fmt(*(++argv));
+                       }
                else if (strcmp(*argv,"-keyform") == 0)
                        {
                        if (--argc < 1) badarg = 1;
@@ -157,7 +172,7 @@ int MAIN(int argc, char **argv)
                        if (--argc < 1)
                                badarg = 1;
                        else
-                               engine = *(++argv);
+                               e = setup_engine(bio_err, *(++argv), 0);
                        }
 #endif
                else if(!strcmp(*argv, "-pubin"))
@@ -180,14 +195,16 @@ int MAIN(int argc, char **argv)
                        pkey_op = EVP_PKEY_OP_ENCRYPT;
                else if(!strcmp(*argv, "-decrypt"))
                        pkey_op = EVP_PKEY_OP_DECRYPT;
+               else if(!strcmp(*argv, "-derive"))
+                       pkey_op = EVP_PKEY_OP_DERIVE;
                else if (strcmp(*argv,"-pkeyopt") == 0)
                        {
                        if (--argc < 1)
                                badarg = 1;
-                       if (!ctx)
+                       else if (!ctx)
                                {
                                BIO_puts(bio_err,
-                                       "-param command before -inkey\n");
+                                       "-pkeyopt command before -inkey\n");
                                badarg = 1;
                                }
                        else if (pkey_ctrl_string(ctx, *(++argv)) <= 0)
@@ -228,17 +245,21 @@ int MAIN(int argc, char **argv)
 /* FIXME: seed PRNG only if needed */
        app_RAND_load_file(NULL, bio_err, 0);
 
-       if(infile)
+       if (pkey_op != EVP_PKEY_OP_DERIVE)
                {
-               if(!(in = BIO_new_file(infile, "rb")))
+               if(infile)
                        {
-                       BIO_printf(bio_err, "Error Reading Input File\n");
-                       ERR_print_errors(bio_err);      
-                       goto end;
+                       if(!(in = BIO_new_file(infile, "rb")))
+                               {
+                               BIO_puts(bio_err,
+                                       "Error Opening Input File\n");
+                               ERR_print_errors(bio_err);      
+                               goto end;
+                               }
                        }
+               else
+                       in = BIO_new_fp(stdin, BIO_NOCLOSE);
                }
-       else
-               in = BIO_new_fp(stdin, BIO_NOCLOSE);
 
        if(outfile)
                {
@@ -280,24 +301,28 @@ int MAIN(int argc, char **argv)
        
        buf_out = OPENSSL_malloc(keysize);
 
-       /* Read the input data */
-       buf_inlen = bio_to_mem(&buf_in, keysize * 10, in);
-       if(buf_inlen <= 0)
+       if (in)
                {
-               BIO_printf(bio_err, "Error reading input Data\n");
-               exit(1);
-               }
-       if(rev)
-               {
-               int i;
-               unsigned char ctmp;
-               for(i = 0; i < buf_inlen/2; i++)
+               /* Read the input data */
+               buf_inlen = bio_to_mem(&buf_in, keysize * 10, in);
+               if(buf_inlen <= 0)
+                       {
+                       BIO_printf(bio_err, "Error reading input Data\n");
+                       exit(1);
+                       }
+               if(rev)
                        {
-                       ctmp = buf_in[i];
-                       buf_in[i] = buf_in[buf_inlen - 1 - i];
-                       buf_in[buf_inlen - 1 - i] = ctmp;
+                       int i;
+                       unsigned char ctmp;
+                       for(i = 0; i < buf_inlen/2; i++)
+                               {
+                               ctmp = buf_in[i];
+                               buf_in[i] = buf_in[buf_inlen - 1 - i];
+                               buf_in[buf_inlen - 1 - i] = ctmp;
+                               }
                        }
                }
+
        switch(pkey_op)
                {
                case EVP_PKEY_OP_VERIFYRECOVER:
@@ -330,6 +355,10 @@ int MAIN(int argc, char **argv)
                        goto end;
                break; 
 
+               case EVP_PKEY_OP_DERIVE:
+               rv  = EVP_PKEY_derive(ctx, buf_out, &buf_outlen);
+               break;
+
                }
 
        if(rv <= 0)
@@ -387,15 +416,15 @@ static void usage()
 
 static EVP_PKEY_CTX *init_ctx(int *pkeysize,
                                char *keyfile, int keyform, int key_type,
-                               char *passargin, int pkey_op, char *engine)
+                               char *passargin, int pkey_op, ENGINE *e)
        {
-       ENGINE *e = NULL;
        EVP_PKEY *pkey = NULL;
        EVP_PKEY_CTX *ctx = NULL;
        char *passin = NULL;
        int rv = -1;
        X509 *x;
-       if(((pkey_op == EVP_PKEY_OP_SIGN) || (pkey_op == EVP_PKEY_OP_DECRYPT))
+       if(((pkey_op == EVP_PKEY_OP_SIGN) || (pkey_op == EVP_PKEY_OP_DECRYPT) 
+               || (pkey_op == EVP_PKEY_OP_DERIVE))
                && (key_type != KEY_PRIVKEY))
                {
                BIO_printf(bio_err, "A private key is needed for this operation\n");
@@ -435,7 +464,7 @@ static EVP_PKEY_CTX *init_ctx(int *pkeysize,
        if (!pkey)
                goto end;
 
-       ctx = EVP_PKEY_CTX_new(pkey, NULL);
+       ctx = EVP_PKEY_CTX_new(pkey, e);
 
        EVP_PKEY_free(pkey);
 
@@ -463,6 +492,10 @@ static EVP_PKEY_CTX *init_ctx(int *pkeysize,
                case EVP_PKEY_OP_DECRYPT:
                rv = EVP_PKEY_decrypt_init(ctx);
                break;
+
+               case EVP_PKEY_OP_DERIVE:
+               rv = EVP_PKEY_derive_init(ctx);
+               break;
                }
 
        if (rv <= 0)
@@ -481,3 +514,31 @@ static EVP_PKEY_CTX *init_ctx(int *pkeysize,
 
        }
 
+static int setup_peer(BIO *err, EVP_PKEY_CTX *ctx, int peerform,
+                                                       const char *file)
+       {
+       EVP_PKEY *peer = NULL;
+       int ret;
+       if (!ctx)
+               {
+               BIO_puts(err, "-peerkey command before -inkey\n");
+               return 0;
+               }
+               
+       peer = load_pubkey(bio_err, file, peerform, 0, NULL, NULL, "Peer Key");
+
+       if (!peer)
+               {
+               BIO_printf(bio_err, "Error reading peer key %s\n", file);
+               ERR_print_errors(err);
+               return 0;
+               }
+
+       ret = EVP_PKEY_derive_set_peer(ctx, peer);
+
+       EVP_PKEY_free(peer);
+       if (ret <= 0)
+               ERR_print_errors(err);
+       return ret;
+       }
+                       
index b4b181c..10bf3fe 100644 (file)
@@ -234,6 +234,7 @@ void ERR_load_DH_strings(void);
 #define DH_F_DH_PUB_ENCODE                              109
 #define DH_F_GENERATE_KEY                               103
 #define DH_F_GENERATE_PARAMETERS                        104
+#define DH_F_PKEY_DH_DERIVE                             112
 #define DH_F_PKEY_DH_KEYGEN                             113
 
 /* Reason codes. */
@@ -242,6 +243,7 @@ void ERR_load_DH_strings(void);
 #define DH_R_BN_ERROR                                   106
 #define DH_R_DECODE_ERROR                               104
 #define DH_R_INVALID_PUBKEY                             102
+#define DH_R_KEYS_NOT_SET                               108
 #define DH_R_NO_PARAMETERS_SET                          107
 #define DH_R_NO_PRIVATE_VALUE                           100
 #define DH_R_PARAMETER_ENCODING_ERROR                   105
index 6d31257..7a83768 100644 (file)
@@ -397,6 +397,15 @@ static int dh_bits(const EVP_PKEY *pkey)
        return BN_num_bits(pkey->pkey.dh->p);
        }
 
+static int dh_cmp_parameters(const EVP_PKEY *a, const EVP_PKEY *b)
+       {
+       if (    BN_cmp(a->pkey.dh->p,b->pkey.dh->p) ||
+               BN_cmp(a->pkey.dh->g,b->pkey.dh->g))
+               return 0;
+       else
+               return 1;
+       }
+
 static int dh_copy_parameters(EVP_PKEY *to, const EVP_PKEY *from)
        {
        BIGNUM *a;
@@ -409,20 +418,18 @@ static int dh_copy_parameters(EVP_PKEY *to, const EVP_PKEY *from)
 
        if ((a=BN_dup(from->pkey.dh->g)) == NULL)
                return 0;
-       if (to->pkey.dsa->g != NULL)
+       if (to->pkey.dh->g != NULL)
                BN_free(to->pkey.dh->g);
        to->pkey.dh->g=a;
 
        return 1;
        }
 
-static int dh_cmp_parameters(const EVP_PKEY *a, const EVP_PKEY *b)
+static int dh_missing_parameters(const EVP_PKEY *a)
        {
-       if (    BN_cmp(a->pkey.dh->p,b->pkey.dsa->p) ||
-               BN_cmp(a->pkey.dh->g,b->pkey.dsa->g))
-               return 0;
-       else
+       if (!a->pkey.dh->p || !a->pkey.dh->g)
                return 1;
+       return 0;
        }
 
 static int dh_pub_cmp(const EVP_PKEY *a, const EVP_PKEY *b)
@@ -481,7 +488,7 @@ const EVP_PKEY_ASN1_METHOD dh_asn1_meth =
 
        dh_param_decode,
        dh_param_encode,
-       0,
+       dh_missing_parameters,
        dh_copy_parameters,
        dh_cmp_parameters,
        dh_param_print,
index 64acfde..2fdfc55 100644 (file)
@@ -82,6 +82,7 @@ static ERR_STRING_DATA DH_str_functs[]=
 {ERR_FUNC(DH_F_DH_PUB_ENCODE), "DH_PUB_ENCODE"},
 {ERR_FUNC(DH_F_GENERATE_KEY),  "GENERATE_KEY"},
 {ERR_FUNC(DH_F_GENERATE_PARAMETERS),   "GENERATE_PARAMETERS"},
+{ERR_FUNC(DH_F_PKEY_DH_DERIVE),        "PKEY_DH_DERIVE"},
 {ERR_FUNC(DH_F_PKEY_DH_KEYGEN),        "PKEY_DH_KEYGEN"},
 {0,NULL}
        };
@@ -93,6 +94,7 @@ static ERR_STRING_DATA DH_str_reasons[]=
 {ERR_REASON(DH_R_BN_ERROR)               ,"bn error"},
 {ERR_REASON(DH_R_DECODE_ERROR)           ,"decode error"},
 {ERR_REASON(DH_R_INVALID_PUBKEY)         ,"invalid public key"},
+{ERR_REASON(DH_R_KEYS_NOT_SET)           ,"keys not set"},
 {ERR_REASON(DH_R_NO_PARAMETERS_SET)      ,"no parameters set"},
 {ERR_REASON(DH_R_NO_PRIVATE_VALUE)       ,"no private value"},
 {ERR_REASON(DH_R_PARAMETER_ENCODING_ERROR),"parameter encoding error"},
index d2e6aaf..aaf3280 100644 (file)
@@ -115,11 +115,16 @@ static int pkey_dh_ctrl(EVP_PKEY_CTX *ctx, int type, int p1, void *p2)
                dctx->generator = p1;
                return 1;
 
+               case EVP_PKEY_CTRL_PEER_KEY:
+               /* Default behaviour is OK */
+               return 1;
+
                default:
                return -2;
 
                }
        }
+
                        
 static int pkey_dh_ctrl_str(EVP_PKEY_CTX *ctx,
                        const char *type, const char *value)
@@ -182,6 +187,22 @@ static int pkey_dh_keygen(EVP_PKEY_CTX *ctx, EVP_PKEY *pkey)
        return DH_generate_key(pkey->pkey.dh);
        }
 
+static int pkey_dh_derive(EVP_PKEY_CTX *ctx, unsigned char *key, int *keylen)
+       {
+       int ret;
+       if (!ctx->pkey || !ctx->peerkey)
+               {
+               DHerr(DH_F_PKEY_DH_DERIVE, DH_R_KEYS_NOT_SET);
+               return 0;
+               }
+       ret = DH_compute_key(key, ctx->peerkey->pkey.dh->pub_key,
+                                                       ctx->pkey->pkey.dh);
+       if (ret < 0)
+               return ret;
+       *keylen = ret;
+       return 1;
+       }
+
 const EVP_PKEY_METHOD dh_pkey_meth = 
        {
        EVP_PKEY_DH,
@@ -209,7 +230,8 @@ const EVP_PKEY_METHOD dh_pkey_meth =
 
        0,0,
 
-       0,0,
+       0,
+       pkey_dh_derive,
 
        pkey_dh_ctrl,
        pkey_dh_ctrl_str
index d607816..bfffdd5 100644 (file)
@@ -933,6 +933,7 @@ void EVP_PKEY_asn1_set_ctrl(EVP_PKEY_ASN1_METHOD *ameth,
                                        EVP_PKEY_CTRL_MD, 0, (void *)md)
 
 #define EVP_PKEY_CTRL_MD               1
+#define EVP_PKEY_CTRL_PEER_KEY         2
 
 #define EVP_PKEY_ALG_CTRL              0x1000
 
@@ -972,6 +973,10 @@ int EVP_PKEY_decrypt(EVP_PKEY_CTX *ctx,
                        unsigned char *out, int *outlen,
                        const unsigned char *in, int inlen);
 
+int EVP_PKEY_derive_init(EVP_PKEY_CTX *ctx);
+int EVP_PKEY_derive_set_peer(EVP_PKEY_CTX *ctx, EVP_PKEY *peer);
+int EVP_PKEY_derive(EVP_PKEY_CTX *ctx, unsigned char *key, int *keylen);
+
 typedef int EVP_PKEY_gen_cb(EVP_PKEY_CTX *ctx);
 
 int EVP_PKEY_paramgen_init(EVP_PKEY_CTX *ctx);
@@ -1018,6 +1023,7 @@ void ERR_load_EVP_strings(void);
 #define EVP_F_EVP_PKEY_DECRYPT_OLD                      151
 #define EVP_F_EVP_PKEY_DERIVE                           153
 #define EVP_F_EVP_PKEY_DERIVE_INIT                      154
+#define EVP_F_EVP_PKEY_DERIVE_SET_PEER                  155
 #define EVP_F_EVP_PKEY_ENCRYPT                          105
 #define EVP_F_EVP_PKEY_ENCRYPT_INIT                     139
 #define EVP_F_EVP_PKEY_ENCRYPT_OLD                      152
@@ -1061,6 +1067,7 @@ void ERR_load_EVP_strings(void);
 #define EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH                 138
 #define EVP_R_DECODE_ERROR                              114
 #define EVP_R_DIFFERENT_KEY_TYPES                       101
+#define EVP_R_DIFFERENT_PARAMETERS                      153
 #define EVP_R_ENCODE_ERROR                              115
 #define EVP_R_EVP_PBE_CIPHERINIT_ERROR                  119
 #define EVP_R_EXPECTING_AN_RSA_KEY                      127
@@ -1080,6 +1087,7 @@ void ERR_load_EVP_strings(void);
 #define EVP_R_NO_CIPHER_SET                             131
 #define EVP_R_NO_DIGEST_SET                             139
 #define EVP_R_NO_DSA_PARAMETERS                                 116
+#define EVP_R_NO_KEY_SET                                154
 #define EVP_R_NO_OPERATION_SET                          149
 #define EVP_R_NO_SIGN_FUNCTION_CONFIGURED               104
 #define EVP_R_NO_VERIFY_FUNCTION_CONFIGURED             105
index 50d5a71..225564d 100644 (file)
@@ -97,6 +97,7 @@ static ERR_STRING_DATA EVP_str_functs[]=
 {ERR_FUNC(EVP_F_EVP_PKEY_DECRYPT_OLD), "EVP_PKEY_decrypt_old"},
 {ERR_FUNC(EVP_F_EVP_PKEY_DERIVE),      "EVP_PKEY_DERIVE"},
 {ERR_FUNC(EVP_F_EVP_PKEY_DERIVE_INIT), "EVP_PKEY_DERIVE_INIT"},
+{ERR_FUNC(EVP_F_EVP_PKEY_DERIVE_SET_PEER),     "EVP_PKEY_DERIVE_SET_PEER"},
 {ERR_FUNC(EVP_F_EVP_PKEY_ENCRYPT),     "EVP_PKEY_encrypt"},
 {ERR_FUNC(EVP_F_EVP_PKEY_ENCRYPT_INIT),        "EVP_PKEY_encrypt_init"},
 {ERR_FUNC(EVP_F_EVP_PKEY_ENCRYPT_OLD), "EVP_PKEY_encrypt_old"},
@@ -143,6 +144,7 @@ static ERR_STRING_DATA EVP_str_reasons[]=
 {ERR_REASON(EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH),"data not multiple of block length"},
 {ERR_REASON(EVP_R_DECODE_ERROR)          ,"decode error"},
 {ERR_REASON(EVP_R_DIFFERENT_KEY_TYPES)   ,"different key types"},
+{ERR_REASON(EVP_R_DIFFERENT_PARAMETERS)  ,"different parameters"},
 {ERR_REASON(EVP_R_ENCODE_ERROR)          ,"encode error"},
 {ERR_REASON(EVP_R_EVP_PBE_CIPHERINIT_ERROR),"evp pbe cipherinit error"},
 {ERR_REASON(EVP_R_EXPECTING_AN_RSA_KEY)  ,"expecting an rsa key"},
@@ -162,6 +164,7 @@ static ERR_STRING_DATA EVP_str_reasons[]=
 {ERR_REASON(EVP_R_NO_CIPHER_SET)         ,"no cipher set"},
 {ERR_REASON(EVP_R_NO_DIGEST_SET)         ,"no digest set"},
 {ERR_REASON(EVP_R_NO_DSA_PARAMETERS)     ,"no dsa parameters"},
+{ERR_REASON(EVP_R_NO_KEY_SET)            ,"no key set"},
 {ERR_REASON(EVP_R_NO_OPERATION_SET)      ,"no operation set"},
 {ERR_REASON(EVP_R_NO_SIGN_FUNCTION_CONFIGURED),"no sign function configured"},
 {ERR_REASON(EVP_R_NO_VERIFY_FUNCTION_CONFIGURED),"no verify function configured"},
index 0ad8098..3d09ba2 100644 (file)
@@ -262,6 +262,66 @@ int EVP_PKEY_derive_init(EVP_PKEY_CTX *ctx)
        return ret;
        }
 
+int EVP_PKEY_derive_set_peer(EVP_PKEY_CTX *ctx, EVP_PKEY *peer)
+       {
+       int ret;
+       if (!ctx || !ctx->pmeth || !ctx->pmeth->derive || !ctx->pmeth->ctrl)
+               {
+               EVPerr(EVP_F_EVP_PKEY_DERIVE_SET_PEER,
+                       EVP_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE);
+               return -2;
+               }
+       if (ctx->operation != EVP_PKEY_OP_DERIVE)
+               {
+               EVPerr(EVP_F_EVP_PKEY_DERIVE_SET_PEER,
+                                       EVP_R_OPERATON_NOT_INITIALIZED);
+               return -1;
+               }
+
+       ret = ctx->pmeth->ctrl(ctx, EVP_PKEY_CTRL_PEER_KEY, 0, peer);
+
+       if (ret <= 0)
+               return ret;
+
+       if (ret == 2)
+               return 1;
+
+       if (!ctx->pkey)
+               {
+               EVPerr(EVP_F_EVP_PKEY_DERIVE_SET_PEER, EVP_R_NO_KEY_SET);
+               return -1;
+               }
+
+       if (ctx->pkey->type != peer->type)
+               {
+               EVPerr(EVP_F_EVP_PKEY_DERIVE_SET_PEER,
+                                               EVP_R_DIFFERENT_KEY_TYPES);
+               return -1;
+               }
+
+       if (!EVP_PKEY_missing_parameters(peer) &&
+               !EVP_PKEY_cmp_parameters(ctx->pkey, peer))
+               {
+               EVPerr(EVP_F_EVP_PKEY_DERIVE_SET_PEER,
+                                               EVP_R_DIFFERENT_PARAMETERS);
+               return -1;
+               }
+
+       ctx->peerkey = peer;
+
+       ret = ctx->pmeth->ctrl(ctx, EVP_PKEY_CTRL_PEER_KEY, 1, peer);
+
+       if (ret <= 0)
+               {
+               ctx->peerkey = NULL;
+               return ret;
+               }
+
+       CRYPTO_add(&peer->references,1,CRYPTO_LOCK_EVP_PKEY);
+       return 1;
+       }
+
+
 int EVP_PKEY_derive(EVP_PKEY_CTX *ctx, unsigned char *key, int *pkeylen)
        {
        if (!ctx || !ctx->pmeth || !ctx->pmeth->derive)
index 95b1e4e..97fcf26 100644 (file)
@@ -153,6 +153,8 @@ void EVP_PKEY_CTX_free(EVP_PKEY_CTX *ctx)
                ctx->pmeth->cleanup(ctx);
        if (ctx->pkey)
                EVP_PKEY_free(ctx->pkey);
+       if (ctx->peerkey)
+               EVP_PKEY_free(ctx->peerkey);
        OPENSSL_free(ctx);
        }