scd:p15: Get the label value of all objects for better diagnostics.

* scd/app-p15.c (struct cdf_object_s): Add fields authid, authidlen,
and label.
(struct prkdf_object_s): Add field label.
(struct aodf_object_s): Ditto.
(release_cdflist): Free new fields.
(release_prkdflist): Free new field.
(release_aodf_object): Ditto.
(parse_common_obj_attr): Return the label.
(read_ef_prkdf): Store the label.
(read_ef_pukdf): Ditto.
(read_ef_cdf): Use parse_common_obj_attr and store authid and label.
Print them im verbose mode.
(read_ef_aodf): Store the label and print it.
This commit is contained in:
Werner Koch 2021-02-24 15:50:00 +01:00
parent 8f353cbacb
commit cfdaf2bcc8
No known key found for this signature in database
GPG Key ID: E3FDFF218E45B72B
1 changed files with 180 additions and 78 deletions

View File

@ -186,6 +186,14 @@ struct cdf_object_s
size_t objidlen; size_t objidlen;
unsigned char *objid; unsigned char *objid;
/* Length and allocated buffer with the authId of this object or
NULL if no authID is known. */
size_t authidlen;
unsigned char *authid;
/* NULL or the malloced label of this object. */
char *label;
/* To avoid reading and parsing a certificate more than once, we /* To avoid reading and parsing a certificate more than once, we
* cache the ksba object. */ * cache the ksba object. */
ksba_cert_t cert; ksba_cert_t cert;
@ -262,6 +270,9 @@ struct prkdf_object_s
size_t authidlen; size_t authidlen;
unsigned char *authid; unsigned char *authid;
/* NULL or the malloced label of this object. */
char *label;
/* The keyReference and a flag telling whether it is valid. */ /* The keyReference and a flag telling whether it is valid. */
unsigned long key_reference; unsigned long key_reference;
@ -300,6 +311,9 @@ struct aodf_object_s
size_t authidlen; size_t authidlen;
unsigned char *authid; unsigned char *authid;
/* NULL or the malloced label of this object. */
char *label;
/* The file ID of this AODF. */ /* The file ID of this AODF. */
unsigned short fid; unsigned short fid;
@ -446,6 +460,8 @@ release_cdflist (cdf_object_t a)
cdf_object_t tmp = a->next; cdf_object_t tmp = a->next;
ksba_free (a->cert); ksba_free (a->cert);
xfree (a->objid); xfree (a->objid);
xfree (a->authid);
xfree (a->label);
xfree (a); xfree (a);
a = tmp; a = tmp;
} }
@ -462,6 +478,7 @@ release_prkdflist (prkdf_object_t a)
xfree (a->serial_number); xfree (a->serial_number);
xfree (a->objid); xfree (a->objid);
xfree (a->authid); xfree (a->authid);
xfree (a->label);
xfree (a); xfree (a);
a = tmp; a = tmp;
} }
@ -481,6 +498,7 @@ release_aodf_object (aodf_object_t a)
{ {
xfree (a->objid); xfree (a->objid);
xfree (a->authid); xfree (a->authid);
xfree (a->label);
xfree (a->path); xfree (a->path);
xfree (a); xfree (a);
} }
@ -1073,11 +1091,12 @@ parse_keyusage_flags (const unsigned char *der, size_t derlen,
/* Parse the commonObjectAttributes and store a malloced authid at /* Parse the commonObjectAttributes and store a malloced authid at
* (r_authid,r_authidlen). (NULL,0) is stored on error or if no * (r_authid,r_authidlen). (NULL,0) is stored on error or if no
* authid is found. * authid is found. IF R_LABEL is not NULL the label is stored there
* as a malloed string.
* *
* Example data: * Example data:
* 2 30 17: SEQUENCE { -- commonObjectAttributes * 2 30 17: SEQUENCE { -- commonObjectAttributes
* 4 0C 8: UTF8String 'SK.CH.DS' * 4 0C 8: UTF8String 'SK.CH.DS' -- label
* 14 03 2: BIT STRING 6 unused bits * 14 03 2: BIT STRING 6 unused bits
* : '01'B (bit 0) * : '01'B (bit 0)
* 18 04 1: OCTET STRING --authid * 18 04 1: OCTET STRING --authid
@ -1086,7 +1105,8 @@ parse_keyusage_flags (const unsigned char *der, size_t derlen,
*/ */
static gpg_error_t static gpg_error_t
parse_common_obj_attr (unsigned char const **buffer, size_t *size, parse_common_obj_attr (unsigned char const **buffer, size_t *size,
unsigned char **r_authid, size_t *r_authidlen) unsigned char **r_authid, size_t *r_authidlen,
char **r_label)
{ {
gpg_error_t err; gpg_error_t err;
int where; int where;
@ -1097,6 +1117,8 @@ parse_common_obj_attr (unsigned char const **buffer, size_t *size,
*r_authid = NULL; *r_authid = NULL;
*r_authidlen = 0; *r_authidlen = 0;
if (r_label)
*r_label = NULL;
where = __LINE__; where = __LINE__;
err = parse_ber_header (buffer, size, &class, &tag, &constructed, err = parse_ber_header (buffer, size, &class, &tag, &constructed,
@ -1124,7 +1146,19 @@ parse_common_obj_attr (unsigned char const **buffer, size_t *size,
if (tag == TAG_UTF8_STRING) if (tag == TAG_UTF8_STRING)
{ {
ppp += objlen; /* Skip the Label. */ if (r_label)
{
*r_label = xtrymalloc (objlen + 1);
if (!*r_label)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (*r_label, ppp, objlen);
(*r_label)[objlen] = 0;
}
ppp += objlen;
nnn -= objlen; nnn -= objlen;
where = __LINE__; where = __LINE__;
@ -1167,6 +1201,12 @@ parse_common_obj_attr (unsigned char const **buffer, size_t *size,
log_error ("p15: error parsing commonObjectAttributes at %d: %s\n", log_error ("p15: error parsing commonObjectAttributes at %d: %s\n",
where, gpg_strerror (err)); where, gpg_strerror (err));
if (err && r_label)
{
xfree (*r_label);
*r_label = NULL;
}
return err; return err;
} }
@ -1338,55 +1378,67 @@ parse_common_key_attr (unsigned char const **buffer, size_t *size,
} }
/* Read and parse the Private Key Directory Files. */ /* Read and parse the Private Key Directory Files.
/* *
6034 (privatekeys) * Sample object:
*
30 33 30 11 0C 08 53 4B 2E 43 48 2E 44 53 03 02 030...SK.CH.DS.. * SEQUENCE {
06 80 04 01 07 30 0C 04 01 01 03 03 06 00 40 02 .....0........@. * SEQUENCE { -- commonObjectAttributes
02 00 50 A1 10 30 0E 30 08 04 06 3F 00 40 16 00 ..P..0.0...?.@.. * UTF8String 'SK.CH.DS'
50 02 02 04 00 30 33 30 11 0C 08 53 4B 2E 43 48 P....030...SK.CH * BIT STRING 6 unused bits
2E 4B 45 03 02 06 80 04 01 0A 30 0C 04 01 0C 03 .KE.......0..... * '01'B (bit 0)
03 06 44 00 02 02 00 52 A1 10 30 0E 30 08 04 06 ..D....R..0.0... * OCTET STRING --authid
3F 00 40 16 00 52 02 02 04 00 30 34 30 12 0C 09 ?.@..R....040... * 07
53 4B 2E 43 48 2E 41 55 54 03 02 06 80 04 01 0A SK.CH.AUT....... * }
30 0C 04 01 0D 03 03 06 20 00 02 02 00 51 A1 10 0....... ....Q.. * SEQUENCE { -- commonKeyAttributes
30 0E 30 08 04 06 3F 00 40 16 00 51 02 02 04 00 0.0...?.@..Q.... * OCTET STRING
30 37 30 15 0C 0C 53 4B 2E 43 48 2E 44 53 2D 53 070...SK.CH.DS-S * 01
50 58 03 02 06 80 04 01 0A 30 0C 04 01 02 03 03 PX.......0...... * BIT STRING 6 unused bits
06 20 00 02 02 00 53 A1 10 30 0E 30 08 04 06 3F . ....S..0.0...? * '1000000000'B (bit 9)
00 40 16 00 53 02 02 04 00 00 00 00 00 00 00 00 .@..S........... * INTEGER 80 -- keyReference (optional)
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ * }
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ * [1] { -- keyAttributes
* SEQUENCE { -- privateRSAKeyAttributes
0 30 51: SEQUENCE { * SEQUENCE { -- objectValue
2 30 17: SEQUENCE { -- commonObjectAttributes * OCTET STRING --path
4 0C 8: UTF8String 'SK.CH.DS' * 3F 00 40 16 00 50
14 03 2: BIT STRING 6 unused bits * }
: '01'B (bit 0) * INTEGER 1024 -- modulus
18 04 1: OCTET STRING --authid * }
: 07 * }
: } * }
21 30 12: SEQUENCE { -- commonKeyAttributes *
23 04 1: OCTET STRING * Sample for EC objects
: 01 * [1] { -- keyAttributes
26 03 3: BIT STRING 6 unused bits * [0] { --privateECKeyAttributes
: '1000000000'B (bit 9) * SEQUENCE { -- objectValue
31 02 2: INTEGER 80 -- keyReference (optional) * UTF8String '840b8a098d582647778e25a8465c5601'
: } * BIT STRING 6 unused bits
35 A1 16: [1] { -- keyAttributes * '11'B
37 30 14: SEQUENCE { -- privateRSAKeyAttributes * OCTET STRING 01
39 30 8: SEQUENCE { -- objectValue * }
41 04 6: OCTET STRING --path * SEQUENCE { -- keyInfo
: 3F 00 40 16 00 50 * OCTET STRING 69AE183E6D5BF45B98872C569506326C76AF460F
: } * BIT STRING 4 unused bits
49 02 2: INTEGER 1024 -- modulus * '0100'B (bit 2)
: } * BIT STRING 3 unused bits
: } * '11101'B
: } * INTEGER 3
* }
* [0] {
*/ * SEQUENCE {}
* }
* [1] {
* SEQUENCE {
* SEQUENCE {
* OCTET STRING 50 72 4B 03
* }
* INTEGER 33
* }
* }
* }
* }
*/
static gpg_error_t static gpg_error_t
read_ef_prkdf (app_t app, unsigned short fid, prkdf_object_t *result) read_ef_prkdf (app_t app, unsigned short fid, prkdf_object_t *result)
{ {
@ -1403,6 +1455,7 @@ read_ef_prkdf (app_t app, unsigned short fid, prkdf_object_t *result)
size_t authidlen = 0; size_t authidlen = 0;
unsigned char *objid = NULL; unsigned char *objid = NULL;
size_t objidlen = 0; size_t objidlen = 0;
char *label = NULL;
int record_mode; int record_mode;
err = read_first_record (app, fid, "PrKDF", &buffer, &buflen, &record_mode); err = read_first_record (app, fid, "PrKDF", &buffer, &buflen, &record_mode);
@ -1470,7 +1523,8 @@ read_ef_prkdf (app_t app, unsigned short fid, prkdf_object_t *result)
/* Parse the commonObjectAttributes. */ /* Parse the commonObjectAttributes. */
where = __LINE__; where = __LINE__;
xfree (authid); xfree (authid);
err = parse_common_obj_attr (&pp, &nn, &authid, &authidlen); xfree (label);
err = parse_common_obj_attr (&pp, &nn, &authid, &authidlen, &label);
if (err) if (err)
goto parse_error; goto parse_error;
@ -1576,6 +1630,11 @@ read_ef_prkdf (app_t app, unsigned short fid, prkdf_object_t *result)
prkdf->authid = authid; prkdf->authid = authid;
authid = NULL; authid = NULL;
} }
if (label)
{
prkdf->label = label;
label = NULL;
}
prkdf->pathlen = objlen/2; prkdf->pathlen = objlen/2;
for (i=0; i < prkdf->pathlen; i++, pp += 2, nn -= 2) for (i=0; i < prkdf->pathlen; i++, pp += 2, nn -= 2)
@ -1640,6 +1699,7 @@ read_ef_prkdf (app_t app, unsigned short fid, prkdf_object_t *result)
{ {
xfree (prkdf->objid); xfree (prkdf->objid);
xfree (prkdf->authid); xfree (prkdf->authid);
xfree (prkdf->label);
xfree (prkdf); xfree (prkdf);
} }
err = 0; err = 0;
@ -1665,6 +1725,7 @@ read_ef_prkdf (app_t app, unsigned short fid, prkdf_object_t *result)
leave: leave:
xfree (authid); xfree (authid);
xfree (label);
xfree (objid); xfree (objid);
xfree (buffer); xfree (buffer);
if (err) if (err)
@ -1692,6 +1753,7 @@ read_ef_pukdf (app_t app, unsigned short fid, pukdf_object_t *result)
size_t authidlen = 0; size_t authidlen = 0;
unsigned char *objid = NULL; unsigned char *objid = NULL;
size_t objidlen = 0; size_t objidlen = 0;
char *label = NULL;
int record_mode; int record_mode;
err = read_first_record (app, fid, "PuKDF", &buffer, &buflen, &record_mode); err = read_first_record (app, fid, "PuKDF", &buffer, &buflen, &record_mode);
@ -1760,7 +1822,8 @@ read_ef_pukdf (app_t app, unsigned short fid, pukdf_object_t *result)
/* Parse the commonObjectAttributes. */ /* Parse the commonObjectAttributes. */
where = __LINE__; where = __LINE__;
xfree (authid); xfree (authid);
err = parse_common_obj_attr (&pp, &nn, &authid, &authidlen); xfree (label);
err = parse_common_obj_attr (&pp, &nn, &authid, &authidlen, &label);
if (err) if (err)
goto parse_error; goto parse_error;
@ -1867,6 +1930,11 @@ read_ef_pukdf (app_t app, unsigned short fid, pukdf_object_t *result)
pukdf->authid = authid; pukdf->authid = authid;
authid = NULL; authid = NULL;
} }
if (label)
{
pukdf->label = label;
label = NULL;
}
pukdf->pathlen = objlen/2; pukdf->pathlen = objlen/2;
for (i=0; i < pukdf->pathlen; i++, pp += 2, nn -= 2) for (i=0; i < pukdf->pathlen; i++, pp += 2, nn -= 2)
@ -1921,7 +1989,9 @@ read_ef_pukdf (app_t app, unsigned short fid, pukdf_object_t *result)
log_info ("p15: PuKDF %04hX: id=", fid); log_info ("p15: PuKDF %04hX: id=", fid);
for (i=0; i < pukdf->objidlen; i++) for (i=0; i < pukdf->objidlen; i++)
log_printf ("%02X", pukdf->objid[i]); log_printf ("%02X", pukdf->objid[i]);
log_printf (" path="); if (pukdf->label)
log_printf (" (%s)", pukdf->label);
log_info ("p15: path=");
for (i=0; i < pukdf->pathlen; i++) for (i=0; i < pukdf->pathlen; i++)
log_printf ("%s%04hX", i?"/":"",pukdf->path[i]); log_printf ("%s%04hX", i?"/":"",pukdf->path[i]);
if (pukdf->have_off) if (pukdf->have_off)
@ -1965,6 +2035,7 @@ read_ef_pukdf (app_t app, unsigned short fid, pukdf_object_t *result)
{ {
xfree (pukdf->objid); xfree (pukdf->objid);
xfree (pukdf->authid); xfree (pukdf->authid);
xfree (pukdf->label);
xfree (pukdf); xfree (pukdf);
} }
err = 0; err = 0;
@ -1990,6 +2061,7 @@ read_ef_pukdf (app_t app, unsigned short fid, pukdf_object_t *result)
leave: leave:
xfree (authid); xfree (authid);
xfree (label);
xfree (objid); xfree (objid);
xfree (buffer); xfree (buffer);
if (err) if (err)
@ -2016,6 +2088,9 @@ read_ef_cdf (app_t app, unsigned short fid, int cdftype, cdf_object_t *result)
cdf_object_t cdflist = NULL; cdf_object_t cdflist = NULL;
int i; int i;
int recno = 1; int recno = 1;
unsigned char *authid = NULL;
size_t authidlen = 0;
char *label = NULL;
int record_mode; int record_mode;
err = read_first_record (app, fid, "CDF", &buffer, &buflen, &record_mode); err = read_first_record (app, fid, "CDF", &buffer, &buflen, &record_mode);
@ -2054,16 +2129,13 @@ read_ef_cdf (app_t app, unsigned short fid, int cdftype, cdf_object_t *result)
p += objlen; p += objlen;
n -= objlen; n -= objlen;
/* Skip the commonObjectAttributes. */ /* Parse the commonObjectAttributes. */
where = __LINE__; where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, xfree (authid);
&ndef, &objlen, &hdrlen); xfree (label);
if (!err && (objlen > nn || tag != TAG_SEQUENCE)) err = parse_common_obj_attr (&pp, &nn, &authid, &authidlen, &label);
err = gpg_error (GPG_ERR_INV_OBJ);
if (err) if (err)
goto parse_error; goto parse_error;
pp += objlen;
nn -= objlen;
/* Parse the commonCertificateAttributes. */ /* Parse the commonCertificateAttributes. */
where = __LINE__; where = __LINE__;
@ -2154,6 +2226,18 @@ read_ef_cdf (app_t app, unsigned short fid, int cdftype, cdf_object_t *result)
err = gpg_error_from_syserror (); err = gpg_error_from_syserror ();
goto leave; goto leave;
} }
if (authid)
{
cdf->authidlen = authidlen;
cdf->authid = authid;
authid = NULL;
}
if (label)
{
cdf->label = label;
label = NULL;
}
cdf->objidlen = objidlen; cdf->objidlen = objidlen;
cdf->objid = xtrymalloc (objidlen); cdf->objid = xtrymalloc (objidlen);
if (!cdf->objid) if (!cdf->objid)
@ -2209,14 +2293,22 @@ read_ef_cdf (app_t app, unsigned short fid, int cdftype, cdf_object_t *result)
if (opt.verbose) if (opt.verbose)
{ {
log_info ("p15: CDF(%c) %04hX: id=", cdftype, fid); log_info ("p15: CDF-%c %04hX: id=", cdftype, fid);
for (i=0; i < cdf->objidlen; i++) for (i=0; i < cdf->objidlen; i++)
log_printf ("%02X", cdf->objid[i]); log_printf ("%02X", cdf->objid[i]);
log_printf (" path="); if (cdf->label)
log_printf (" (%s)", cdf->label);
log_info ("p15: path=");
for (i=0; i < cdf->pathlen; i++) for (i=0; i < cdf->pathlen; i++)
log_printf ("%s%04hX", i?"/":"", cdf->path[i]); log_printf ("%s%04hX", i?"/":"", cdf->path[i]);
if (cdf->have_off) if (cdf->have_off)
log_printf ("[%lu/%lu]", cdf->off, cdf->len); log_printf ("[%lu/%lu]", cdf->off, cdf->len);
if (cdf->authid)
{
log_printf (" authid=");
for (i=0; i < cdf->authidlen; i++)
log_printf ("%02X", cdf->authid[i]);
}
log_printf ("\n"); log_printf ("\n");
} }
@ -2233,6 +2325,8 @@ read_ef_cdf (app_t app, unsigned short fid, int cdftype, cdf_object_t *result)
err = 0; err = 0;
next_record: next_record:
xfree (authid);
xfree (label);
/* If the card uses a record oriented file structure, read the /* If the card uses a record oriented file structure, read the
* next record. Otherwise we keep on parsing the current buffer. */ * next record. Otherwise we keep on parsing the current buffer. */
recno++; recno++;
@ -2252,6 +2346,8 @@ read_ef_cdf (app_t app, unsigned short fid, int cdftype, cdf_object_t *result)
} /* End loop over all records. */ } /* End loop over all records. */
leave: leave:
xfree (authid);
xfree (label);
xfree (buffer); xfree (buffer);
if (err) if (err)
release_cdflist (cdflist); release_cdflist (cdflist);
@ -2379,7 +2475,8 @@ read_ef_aodf (app_t app, unsigned short fid, aodf_object_t *result)
/* Parse the commonObjectAttributes. */ /* Parse the commonObjectAttributes. */
where = __LINE__; where = __LINE__;
err = parse_common_obj_attr (&pp, &nn, &aodf->authid, &aodf->authidlen); err = parse_common_obj_attr (&pp, &nn, &aodf->authid, &aodf->authidlen,
&aodf->label);
if (err) if (err)
goto parse_error; goto parse_error;
@ -2401,7 +2498,7 @@ read_ef_aodf (app_t app, unsigned short fid, aodf_object_t *result)
/* Get the Id. */ /* Get the Id. */
where = __LINE__; where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen); &ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn if (!err && (objlen > nnn
|| class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING)) || class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING))
err = gpg_error (GPG_ERR_INV_OBJ); err = gpg_error (GPG_ERR_INV_OBJ);
@ -2767,17 +2864,12 @@ read_ef_aodf (app_t app, unsigned short fid, aodf_object_t *result)
err = 0; err = 0;
if (opt.verbose) if (opt.verbose)
{ {
log_info ("p15: AODF %04hX: id=", fid); log_info ("p15: AODF %04hX: id=", fid);
for (i=0; i < aodf->objidlen; i++) for (i=0; i < aodf->objidlen; i++)
log_printf ("%02X", aodf->objid[i]); log_printf ("%02X", aodf->objid[i]);
if (aodf->authid) if (aodf->label)
{ log_printf (" (%s)", aodf->label);
log_printf (" authid="); log_info ("p15: ");
for (i=0; i < aodf->authidlen; i++)
log_printf ("%02X", aodf->authid[i]);
}
if (aodf->pin_reference_valid)
log_printf (" pinref=0x%02lX", aodf->pin_reference);
if (aodf->pathlen) if (aodf->pathlen)
{ {
log_printf (" path="); log_printf (" path=");
@ -2786,6 +2878,14 @@ read_ef_aodf (app_t app, unsigned short fid, aodf_object_t *result)
if (aodf->have_off) if (aodf->have_off)
log_printf ("[%lu/%lu]", aodf->off, aodf->len); log_printf ("[%lu/%lu]", aodf->off, aodf->len);
} }
if (aodf->authid)
{
log_printf (" authid=");
for (i=0; i < aodf->authidlen; i++)
log_printf ("%02X", aodf->authid[i]);
}
if (aodf->pin_reference_valid)
log_printf (" pinref=0x%02lX", aodf->pin_reference);
log_printf (" min=%lu", aodf->min_length); log_printf (" min=%lu", aodf->min_length);
log_printf (" stored=%lu", aodf->stored_length); log_printf (" stored=%lu", aodf->stored_length);
if (aodf->max_length_valid) if (aodf->max_length_valid)
@ -2793,7 +2893,7 @@ read_ef_aodf (app_t app, unsigned short fid, aodf_object_t *result)
if (aodf->pad_char_valid) if (aodf->pad_char_valid)
log_printf (" pad=0x%02x", aodf->pad_char); log_printf (" pad=0x%02x", aodf->pad_char);
log_info ("p15: flags="); log_info ("p15: flags=");
s = ""; s = "";
if (aodf->pinflags.case_sensitive) if (aodf->pinflags.case_sensitive)
log_printf ("%scase_sensitive", s), s = ","; log_printf ("%scase_sensitive", s), s = ",";
@ -3299,7 +3399,9 @@ read_p15_info (app_t app)
log_info ("p15: PrKDF %04hX: id=", app->app_local->odf.private_keys); log_info ("p15: PrKDF %04hX: id=", app->app_local->odf.private_keys);
for (i=0; i < prkdf->objidlen; i++) for (i=0; i < prkdf->objidlen; i++)
log_printf ("%02X", prkdf->objid[i]); log_printf ("%02X", prkdf->objid[i]);
log_printf (" path="); if (prkdf->label)
log_printf (" (%s)", prkdf->label);
log_info ("p15: path=");
for (i=0; i < prkdf->pathlen; i++) for (i=0; i < prkdf->pathlen; i++)
log_printf ("%s%04hX", i?"/":"",prkdf->path[i]); log_printf ("%s%04hX", i?"/":"",prkdf->path[i]);
if (prkdf->have_off) if (prkdf->have_off)