From b6ba6a79ce9336f1b53f16f3d1190dd009fb166e Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 11 Sep 2020 15:23:22 +0200 Subject: [PATCH] common: New function cmp_canon_sexp. * common/sexputil.c (cmp_canon_sexp): New. (cmp_canon_sexp_def_tcmp): New. * common/t-sexputil.c (test_cmp_canon_sexp): Add a simple test. -- To be used to fix GnuPG-bug-id: 5061 Signed-off-by: Werner Koch --- common/sexputil.c | 90 ++++++++++++++++++++++++++++++++++++++ common/t-sexputil.c | 102 ++++++++++++++++++++++++++++++++++++++++++++ common/tlv.c | 4 +- common/util.h | 6 +++ 4 files changed, 200 insertions(+), 2 deletions(-) diff --git a/common/sexputil.c b/common/sexputil.c index 981a06664..9bb1d6bdc 100644 --- a/common/sexputil.c +++ b/common/sexputil.c @@ -263,6 +263,96 @@ cmp_simple_canon_sexp (const unsigned char *a_orig, } + +/* Helper for cmp_canon_sexp. */ +static int +cmp_canon_sexp_def_tcmp (void *ctx, int depth, + const unsigned char *aval, size_t alen, + const unsigned char *bval, size_t blen) +{ + (void)ctx; + (void)depth; + + if (alen > blen) + return 1; + else if (alen < blen) + return -1; + else + return memcmp (aval, bval, alen); +} + + +/* Compare the two canonical encoded s-expressions A with maximum + * length ALEN and B with maximum length BLEN. + * + * Returns 0 if they match. + * + * If TCMP is NULL, this is not different really different from a + * memcmp but does not consider any garbage after the last closing + * parentheses. + * + * If TCMP is not NULL, it is expected to be a function to compare the + * values of each token. TCMP is called for each token while parsing + * the s-expressions until TCMP return a non-zero value. Here the CTX + * receives the provided value TCMPCTX, DEPTH is the number of + * currently open parentheses and (AVAL,ALEN) and (BVAL,BLEN) the + * values of the current token. TCMP needs to return zero to indicate + * that the tokens match. */ +int +cmp_canon_sexp (const unsigned char *a, size_t alen, + const unsigned char *b, size_t blen, + int (*tcmp)(void *ctx, int depth, + const unsigned char *aval, size_t avallen, + const unsigned char *bval, size_t bvallen), + void *tcmpctx) +{ + const unsigned char *a_buf, *a_tok; + const unsigned char *b_buf, *b_tok; + size_t a_buflen, a_toklen; + size_t b_buflen, b_toklen; + int a_depth, b_depth, ret; + + if ((!a && !b) || (!alen && !blen)) + return 0; /* Both are NULL, they are identical. */ + if (!a || !b) + return !!a - !!b; /* One is NULL, they are not identical. */ + if (*a != '(' || *b != '(') + log_bug ("invalid S-exp in %s\n", __func__); + + if (!tcmp) + tcmp = cmp_canon_sexp_def_tcmp; + + a_depth = 0; + a_buf = a; + a_buflen = alen; + b_depth = 0; + b_buf = b; + b_buflen = blen; + + for (;;) + { + if (parse_sexp (&a_buf, &a_buflen, &a_depth, &a_tok, &a_toklen)) + return -1; /* A is invalid. */ + if (parse_sexp (&b_buf, &b_buflen, &b_depth, &b_tok, &b_toklen)) + return -1; /* B is invalid. */ + if (!a_depth && !b_depth) + return 0; /* End of both expressions - they match. */ + if (a_depth != b_depth) + return a_depth - b_depth; /* Not the same structure */ + if (!a_tok && !b_tok) + ; /* parens */ + else if (a_tok && b_tok) + { + ret = tcmp (tcmpctx, a_depth, a_tok, a_toklen, b_tok, b_toklen); + if (ret) + return ret; /* Mismatch */ + } + else /* One has a paren other has not. */ + return !!a_tok - !!b_tok; + } +} + + /* Create a simple S-expression from the hex string at LINE. Returns a newly allocated buffer with that canonical encoded S-expression or NULL in case of an error. On return the number of characters diff --git a/common/t-sexputil.c b/common/t-sexputil.c index ceb828076..659eadd3a 100644 --- a/common/t-sexputil.c +++ b/common/t-sexputil.c @@ -178,6 +178,107 @@ test_make_canon_sexp_from_rsa_pk (void) } + +/* Communiacation object for tcmp. */ +struct tcmp_parm_s { + int curve_seen; +}; + +/* Helper for test_cmp_canon_sexp. */ +static int +tcmp1 (void *opaque, int depth, + const unsigned char *aval, size_t alen, + const unsigned char *bval, size_t blen) +{ + struct tcmp_parm_s *parm = opaque; + + (void)depth; + + if (parm->curve_seen) + { + /* Last token was "curve", canonicalize its argument. */ + parm->curve_seen = 0; + + if (alen == 8 && !memcmp (aval, "nistp256", alen)) + { + alen = 19; + aval = "1.2.840.10045.3.1.7"; + } + + if (blen == 8 && !memcmp (bval, "nistp256", blen)) + { + blen = 19; + bval = "1.2.840.10045.3.1.7"; + } + } + else if (alen == 5 && !memcmp (aval, "curve", 5)) + parm->curve_seen = 1; + else + parm->curve_seen = 0; + + if (alen > blen) + return 1; + else if (alen < blen) + return -1; + else + return memcmp (aval, bval, alen); +} + + +static void +test_cmp_canon_sexp (void) +{ + struct { + unsigned char *a; + unsigned char *b; + int expected0; /* Expected result without compare function. */ + int expected1; /* Expected result with compare function tcmp1. */ + } + tests[] = { + { + "(10:public-key(3:ecc(5:curve8:nistp256)(1:q10:qqqqqqqqqq)))", + "(10:public-key(3:ecc(5:curve8:nistp256)(1:q10:qqqqqqqqqq)))", + 0, 0 + }, + { + "(10:public-key(3:ecc(5:curve19:1.2.840.10045.3.1.7)(1:q10:qqqqqqqqqq)))", + "(10:public-key(3:ecc(5:curve19:1.2.840.10045.3.1.7)(1:q10:qqqqqqqqqq)))", + 0, 0 + }, + { + "(10:public-key(3:ecc(5:curve8:nistp256)(1:q10:qqqqqqqqqq)))", + "(10:public-key(3:ecc(5:curve19:1.2.840.10045.3.1.7)(1:q10:qqqqqqqqqq)))", + -1, 0 + }, + { + "(10:public-key(3:ecc(5:curve19:1.2.840.10045.3.1.7)(1:q10:qqqqqqqqqq)))", + "(10:public-key(3:ecc(5:curve8:nistp256)(1:q10:qqqqqqqqqq)))", + 1, 0 + }, + { + NULL + } + }; + struct tcmp_parm_s parm = {0}; + int idx; + int res; + + for (idx=0; tests[idx].a; idx++) + { + res = cmp_canon_sexp (tests[idx].a, strlen (tests[idx].a), + tests[idx].b, strlen (tests[idx].b), + NULL, NULL); + if (res != tests[idx].expected0) + fail (idx); + res = cmp_canon_sexp (tests[idx].a, strlen (tests[idx].a), + tests[idx].b, strlen (tests[idx].b), + tcmp1, &parm); + if (res != tests[idx].expected1) + fail (idx); + } +} + + int main (int argc, char **argv) { @@ -186,6 +287,7 @@ main (int argc, char **argv) test_hash_algo_from_sigval (); test_make_canon_sexp_from_rsa_pk (); + test_cmp_canon_sexp (); return 0; } diff --git a/common/tlv.c b/common/tlv.c index 947464bf7..abef83a37 100644 --- a/common/tlv.c +++ b/common/tlv.c @@ -242,8 +242,8 @@ parse_ber_header (unsigned char const **buffer, size_t *size, returned as a pointer into the original buffer at TOK and TOKLEN. If a parentheses is the next token, TOK will be set to NULL. TOKLEN is checked to be within the bounds. On error an error code - is returned and no pointer is not guaranteed to point to - a meaningful value. DEPTH should be initialized to 0 and will + is returned and pointers are not guaranteed to point to + meaningful values. DEPTH should be initialized to 0 and will reflect on return the actual depth of the tree. To detect the end of the S-expression it is advisable to check DEPTH after a successful return. diff --git a/common/util.h b/common/util.h index 490c443d8..f39093566 100644 --- a/common/util.h +++ b/common/util.h @@ -185,6 +185,12 @@ gpg_error_t make_canon_sexp_pad (gcry_sexp_t sexp, int secure, gpg_error_t keygrip_from_canon_sexp (const unsigned char *key, size_t keylen, unsigned char *grip); int cmp_simple_canon_sexp (const unsigned char *a, const unsigned char *b); +int cmp_canon_sexp (const unsigned char *a, size_t alen, + const unsigned char *b, size_t blen, + int (*tcmp)(void *ctx, int depth, + const unsigned char *aval, size_t avallen, + const unsigned char *bval, size_t bvallen), + void *tcmpctx); unsigned char *make_simple_sexp_from_hexstr (const char *line, size_t *nscanned); int hash_algo_from_sigval (const unsigned char *sigval);