diff options
-rw-r--r-- | CHANGES | 9 | ||||
-rw-r--r-- | apps/apps.c | 27 | ||||
-rw-r--r-- | apps/ca.c | 181 | ||||
-rw-r--r-- | crypto/asn1/a_strex.c | 16 | ||||
-rw-r--r-- | crypto/asn1/t_x509.c | 2 | ||||
-rw-r--r-- | crypto/x509/x509.h | 5 | ||||
-rw-r--r-- | crypto/x509v3/v3_utl.c | 2 | ||||
-rw-r--r-- | crypto/x509v3/x509v3.h | 2 |
8 files changed, 197 insertions, 47 deletions
@@ -3,6 +3,15 @@ Changes between 0.9.6 and 0.9.7 [xx XXX 2000] + *) Allow multiple 'certopt' and 'nameopt' options to be separated + by commas. Add 'namopt' and 'certopt' options to the 'ca' config + file: this allows the display of the certificate about to be + signed to be customised, to allow certain fields to be included + or excluded and extension details. The old system didn't display + multicharacter strings properly, omitted fields not in the policy + and couldn't display additional details such as extensions. + [Steve Henson] + *) Fix memory leaks in err.c: free err_data string if necessary; don't write to the wrong index in ERR_set_error_data. [Bodo Moeller] diff --git a/apps/apps.c b/apps/apps.c index e4ca8b8ccd..ab30e53ba5 100644 --- a/apps/apps.c +++ b/apps/apps.c @@ -66,6 +66,7 @@ #undef NON_MAIN #include <openssl/err.h> #include <openssl/x509.h> +#include <openssl/x509v3.h> #include <openssl/pem.h> #include <openssl/pkcs12.h> #include <openssl/safestack.h> @@ -87,6 +88,7 @@ typedef struct { } NAME_EX_TBL; static int set_table_opts(unsigned long *flags, const char *arg, const NAME_EX_TBL *in_tbl); +static int set_multi_opts(unsigned long *flags, const char *arg, const NAME_EX_TBL *in_tbl); int app_init(long mesgwin); #ifdef undef /* never finished - probably never will be :-) */ @@ -744,10 +746,14 @@ end: /* BIO_dump unknown extensions */ #define X509V3_EXT_DUMP_UNKNOWN (3L << 16) +#define X509_FLAG_CA (X509_FLAG_NO_ISSUER | X509_FLAG_NO_PUBKEY | \ + X509_FLAG_NO_HEADER | X509_FLAG_NO_VERSION) + int set_cert_ex(unsigned long *flags, const char *arg) { static const NAME_EX_TBL cert_tbl[] = { { "compatible", X509_FLAG_COMPAT, 0xffffffffl}, + { "ca_default", X509_FLAG_CA, 0xffffffffl}, { "no_header", X509_FLAG_NO_HEADER, 0}, { "no_version", X509_FLAG_NO_VERSION, 0}, { "no_serial", X509_FLAG_NO_SERIAL, 0}, @@ -765,7 +771,7 @@ int set_cert_ex(unsigned long *flags, const char *arg) { "ext_dump", X509V3_EXT_DUMP_UNKNOWN, X509V3_EXT_UNKNOWN_MASK}, { NULL, 0, 0} }; - return set_table_opts(flags, arg, cert_tbl); + return set_multi_opts(flags, arg, cert_tbl); } int set_name_ex(unsigned long *flags, const char *arg) @@ -796,9 +802,26 @@ int set_name_ex(unsigned long *flags, const char *arg) { "RFC2253", XN_FLAG_RFC2253, 0xffffffffL}, { "oneline", XN_FLAG_ONELINE, 0xffffffffL}, { "multiline", XN_FLAG_MULTILINE, 0xffffffffL}, + { "ca_default", XN_FLAG_MULTILINE, 0xffffffffL}, { NULL, 0, 0} }; - return set_table_opts(flags, arg, ex_tbl); + return set_multi_opts(flags, arg, ex_tbl); +} + +static int set_multi_opts(unsigned long *flags, const char *arg, const NAME_EX_TBL *in_tbl) +{ + STACK_OF(CONF_VALUE) *vals; + CONF_VALUE *val; + int i, ret = 1; + if(!arg) return 0; + vals = X509V3_parse_list(arg); + for (i = 0; i < sk_CONF_VALUE_num(vals); i++) { + val = sk_CONF_VALUE_value(vals, i); + if (!set_table_opts(flags, val->name, in_tbl)) + ret = 0; + } + sk_CONF_VALUE_pop_free(vals, X509V3_conf_free); + return ret; } static int set_table_opts(unsigned long *flags, const char *arg, const NAME_EX_TBL *in_tbl) @@ -132,6 +132,8 @@ #define ENV_EXTENSIONS "x509_extensions" #define ENV_CRLEXT "crl_extensions" #define ENV_MSIE_HACK "msie_hack" +#define ENV_NAMEOPT "name_opt" +#define ENV_CERTOPT "cert_opt" #define ENV_DATABASE "database" @@ -210,23 +212,29 @@ static int save_serial(char *serialfile, BIGNUM *serial); static int certify(X509 **xret, char *infile,EVP_PKEY *pkey,X509 *x509, const EVP_MD *dgst,STACK_OF(CONF_VALUE) *policy,TXT_DB *db, BIGNUM *serial, char *subj, char *startdate,char *enddate, - int days, int batch, char *ext_sect, LHASH *conf,int verbose); + int days, int batch, char *ext_sect, LHASH *conf,int verbose, + unsigned long certopt, unsigned long nameopt, int default_op, + int ext_copy); static int certify_cert(X509 **xret, char *infile,EVP_PKEY *pkey,X509 *x509, const EVP_MD *dgst,STACK_OF(CONF_VALUE) *policy, TXT_DB *db, BIGNUM *serial, char *subj, char *startdate, char *enddate, int days, int batch, char *ext_sect, - LHASH *conf,int verbose); + LHASH *conf,int verbose, unsigned long certopt, + unsigned long nameopt, int default_op, int ext_copy); static int certify_spkac(X509 **xret, char *infile,EVP_PKEY *pkey,X509 *x509, const EVP_MD *dgst,STACK_OF(CONF_VALUE) *policy, TXT_DB *db, BIGNUM *serial,char *subj, char *startdate, char *enddate, int days, char *ext_sect,LHASH *conf, - int verbose); + int verbose, unsigned long certopt, unsigned long nameopt, + int default_op, int ext_copy); static int fix_data(int nid, int *type); static void write_new_certificate(BIO *bp, X509 *x, int output_der, int notext); static int do_body(X509 **xret, EVP_PKEY *pkey, X509 *x509, const EVP_MD *dgst, STACK_OF(CONF_VALUE) *policy, TXT_DB *db, BIGNUM *serial,char *subj, char *startdate, char *enddate, int days, int batch, int verbose, - X509_REQ *req, char *ext_sect, LHASH *conf); + X509_REQ *req, char *ext_sect, LHASH *conf, + unsigned long certopt, unsigned long nameopt, int default_op, + int ext_copy); static X509_NAME *do_subject(char *subject); static int do_revoke(X509 *x509, TXT_DB *db, int ext, char *extval); static int get_certificate_status(const char *ser_status, TXT_DB *db); @@ -234,6 +242,7 @@ static int do_updatedb(TXT_DB *db); static int check_time_format(char *str); char *make_revocation_str(int rev_type, char *rev_arg); int make_revoked(X509_REVOKED *rev, char *str); +int old_entry_print(BIO *bp, ASN1_OBJECT *obj, ASN1_STRING *str); static LHASH *conf=NULL; static LHASH *extconf=NULL; static char *section=NULL; @@ -292,6 +301,9 @@ int MAIN(int argc, char **argv) int days=0; int batch=0; int notext=0; + unsigned long nameopt = 0, certopt = 0; + int default_op = 1; + int ext_copy = 0; X509 *x509=NULL; X509 *x=NULL; BIO *in=NULL,*out=NULL,*Sout=NULL,*Cout=NULL; @@ -758,6 +770,35 @@ bad: if ((f != NULL) && ((*f == 'y') || (*f == 'Y'))) msie_hack=1; + f=CONF_get_string(conf,section,ENV_NAMEOPT); + + if (f) + { + if (!set_name_ex(&nameopt, f)) + { + BIO_printf(bio_err, "Invalid name options: \"%s\"\n", f); + goto err; + } + default_op = 0; + } + else + ERR_clear_error(); + + f=CONF_get_string(conf,section,ENV_CERTOPT); + + if (f) + { + if (!set_cert_ex(&certopt, f)) + { + BIO_printf(bio_err, "Invalid certificate options: \"%s\"\n", f); + goto err; + } + default_op = 0; + } + else + ERR_clear_error(); + + /*****************************************************************/ /* lookup where to write new certificates */ if ((outdir == NULL) && (req)) @@ -1148,7 +1189,7 @@ bad: total++; j=certify_spkac(&x,spkac_file,pkey,x509,dgst,attribs,db, serial,subj,startdate,enddate, days,extensions,conf, - verbose); + verbose, certopt, nameopt, default_op, ext_copy); if (j < 0) goto err; if (j > 0) { @@ -1172,7 +1213,8 @@ bad: total++; j=certify_cert(&x,ss_cert_file,pkey,x509,dgst,attribs, db,serial,subj,startdate,enddate,days,batch, - extensions,conf,verbose); + extensions,conf,verbose, certopt, nameopt, + default_op, ext_copy); if (j < 0) goto err; if (j > 0) { @@ -1191,7 +1233,8 @@ bad: total++; j=certify(&x,infile,pkey,x509,dgst,attribs,db, serial,subj,startdate,enddate,days,batch, - extensions,conf,verbose); + extensions,conf,verbose, certopt, nameopt, + default_op, ext_copy); if (j < 0) goto err; if (j > 0) { @@ -1210,7 +1253,8 @@ bad: total++; j=certify(&x,argv[i],pkey,x509,dgst,attribs,db, serial,subj,startdate,enddate,days,batch, - extensions,conf,verbose); + extensions,conf,verbose, certopt, nameopt, + default_op, ext_copy); if (j < 0) goto err; if (j > 0) { @@ -1684,7 +1728,9 @@ err: static int certify(X509 **xret, char *infile, EVP_PKEY *pkey, X509 *x509, const EVP_MD *dgst, STACK_OF(CONF_VALUE) *policy, TXT_DB *db, BIGNUM *serial, char *subj, char *startdate, char *enddate, int days, - int batch, char *ext_sect, LHASH *lconf, int verbose) + int batch, char *ext_sect, LHASH *lconf, int verbose, + unsigned long certopt, unsigned long nameopt, int default_op, + int ext_copy) { X509_REQ *req=NULL; BIO *in=NULL; @@ -1732,7 +1778,8 @@ static int certify(X509 **xret, char *infile, EVP_PKEY *pkey, X509 *x509, BIO_printf(bio_err,"Signature ok\n"); ok=do_body(xret,pkey,x509,dgst,policy,db,serial,subj,startdate, enddate, - days,batch,verbose,req,ext_sect,lconf); + days,batch,verbose,req,ext_sect,lconf, + certopt, nameopt, default_op, ext_copy); err: if (req != NULL) X509_REQ_free(req); @@ -1743,7 +1790,9 @@ err: static int certify_cert(X509 **xret, char *infile, EVP_PKEY *pkey, X509 *x509, const EVP_MD *dgst, STACK_OF(CONF_VALUE) *policy, TXT_DB *db, BIGNUM *serial, char *subj, char *startdate, char *enddate, int days, - int batch, char *ext_sect, LHASH *lconf, int verbose) + int batch, char *ext_sect, LHASH *lconf, int verbose, + unsigned long certopt, unsigned long nameopt, int default_op, + int ext_copy) { X509 *req=NULL; X509_REQ *rreq=NULL; @@ -1794,7 +1843,8 @@ static int certify_cert(X509 **xret, char *infile, EVP_PKEY *pkey, X509 *x509, goto err; ok=do_body(xret,pkey,x509,dgst,policy,db,serial,subj,startdate,enddate,days, - batch,verbose,rreq,ext_sect,lconf); + batch,verbose,rreq,ext_sect,lconf, certopt, nameopt, default_op, + ext_copy); err: if (rreq != NULL) X509_REQ_free(rreq); @@ -1806,7 +1856,9 @@ err: static int do_body(X509 **xret, EVP_PKEY *pkey, X509 *x509, const EVP_MD *dgst, STACK_OF(CONF_VALUE) *policy, TXT_DB *db, BIGNUM *serial, char *subj, char *startdate, char *enddate, int days, int batch, int verbose, - X509_REQ *req, char *ext_sect, LHASH *lconf) + X509_REQ *req, char *ext_sect, LHASH *lconf, + unsigned long certopt, unsigned long nameopt, int default_op, + int ext_copy) { X509_NAME *name=NULL,*CAname=NULL,*subject=NULL; ASN1_UTCTIME *tm,*tmptm; @@ -1821,7 +1873,7 @@ static int do_body(X509 **xret, EVP_PKEY *pkey, X509 *x509, const EVP_MD *dgst, char *p; CONF_VALUE *cv; char *row[DB_NUMBER],**rrow,**irow=NULL; - char buf[25],*pbuf; + char buf[25]; tmptm=ASN1_UTCTIME_new(); if (tmptm == NULL) @@ -1847,20 +1899,14 @@ static int do_body(X509 **xret, EVP_PKEY *pkey, X509 *x509, const EVP_MD *dgst, X509_NAME_free(n); } - BIO_printf(bio_err,"The Subject's Distinguished Name is as follows\n"); + if (default_op) + BIO_printf(bio_err,"The Subject's Distinguished Name is as follows\n"); name=X509_REQ_get_subject_name(req); for (i=0; i<X509_NAME_entry_count(name); i++) { - ne=(X509_NAME_ENTRY *)X509_NAME_get_entry(name,i); - obj=X509_NAME_ENTRY_get_object(ne); - j=i2a_ASN1_OBJECT(bio_err,obj); + ne= X509_NAME_get_entry(name,i); str=X509_NAME_ENTRY_get_data(ne); - pbuf=buf; - for (j=22-j; j>0; j--) - *(pbuf++)=' '; - *(pbuf++)=':'; - *(pbuf++)='\0'; - BIO_puts(bio_err,buf); + obj=X509_NAME_ENTRY_get_object(ne); if (msie_hack) { @@ -1879,17 +1925,6 @@ static int do_body(X509 **xret, EVP_PKEY *pkey, X509 *x509, const EVP_MD *dgst, str->type=V_ASN1_IA5STRING; } - if (str->type == V_ASN1_PRINTABLESTRING) - BIO_printf(bio_err,"PRINTABLE:'"); - else if (str->type == V_ASN1_T61STRING) - BIO_printf(bio_err,"T61STRING:'"); - else if (str->type == V_ASN1_IA5STRING) - BIO_printf(bio_err,"IA5STRING:'"); - else if (str->type == V_ASN1_UNIVERSALSTRING) - BIO_printf(bio_err,"UNIVERSALSTRING:'"); - else - BIO_printf(bio_err,"ASN.1 %2d:'",str->type); - /* check some things */ if ((OBJ_obj2nid(obj) == NID_pkcs9_emailAddress) && (str->type != V_ASN1_IA5STRING)) @@ -1906,6 +1941,28 @@ static int do_body(X509 **xret, EVP_PKEY *pkey, X509 *x509, const EVP_MD *dgst, BIO_printf(bio_err,"\nThe string contains characters that are illegal for the ASN.1 type\n"); goto err; } + + if (default_op) + old_entry_print(bio_err, obj, str); +#if 0 + j=i2a_ASN1_OBJECT(bio_err,obj); + pbuf=buf; + for (j=22-j; j>0; j--) + *(pbuf++)=' '; + *(pbuf++)=':'; + *(pbuf++)='\0'; + BIO_puts(bio_err,buf); + + if (str->type == V_ASN1_PRINTABLESTRING) + BIO_printf(bio_err,"PRINTABLE:'"); + else if (str->type == V_ASN1_T61STRING) + BIO_printf(bio_err,"T61STRING:'"); + else if (str->type == V_ASN1_IA5STRING) + BIO_printf(bio_err,"IA5STRING:'"); + else if (str->type == V_ASN1_UNIVERSALSTRING) + BIO_printf(bio_err,"UNIVERSALSTRING:'"); + else + BIO_printf(bio_err,"ASN.1 %2d:'",str->type); p=(char *)str->data; for (j=str->length; j>0; j--) @@ -1920,6 +1977,7 @@ static int do_body(X509 **xret, EVP_PKEY *pkey, X509 *x509, const EVP_MD *dgst, p++; } BIO_printf(bio_err,"'\n"); +#endif } /* Ok, now we check the 'policy' stuff. */ @@ -2196,6 +2254,13 @@ again2: if (!batch) { + if (!default_op) + { + BIO_printf(bio_err, "Certificate Details:\n"); + /* Never print signature details because signature not present */ + certopt |= X509_FLAG_NO_SIGDUMP | X509_FLAG_NO_SIGNAME; + X509_print_ex(bio_err, ret, nameopt, certopt); + } BIO_printf(bio_err,"Sign the certificate? [y/n]:"); (void)BIO_flush(bio_err); buf[0]='\0'; @@ -2312,7 +2377,8 @@ static void write_new_certificate(BIO *bp, X509 *x, int output_der, int notext) static int certify_spkac(X509 **xret, char *infile, EVP_PKEY *pkey, X509 *x509, const EVP_MD *dgst, STACK_OF(CONF_VALUE) *policy, TXT_DB *db, BIGNUM *serial, char *subj, char *startdate, char *enddate, int days, - char *ext_sect, LHASH *lconf, int verbose) + char *ext_sect, LHASH *lconf, int verbose, unsigned long certopt, + unsigned long nameopt, int default_op, int ext_copy) { STACK_OF(CONF_VALUE) *sk=NULL; LHASH *parms=NULL; @@ -2447,7 +2513,8 @@ static int certify_spkac(X509 **xret, char *infile, EVP_PKEY *pkey, X509 *x509, X509_REQ_set_pubkey(req,pktmp); EVP_PKEY_free(pktmp); ok=do_body(xret,pkey,x509,dgst,policy,db,serial,subj,startdate,enddate, - days,1,verbose,req,ext_sect,lconf); + days,1,verbose,req,ext_sect,lconf, certopt, nameopt, default_op, + ext_copy); err: if (req != NULL) X509_REQ_free(req); if (parms != NULL) CONF_free(parms); @@ -3055,3 +3122,43 @@ static X509_NAME *do_subject(char *subject) return n; } + + +int old_entry_print(BIO *bp, ASN1_OBJECT *obj, ASN1_STRING *str) + { + char buf[25],*pbuf, *p; + int j; + j=i2a_ASN1_OBJECT(bp,obj); + pbuf=buf; + for (j=22-j; j>0; j--) + *(pbuf++)=' '; + *(pbuf++)=':'; + *(pbuf++)='\0'; + BIO_puts(bp,buf); + + if (str->type == V_ASN1_PRINTABLESTRING) + BIO_printf(bp,"PRINTABLE:'"); + else if (str->type == V_ASN1_T61STRING) + BIO_printf(bp,"T61STRING:'"); + else if (str->type == V_ASN1_IA5STRING) + BIO_printf(bp,"IA5STRING:'"); + else if (str->type == V_ASN1_UNIVERSALSTRING) + BIO_printf(bp,"UNIVERSALSTRING:'"); + else + BIO_printf(bp,"ASN.1 %2d:'",str->type); + + p=(char *)str->data; + for (j=str->length; j>0; j--) + { + if ((*p >= ' ') && (*p <= '~')) + BIO_printf(bp,"%c",*p); + else if (*p & 0x80) + BIO_printf(bp,"\\0x%02X",*p); + else if ((unsigned char)*p == 0xf7) + BIO_printf(bp,"^?"); + else BIO_printf(bp,"^%c",*p+'@'); + p++; + } + BIO_printf(bp,"'\n"); + return 1; + } diff --git a/crypto/asn1/a_strex.c b/crypto/asn1/a_strex.c index 5335538ae0..defc4e7ecf 100644 --- a/crypto/asn1/a_strex.c +++ b/crypto/asn1/a_strex.c @@ -371,6 +371,8 @@ static int do_indent(char_io *io_ch, void *arg, int indent) return 1; } +#define FN_WIDTH_LN 25 +#define FN_WIDTH_SN 10 static int do_name_ex(char_io *io_ch, void *arg, X509_NAME *n, int indent, unsigned long flags) @@ -456,19 +458,25 @@ static int do_name_ex(char_io *io_ch, void *arg, X509_NAME *n, val = X509_NAME_ENTRY_get_data(ent); fn_nid = OBJ_obj2nid(fn); if(fn_opt != XN_FLAG_FN_NONE) { - int objlen; + int objlen, fld_len; if((fn_opt == XN_FLAG_FN_OID) || (fn_nid==NID_undef) ) { OBJ_obj2txt(objtmp, 80, fn, 1); objbuf = objtmp; } else { - if(fn_opt == XN_FLAG_FN_SN) + if(fn_opt == XN_FLAG_FN_SN) { + fld_len = FN_WIDTH_SN; objbuf = OBJ_nid2sn(fn_nid); - else if(fn_opt == XN_FLAG_FN_LN) + } else if(fn_opt == XN_FLAG_FN_LN) { + fld_len = FN_WIDTH_LN; objbuf = OBJ_nid2ln(fn_nid); - else objbuf = ""; + } else objbuf = ""; } objlen = strlen(objbuf); if(!io_ch(arg, objbuf, objlen)) return -1; + if ((objlen < fld_len) && (flags & XN_FLAG_FN_ALIGN)) { + if (!do_indent(io_ch, arg, fld_len - objlen)) return -1; + outlen += fld_len - objlen; + } if(!io_ch(arg, sep_eq, sep_eq_len)) return -1; outlen += objlen + sep_eq_len; } diff --git a/crypto/asn1/t_x509.c b/crypto/asn1/t_x509.c index bd019bb9b1..17ed9f2f7f 100644 --- a/crypto/asn1/t_x509.c +++ b/crypto/asn1/t_x509.c @@ -112,7 +112,7 @@ int X509_print_ex(BIO *bp, X509 *x, unsigned long nmflags, unsigned long cflag) if((nmflags & XN_FLAG_SEP_MASK) == XN_FLAG_SEP_MULTILINE) { mlch = '\n'; - nmindent = 16; + nmindent = 12; } if(nmflags == X509_FLAG_COMPAT) diff --git a/crypto/x509/x509.h b/crypto/x509/x509.h index 1aeea2003d..2b9d1050b7 100644 --- a/crypto/x509/x509.h +++ b/crypto/x509/x509.h @@ -364,6 +364,8 @@ DECLARE_STACK_OF(X509_TRUST) #define XN_FLAG_DUMP_UNKNOWN_FIELDS (1 << 24) +#define XN_FLAG_FN_ALIGN (1 << 25) /* Align field names to 20 characters */ + /* Complete set of RFC2253 flags */ #define XN_FLAG_RFC2253 (ASN1_STRFLGS_RFC2253 | \ @@ -386,7 +388,8 @@ DECLARE_STACK_OF(X509_TRUST) ASN1_STRFLGS_ESC_MSB | \ XN_FLAG_SEP_MULTILINE | \ XN_FLAG_SPC_EQ | \ - XN_FLAG_FN_LN) + XN_FLAG_FN_LN | \ + XN_FLAG_FN_ALIGN) typedef struct X509_revoked_st { diff --git a/crypto/x509v3/v3_utl.c b/crypto/x509v3/v3_utl.c index 434ddbbc3c..3e3d7ac916 100644 --- a/crypto/x509v3/v3_utl.c +++ b/crypto/x509v3/v3_utl.c @@ -240,7 +240,7 @@ int X509V3_get_value_int(CONF_VALUE *value, ASN1_INTEGER **aint) /*#define DEBUG*/ -STACK_OF(CONF_VALUE) *X509V3_parse_list(char *line) +STACK_OF(CONF_VALUE) *X509V3_parse_list(const char *line) { char *p, *q, c; char *ntmp, *vtmp; diff --git a/crypto/x509v3/x509v3.h b/crypto/x509v3/x509v3.h index da770dcd7c..6b186de724 100644 --- a/crypto/x509v3/x509v3.h +++ b/crypto/x509v3/x509v3.h @@ -498,7 +498,7 @@ void X509V3_EXT_cleanup(void); X509V3_EXT_METHOD *X509V3_EXT_get(X509_EXTENSION *ext); X509V3_EXT_METHOD *X509V3_EXT_get_nid(int nid); int X509V3_add_standard_extensions(void); -STACK_OF(CONF_VALUE) *X509V3_parse_list(char *line); +STACK_OF(CONF_VALUE) *X509V3_parse_list(const char *line); void *X509V3_EXT_d2i(X509_EXTENSION *ext); void *X509V3_get_d2i(STACK_OF(X509_EXTENSION) *x, int nid, int *crit, int *idx); |