diff --git a/client/Makefile b/client/Makefile index 6c04a752..4a4dd65a 100644 --- a/client/Makefile +++ b/client/Makefile @@ -110,6 +110,10 @@ CMDSRCS = crapto1/crapto1.c\ ui.c \ cmddata.c \ lfdemod.c \ + emv/crypto.c\ + emv/emv_pk.c\ + emv/emv_pki.c\ + emv/emv_pki_priv.c\ emv/apduinfo.c\ emv/dump.c\ emv/tlv.c\ diff --git a/client/emv/crypto.c b/client/emv/crypto.c new file mode 100644 index 00000000..a40e1a6b --- /dev/null +++ b/client/emv/crypto.c @@ -0,0 +1,181 @@ +/* + * libopenemv - a library to work with EMV family of smart cards + * Copyright (C) 2015 Dmitry Eremin-Solenikov + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "crypto.h" +#include "crypto_backend.h" + +#include + +static struct crypto_backend *crypto_backend; + +static bool crypto_init(void) +{ + //const char *driver; + + if (crypto_backend) + return true; + + // crypto_backend = crypto_nettle_init(); TODO!!!!! + + if (!crypto_backend) + return false; + + return true; +} + +struct crypto_hash *crypto_hash_open(enum crypto_algo_hash hash) +{ + struct crypto_hash *ch; + + if (!crypto_init()) + return NULL; + + ch = crypto_backend->hash_open(hash); + if (ch) + ch->algo = hash; + + return ch; +} + +void crypto_hash_close(struct crypto_hash *ch) +{ + ch->close(ch); +} + +void crypto_hash_write(struct crypto_hash *ch, const unsigned char *buf, size_t len) +{ + ch->write(ch, buf, len); +} + +unsigned char *crypto_hash_read(struct crypto_hash *ch) +{ + return ch->read(ch); +} + +size_t crypto_hash_get_size(const struct crypto_hash *ch) +{ + return ch->get_size(ch); +} + +struct crypto_pk *crypto_pk_open(enum crypto_algo_pk pk, ...) +{ + struct crypto_pk *cp; + va_list vl; + + if (!crypto_init()) + return NULL; + + va_start(vl, pk); + cp = crypto_backend->pk_open(pk, vl); + va_end(vl); + + if (cp) + cp->algo = pk; + + return cp; +} + +struct crypto_pk *crypto_pk_open_priv(enum crypto_algo_pk pk, ...) +{ + struct crypto_pk *cp; + va_list vl; + + if (!crypto_init()) + return NULL; + + if (!crypto_backend->pk_open_priv) + return NULL; + + va_start(vl, pk); + cp = crypto_backend->pk_open_priv(pk, vl); + va_end(vl); + + if (cp) + cp->algo = pk; + + return cp; +} + +struct crypto_pk *crypto_pk_genkey(enum crypto_algo_pk pk, ...) +{ + struct crypto_pk *cp; + va_list vl; + + if (!crypto_init()) + return NULL; + + if (!crypto_backend->pk_genkey) + return NULL; + + va_start(vl, pk); + cp = crypto_backend->pk_genkey(pk, vl); + va_end(vl); + + if (cp) + cp->algo = pk; + + return cp; +} + +void crypto_pk_close(struct crypto_pk *cp) +{ + cp->close(cp); +} + +unsigned char *crypto_pk_encrypt(const struct crypto_pk *cp, const unsigned char *buf, size_t len, size_t *clen) +{ + return cp->encrypt(cp, buf, len, clen); +} + +unsigned char *crypto_pk_decrypt(const struct crypto_pk *cp, const unsigned char *buf, size_t len, size_t *clen) +{ + if (!cp->decrypt) { + *clen = 0; + + return NULL; + } + + return cp->decrypt(cp, buf, len, clen); +} + +enum crypto_algo_pk crypto_pk_get_algo(const struct crypto_pk *cp) +{ + if (!cp) + return PK_INVALID; + + return cp->algo; +} + +size_t crypto_pk_get_nbits(const struct crypto_pk *cp) +{ + if (!cp->get_nbits) + return 0; + + return cp->get_nbits(cp); +} + +unsigned char *crypto_pk_get_parameter(const struct crypto_pk *cp, unsigned param, size_t *plen) +{ + *plen = 0; + + if (!cp->get_parameter) + return NULL; + + return cp->get_parameter(cp, param, plen); +} diff --git a/client/emv/crypto.h b/client/emv/crypto.h new file mode 100644 index 00000000..940e8b2b --- /dev/null +++ b/client/emv/crypto.h @@ -0,0 +1,48 @@ +/* + * libopenemv - a library to work with EMV family of smart cards + * Copyright (C) 2015 Dmitry Eremin-Solenikov + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ + +#ifndef CRYPTO_H +#define CRYPTO_H + +#include +#include + +enum crypto_algo_hash { + HASH_INVALID, + HASH_SHA_1, +}; + +struct crypto_hash *crypto_hash_open(enum crypto_algo_hash hash); +void crypto_hash_close(struct crypto_hash *ch); +void crypto_hash_write(struct crypto_hash *ch, const unsigned char *buf, size_t len); +unsigned char *crypto_hash_read(struct crypto_hash *ch); +size_t crypto_hash_get_size(const struct crypto_hash *ch); + +enum crypto_algo_pk { + PK_INVALID, + PK_RSA, +}; + +struct crypto_pk *crypto_pk_open(enum crypto_algo_pk pk, ...); +struct crypto_pk *crypto_pk_open_priv(enum crypto_algo_pk pk, ...); +struct crypto_pk *crypto_pk_genkey(enum crypto_algo_pk pk, ...); +void crypto_pk_close(struct crypto_pk *cp); +unsigned char *crypto_pk_encrypt(const struct crypto_pk *cp, const unsigned char *buf, size_t len, size_t *clen); +unsigned char *crypto_pk_decrypt(const struct crypto_pk *cp, const unsigned char *buf, size_t len, size_t *clen); +enum crypto_algo_pk crypto_pk_get_algo(const struct crypto_pk *cp); +size_t crypto_pk_get_nbits(const struct crypto_pk *cp); +unsigned char *crypto_pk_get_parameter(const struct crypto_pk *cp, unsigned param, size_t *plen); + +#endif diff --git a/client/emv/crypto_backend.h b/client/emv/crypto_backend.h new file mode 100644 index 00000000..79273c0a --- /dev/null +++ b/client/emv/crypto_backend.h @@ -0,0 +1,48 @@ +/* + * libopenemv - a library to work with EMV family of smart cards + * Copyright (C) 2015 Dmitry Eremin-Solenikov + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ + +#ifndef CRYPTO_BACKEND_H +#define CRYPTO_BACKEND_H + +#include "crypto.h" + +#include +#include + +struct crypto_hash { + enum crypto_algo_hash algo; + void (*write)(struct crypto_hash *ch, const unsigned char *buf, size_t len); + unsigned char *(*read)(struct crypto_hash *ch); + void (*close)(struct crypto_hash *ch); + size_t (*get_size)(const struct crypto_hash *ch); +}; + +struct crypto_pk { + enum crypto_algo_pk algo; + unsigned char *(*encrypt)(const struct crypto_pk *cp, const unsigned char *buf, size_t len, size_t *clen); + unsigned char *(*decrypt)(const struct crypto_pk *cp, const unsigned char *buf, size_t len, size_t *clen); + unsigned char *(*get_parameter)(const struct crypto_pk *cp, unsigned param, size_t *plen); + size_t (*get_nbits)(const struct crypto_pk *cp); + void (*close)(struct crypto_pk *cp); +}; + +struct crypto_backend { + struct crypto_hash *(*hash_open)(enum crypto_algo_hash hash); + struct crypto_pk *(*pk_open)(enum crypto_algo_pk pk, va_list vl); + struct crypto_pk *(*pk_open_priv)(enum crypto_algo_pk pk, va_list vl); + struct crypto_pk *(*pk_genkey)(enum crypto_algo_pk pk, va_list vl); +}; + +#endif diff --git a/client/emv/crypto_libgcrypt.c b/client/emv/crypto_libgcrypt.c new file mode 100644 index 00000000..03bcf45b --- /dev/null +++ b/client/emv/crypto_libgcrypt.c @@ -0,0 +1,532 @@ +/* + * libopenemv - a library to work with EMV family of smart cards + * Copyright (C) 2015 Dmitry Eremin-Solenikov + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "openemv/crypto.h" +#include "crypto_backend.h" + +#include +#include + +#define GCRYPT_NO_DEPRECATED +#define GCRYPT_NO_MPI_MACROS +#include + +struct crypto_hash_libgcrypt { + struct crypto_hash ch; + gcry_md_hd_t md; +}; + +static void crypto_hash_libgcrypt_close(struct crypto_hash *_ch) +{ + struct crypto_hash_libgcrypt *ch = container_of(_ch, struct crypto_hash_libgcrypt, ch); + + gcry_md_close(ch->md); + free(ch); +} + +static void crypto_hash_libgcrypt_write(struct crypto_hash *_ch, const unsigned char *buf, size_t len) +{ + struct crypto_hash_libgcrypt *ch = container_of(_ch, struct crypto_hash_libgcrypt, ch); + + gcry_md_write(ch->md, buf, len); +} + +static unsigned char *crypto_hash_libgcrypt_read(struct crypto_hash *_ch) +{ + struct crypto_hash_libgcrypt *ch = container_of(_ch, struct crypto_hash_libgcrypt, ch); + + return gcry_md_read(ch->md, 0); +} + +static size_t crypto_hash_libgcrypt_get_size(const struct crypto_hash *ch) +{ + int algo = GCRY_MD_NONE; + + if (ch->algo == HASH_SHA_1) + algo = GCRY_MD_SHA1; + + return gcry_md_get_algo_dlen(algo); +} + +static struct crypto_hash *crypto_hash_libgcrypt_open(enum crypto_algo_hash hash) +{ + struct crypto_hash_libgcrypt *ch = malloc(sizeof(*ch)); + gcry_error_t err; + int algo = GCRY_MD_NONE; + + if (hash == HASH_SHA_1) + algo = GCRY_MD_SHA1; + + err = gcry_md_open(&ch->md, algo, 0); + if (err) { + fprintf(stderr, "LibGCrypt error %s/%s\n", + gcry_strsource (err), + gcry_strerror (err)); + free(ch); + + return NULL; + } + + ch->ch.write = crypto_hash_libgcrypt_write; + ch->ch.read = crypto_hash_libgcrypt_read; + ch->ch.close = crypto_hash_libgcrypt_close; + ch->ch.get_size = crypto_hash_libgcrypt_get_size; + + return &ch->ch; +} + +struct crypto_pk_libgcrypt { + struct crypto_pk cp; + gcry_sexp_t pk; +}; + +static struct crypto_pk *crypto_pk_libgcrypt_open_rsa(va_list vl) +{ + struct crypto_pk_libgcrypt *cp = malloc(sizeof(*cp)); + gcry_error_t err; + char *mod = va_arg(vl, char *); + int modlen = va_arg(vl, size_t); + char *exp = va_arg(vl, char *); + int explen = va_arg(vl, size_t); + + err = gcry_sexp_build(&cp->pk, NULL, "(public-key (rsa (n %b) (e %b)))", + modlen, mod, explen, exp); + if (err) { + fprintf(stderr, "LibGCrypt error %s/%s\n", + gcry_strsource (err), + gcry_strerror (err)); + free(cp); + return NULL; + } + + return &cp->cp; +} + +static struct crypto_pk *crypto_pk_libgcrypt_open_priv_rsa(va_list vl) +{ + struct crypto_pk_libgcrypt *cp = malloc(sizeof(*cp)); + gcry_error_t err; + char *mod = va_arg(vl, char *); + int modlen = va_arg(vl, size_t); + char *exp = va_arg(vl, char *); + int explen = va_arg(vl, size_t); + char *d = va_arg(vl, char *); + int dlen = va_arg(vl, size_t); + char *p = va_arg(vl, char *); + int plen = va_arg(vl, size_t); + gcry_mpi_t pmpi; + char *q = va_arg(vl, char *); + int qlen = va_arg(vl, size_t); + gcry_mpi_t qmpi; + (void) va_arg(vl, char *); + (void) va_arg(vl, size_t); + (void) va_arg(vl, char *); + (void) va_arg(vl, size_t); + char *inv = va_arg(vl, char *); + int invlen = va_arg(vl, size_t); + gcry_mpi_t invmpi; + + err = gcry_mpi_scan(&pmpi, GCRYMPI_FMT_USG, p, plen, NULL); + if (err) + goto err_p; + + err = gcry_mpi_scan(&qmpi, GCRYMPI_FMT_USG, q, qlen, NULL); + if (err) + goto err_q; + + err = gcry_mpi_scan(&invmpi, GCRYMPI_FMT_USG, inv, invlen, NULL); + if (err) + goto err_inv; + + if (gcry_mpi_cmp (pmpi, qmpi) > 0) { + gcry_mpi_swap (pmpi, qmpi); + gcry_mpi_invm (invmpi, pmpi, qmpi); + } + + err = gcry_sexp_build(&cp->pk, NULL, "(private-key (rsa (n %b) (e %b) (d %b) (p %M) (q %M) (u %M)))", + modlen, mod, explen, exp, dlen, d, + pmpi, qmpi, invmpi); + if (err) + goto err_sexp; + + err = gcry_pk_testkey(cp->pk); + if (err) + goto err_test; + + gcry_mpi_release(invmpi); + gcry_mpi_release(qmpi); + gcry_mpi_release(pmpi); + + return &cp->cp; + +err_test: + gcry_sexp_release(cp->pk); +err_sexp: + gcry_mpi_release(invmpi); +err_inv: + gcry_mpi_release(qmpi); +err_q: + gcry_mpi_release(pmpi); +err_p: + free(cp); + + fprintf(stderr, "LibGCrypt error %s/%s\n", + gcry_strsource (err), + gcry_strerror (err)); + return NULL; +} + +static struct crypto_pk *crypto_pk_libgcrypt_genkey_rsa(va_list vl) +{ + struct crypto_pk_libgcrypt *cp = malloc(sizeof(*cp)); + gcry_error_t err; + gcry_sexp_t params; + int transient = va_arg(vl, int); + unsigned int nbits = va_arg(vl, unsigned int); + unsigned int exp = va_arg(vl, unsigned int); + + err = gcry_sexp_build(¶ms, NULL, + transient ? + "(genkey (rsa (nbits %u) (rsa-use-e %u) (flags transient-key)))": + "(genkey (rsa (nbits %u) (rsa-use-e %u)))", + nbits, exp); + if (err) { + fprintf(stderr, "LibGCrypt error %s/%s\n", + gcry_strsource (err), + gcry_strerror (err)); + free(cp); + return NULL; + } + + err = gcry_pk_genkey(&cp->pk, params); + gcry_sexp_release(params); + if (err) { + fprintf(stderr, "LibGCrypt error %s/%s\n", + gcry_strsource (err), + gcry_strerror (err)); + free(cp); + return NULL; + } + + return &cp->cp; +} + +static void crypto_pk_libgcrypt_close(struct crypto_pk *_cp) +{ + struct crypto_pk_libgcrypt *cp = container_of(_cp, struct crypto_pk_libgcrypt, cp); + + gcry_sexp_release(cp->pk); + free(cp); +} + +static unsigned char *crypto_pk_libgcrypt_encrypt(const struct crypto_pk *_cp, const unsigned char *buf, size_t len, size_t *clen) +{ + struct crypto_pk_libgcrypt *cp = container_of(_cp, struct crypto_pk_libgcrypt, cp); + gcry_error_t err; + int blen = len; + gcry_sexp_t dsexp, esexp, asexp; + gcry_mpi_t tmpi; + size_t templen; + size_t keysize; + unsigned char *result; + + err = gcry_sexp_build(&dsexp, NULL, "(data (flags raw) (value %b))", + blen, buf); + if (err) { + fprintf(stderr, "LibGCrypt error %s/%s\n", + gcry_strsource (err), + gcry_strerror (err)); + return NULL; + } + + err = gcry_pk_encrypt(&esexp, dsexp, cp->pk); + gcry_sexp_release(dsexp); + if (err) { + fprintf(stderr, "LibGCrypt error %s/%s\n", + gcry_strsource (err), + gcry_strerror (err)); + return NULL; + } + + asexp = gcry_sexp_find_token(esexp, "a", 1); + gcry_sexp_release(esexp); + if (!asexp) + return NULL; + + tmpi = gcry_sexp_nth_mpi(asexp, 1, GCRYMPI_FMT_USG); + gcry_sexp_release(asexp); + if (!tmpi) + return NULL; + + keysize = (gcry_pk_get_nbits(cp->pk) + 7) / 8; + result = malloc(keysize); + if (!result) { + gcry_mpi_release(tmpi); + return NULL; + } + + err = gcry_mpi_print(GCRYMPI_FMT_USG, NULL, keysize, &templen, tmpi); + if (err) { + fprintf(stderr, "LibGCrypt error %s/%s\n", + gcry_strsource (err), + gcry_strerror (err)); + gcry_mpi_release(tmpi); + free(result); + return NULL; + } + + err = gcry_mpi_print(GCRYMPI_FMT_USG, result + keysize - templen, templen, &templen, tmpi); + if (err) { + fprintf(stderr, "LibGCrypt error %s/%s\n", + gcry_strsource (err), + gcry_strerror (err)); + gcry_mpi_release(tmpi); + free(result); + return NULL; + } + memset(result, 0, keysize - templen); + + *clen = keysize; + gcry_mpi_release(tmpi); + + return result; +} + +static unsigned char *crypto_pk_libgcrypt_decrypt(const struct crypto_pk *_cp, const unsigned char *buf, size_t len, size_t *clen) +{ + struct crypto_pk_libgcrypt *cp = container_of(_cp, struct crypto_pk_libgcrypt, cp); + gcry_error_t err; + int blen = len; + gcry_sexp_t esexp, dsexp; + gcry_mpi_t tmpi; + size_t templen; + size_t keysize; + unsigned char *result; + + /* XXX: RSA-only! */ + err = gcry_sexp_build(&esexp, NULL, "(enc-val (flags) (rsa (a %b)))", + blen, buf); + if (err) { + fprintf(stderr, "LibGCrypt error %s/%s\n", + gcry_strsource (err), + gcry_strerror (err)); + return NULL; + } + + err = gcry_pk_decrypt(&dsexp, esexp, cp->pk); + gcry_sexp_release(esexp); + if (err) { + fprintf(stderr, "LibGCrypt error %s/%s\n", + gcry_strsource (err), + gcry_strerror (err)); + return NULL; + } + + tmpi = gcry_sexp_nth_mpi(dsexp, 1, GCRYMPI_FMT_USG); + gcry_sexp_release(dsexp); + if (!tmpi) + return NULL; + + keysize = (gcry_pk_get_nbits(cp->pk) + 7) / 8; + result = malloc(keysize); + if (!result) { + gcry_mpi_release(tmpi); + return NULL; + } + + err = gcry_mpi_print(GCRYMPI_FMT_USG, NULL, keysize, &templen, tmpi); + if (err) { + fprintf(stderr, "LibGCrypt error %s/%s\n", + gcry_strsource (err), + gcry_strerror (err)); + gcry_mpi_release(tmpi); + free(result); + return NULL; + } + + err = gcry_mpi_print(GCRYMPI_FMT_USG, result + keysize - templen, templen, &templen, tmpi); + if (err) { + fprintf(stderr, "LibGCrypt error %s/%s\n", + gcry_strsource (err), + gcry_strerror (err)); + gcry_mpi_release(tmpi); + free(result); + return NULL; + } + memset(result, 0, keysize - templen); + + *clen = keysize; + gcry_mpi_release(tmpi); + + return result; +} + +static size_t crypto_pk_libgcrypt_get_nbits(const struct crypto_pk *_cp) +{ + struct crypto_pk_libgcrypt *cp = container_of(_cp, struct crypto_pk_libgcrypt, cp); + + return gcry_pk_get_nbits(cp->pk); +} + +static unsigned char *crypto_pk_libgcrypt_get_parameter(const struct crypto_pk *_cp, unsigned param, size_t *plen) +{ + struct crypto_pk_libgcrypt *cp = container_of(_cp, struct crypto_pk_libgcrypt, cp); + gcry_error_t err; + gcry_sexp_t psexp; + gcry_mpi_t tmpi; + size_t parameter_size; + unsigned char *result; + const char *name; + + /* XXX: RSA-only! */ + if (param == 0) + name = "n"; + else if (param == 1) + name = "e"; + else + return NULL; + + psexp = gcry_sexp_find_token(cp->pk, name, 1); + if (!psexp) + return NULL; + + tmpi = gcry_sexp_nth_mpi(psexp, 1, GCRYMPI_FMT_USG); + gcry_sexp_release(psexp); + if (!tmpi) + return NULL; + + parameter_size = (gcry_mpi_get_nbits(tmpi) + 7) / 8; + result = malloc(parameter_size); + if (!result) { + gcry_mpi_release(tmpi); + return NULL; + } + + err = gcry_mpi_print(GCRYMPI_FMT_USG, result, parameter_size, NULL, tmpi); + if (err) { + fprintf(stderr, "LibGCrypt error %s/%s\n", + gcry_strsource (err), + gcry_strerror (err)); + gcry_mpi_release(tmpi); + free(result); + return NULL; + } + + *plen = parameter_size; + gcry_mpi_release(tmpi); + + return result; +} + +static struct crypto_pk *crypto_pk_libgcrypt_open(enum crypto_algo_pk pk, va_list vl) +{ + struct crypto_pk *cp; + + if (pk == PK_RSA) + cp = crypto_pk_libgcrypt_open_rsa(vl); + else + return NULL; + + cp->close = crypto_pk_libgcrypt_close; + cp->encrypt = crypto_pk_libgcrypt_encrypt; + cp->get_parameter = crypto_pk_libgcrypt_get_parameter; + cp->get_nbits = crypto_pk_libgcrypt_get_nbits; + + return cp; +} + +static struct crypto_pk *crypto_pk_libgcrypt_open_priv(enum crypto_algo_pk pk, va_list vl) +{ + struct crypto_pk *cp; + + if (pk == PK_RSA) + cp = crypto_pk_libgcrypt_open_priv_rsa(vl); + else + return NULL; + + cp->close = crypto_pk_libgcrypt_close; + cp->encrypt = crypto_pk_libgcrypt_encrypt; + cp->decrypt = crypto_pk_libgcrypt_decrypt; + cp->get_parameter = crypto_pk_libgcrypt_get_parameter; + cp->get_nbits = crypto_pk_libgcrypt_get_nbits; + + return cp; +} + +static struct crypto_pk *crypto_pk_libgcrypt_genkey(enum crypto_algo_pk pk, va_list vl) +{ + struct crypto_pk *cp; + + if (pk == PK_RSA) + cp = crypto_pk_libgcrypt_genkey_rsa(vl); + else + return NULL; + + cp->close = crypto_pk_libgcrypt_close; + cp->encrypt = crypto_pk_libgcrypt_encrypt; + cp->decrypt = crypto_pk_libgcrypt_decrypt; + cp->get_parameter = crypto_pk_libgcrypt_get_parameter; + cp->get_nbits = crypto_pk_libgcrypt_get_nbits; + + return cp; +} + +static struct crypto_backend crypto_libgcrypt_backend = { + .hash_open = crypto_hash_libgcrypt_open, + .pk_open = crypto_pk_libgcrypt_open, + .pk_open_priv = crypto_pk_libgcrypt_open_priv, + .pk_genkey = crypto_pk_libgcrypt_genkey, +}; + +struct crypto_backend *crypto_libgcrypt_init(void) +{ + /* Version check should be the very first call because it + * makes sure that important subsystems are intialized. */ + if (!gcry_check_version (GCRYPT_VERSION)) { + fputs ("libgcrypt version mismatch\n", stderr); + return NULL; + } + + /* We don't want to see any warnings, e.g. because we have not yet + * parsed program options which might be used to suppress such + * warnings. */ + gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); + + /* ... If required, other initialization goes here. Note that the + * process might still be running with increased privileges and that + * the secure memory has not been intialized. */ + + /* Allocate a pool of 16k secure memory. This make the secure memory + * available and also drops privileges where needed. */ + gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0); + + /* It is now okay to let Libgcrypt complain when there was/is + * a problem with the secure memory. */ + gcry_control (GCRYCTL_RESUME_SECMEM_WARN); + + /* ... If required, other initialization goes here. */ + + /* Tell Libgcrypt that initialization has completed. */ + gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0); + +// gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 3u , 0); +// gcry_control (GCRYCTL_PRINT_CONFIG, stdout); + + return &crypto_libgcrypt_backend; +} diff --git a/client/emv/emv_pk.c b/client/emv/emv_pk.c new file mode 100644 index 00000000..d9b3ac32 --- /dev/null +++ b/client/emv/emv_pk.c @@ -0,0 +1,521 @@ +/* + * libopenemv - a library to work with EMV family of smart cards + * Copyright (C) 2012, 2015 Dmitry Eremin-Solenikov + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "emv_pk.h" +#include "crypto.h" + +/* For asprintf */ +#define _GNU_SOURCE +#include +#include +#include +#include + +#define BCD(c) (((c) >= '0' && (c) <= '9') ? ((c) - '0') : \ + -1) + +#define HEX(c) (((c) >= '0' && (c) <= '9') ? ((c) - '0') : \ + ((c) >= 'A' && (c) <= 'F') ? ((c) - 'A' + 10) : \ + ((c) >= 'a' && (c) <= 'f') ? ((c) - 'a' + 10) : \ + -1) + +#define TOHEX(v) ((v) < 10 ? (v) + '0' : (v) - 10 + 'a') + +static ssize_t emv_pk_read_bin(char *buf, unsigned char *bin, size_t size, size_t *read) +{ + size_t left = size; + char *p = buf; + while (*p && *p == ' ') + p++; + + while (left > 0) { + int c1, c2; + c1 = HEX(*p); + if (c1 == -1) + return -(p - buf); + p++; + c2 = HEX(*p); + if (c2 == -1) + return -(p - buf); + p++; + *bin = (c1 * 16 + c2); + bin ++; + left --; + if (*p == ':') + p++; + else if (read) { + *read = (size - left); + break; + } else if (left == 0) + break; + else + return -(p - buf); + } + + while (*p && *p == ' ') + p++; + + p--; + + return (p - buf); +} + +static ssize_t emv_pk_read_ymv(char *buf, unsigned *ymv) +{ + int i; + unsigned char temp[3]; + char *p = buf; + + *ymv = 0; + + while (*p && *p == ' ') + p++; + + for (i = 0; i < 3; i++) { + int c1, c2; + c1 = BCD(*p); + if (c1 == -1) + return -(p - buf); + p++; + c2 = BCD(*p); + if (c2 == -1) + return -(p - buf); + p++; + temp[i] = (c1 * 16 + c2); + } + + while (*p && *p == ' ') + p++; + + p--; + + if (temp[1] > 0x12 || temp[2] > 0x31) + return -(p - buf); + + *ymv = (temp[0] * 0x10000 + temp[1] * 0x100 + temp[2]); + + return (p - buf); +} + +static ssize_t emv_pk_read_string(char *buf, char *str, size_t size) +{ + char *p = buf; + while (*p && *p == ' ') + p++; + + while (size > 1) { + if (*p == ' ') + break; + else if (*p < 0x20 || *p >= 0x7f) + return -(p - buf); + *str = *p; + p++; + str ++; + size --; + } + + *str = 0; + + while (*p && *p == ' ') + p++; + + p--; + + return (p - buf); +} + + +struct emv_pk *emv_pk_parse_pk(char *buf) +{ + struct emv_pk *r = calloc(1, sizeof(*r)); + ssize_t l; + char temp[10]; + + l = emv_pk_read_bin(buf, r->rid, 5, NULL); + if (l <= 0) + goto out; + buf += l; + + l = emv_pk_read_bin(buf, &r->index, 1, NULL); + if (l <= 0) + goto out; + buf += l; + + l = emv_pk_read_ymv(buf, &r->expire); + if (l <= 0) + goto out; + buf += l; + + l = emv_pk_read_string(buf, temp, sizeof(temp)); + if (l <= 0) + goto out; + buf += l; + + if (!strcmp(temp, "rsa")) + r->pk_algo = PK_RSA; + else + goto out; + + l = emv_pk_read_bin(buf, r->exp, sizeof(r->exp), &r->elen); + if (l <= 0) + goto out; + buf += l; + + r->modulus = malloc(2048/8); + l = emv_pk_read_bin(buf, r->modulus, 2048/8, &r->mlen); + if (l <= 0) + goto out2; + buf += l; + + l = emv_pk_read_string(buf, temp, sizeof(temp)); + if (l <= 0) + goto out2; + buf += l; + + if (!strcmp(temp, "sha1")) + r->hash_algo = HASH_SHA_1; + else + goto out2; + + l = emv_pk_read_bin(buf, r->hash, 20, NULL); + if (l <= 0) + goto out2; + + return r; + +out2: + free(r->modulus); +out: + free(r); + return NULL; +} + +static size_t emv_pk_write_bin(char *out, size_t outlen, const unsigned char *buf, size_t len) +{ + int i; + size_t pos = 0; + + if (len == 0) + return 0; + if (outlen < len * 3) + return 0; + + out[pos++] = TOHEX(buf[0] >> 4); + out[pos++] = TOHEX(buf[0] & 0xf); + for (i = 1; i < len; i++) { + out[pos++] = ':'; + out[pos++] = TOHEX(buf[i] >> 4); + out[pos++] = TOHEX(buf[i] & 0xf); + } + out[pos++] = ' '; + + return pos; +} + +static size_t emv_pk_write_str(char *out, size_t outlen, const char *str) +{ + size_t len = strlen(str); + + if (len == 0) + return 0; + if (outlen < len) + return 0; + + memcpy(out, str, len); + + return len; +} + +char *emv_pk_dump_pk(const struct emv_pk *pk) +{ + size_t outsize = 1024; /* should be enough */ + char *out = malloc(outsize); /* should be enough */ + size_t outpos = 0; + size_t rc; + + if (!out) + return NULL; + + rc = emv_pk_write_bin(out + outpos, outsize - outpos, pk->rid, 5); + if (rc == 0) + goto err; + outpos += rc; + + rc = emv_pk_write_bin(out + outpos, outsize - outpos, &pk->index, 1); + if (rc == 0) + goto err; + outpos += rc; + + if (outpos + 7 > outsize) + goto err; + out[outpos++] = TOHEX((pk->expire >> 20) & 0xf); + out[outpos++] = TOHEX((pk->expire >> 16) & 0xf); + out[outpos++] = TOHEX((pk->expire >> 12) & 0xf); + out[outpos++] = TOHEX((pk->expire >> 8 ) & 0xf); + out[outpos++] = TOHEX((pk->expire >> 4 ) & 0xf); + out[outpos++] = TOHEX((pk->expire >> 0 ) & 0xf); + out[outpos++] = ' '; + + if (pk->pk_algo == PK_RSA) { + rc = emv_pk_write_str(out + outpos, outsize - outpos, "rsa"); + if (rc == 0) + goto err; + outpos += rc; + out[outpos++] = ' '; + } else { + if (outpos + 4 > outsize) + goto err; + out[outpos++] = '?'; + out[outpos++] = '?'; + out[outpos++] = TOHEX(pk->pk_algo >> 4); + out[outpos++] = TOHEX(pk->pk_algo & 0xf); + } + + rc = emv_pk_write_bin(out + outpos, outsize - outpos, pk->exp, pk->elen); + if (rc == 0) + goto err; + outpos += rc; + + rc = emv_pk_write_bin(out + outpos, outsize - outpos, pk->modulus, pk->mlen); + if (rc == 0) + goto err; + outpos += rc; + + if (pk->hash_algo == HASH_SHA_1) { + rc = emv_pk_write_str(out + outpos, outsize - outpos, "sha1"); + if (rc == 0) + goto err; + outpos += rc; + out[outpos++] = ' '; + } else { + if (outpos + 4 > outsize) + goto err; + out[outpos++] = '?'; + out[outpos++] = '?'; + out[outpos++] = TOHEX(pk->pk_algo >> 4); + out[outpos++] = TOHEX(pk->pk_algo & 0xf); + } + + + rc = emv_pk_write_bin(out + outpos, outsize - outpos, pk->hash, 20); + if (rc == 0) + goto err; + outpos += rc; + + out[outpos-1] = '\0'; + + return out; + +err: + free(out); + return NULL; +} + +bool emv_pk_verify(const struct emv_pk *pk) +{ + struct crypto_hash *ch = crypto_hash_open(pk->hash_algo); + if (!ch) + return false; + + crypto_hash_write(ch, pk->rid, sizeof(pk->rid)); + crypto_hash_write(ch, &pk->index, 1); + crypto_hash_write(ch, pk->modulus, pk->mlen); + crypto_hash_write(ch, pk->exp, pk->elen); + + unsigned char *h = crypto_hash_read(ch); + if (!h) { + crypto_hash_close(ch); + return false; + } + + size_t hsize = crypto_hash_get_size(ch); + bool r = hsize && !memcmp(h, pk->hash, hsize) ? true : false; + + crypto_hash_close(ch); + + return r; +} + +struct emv_pk *emv_pk_new(size_t modlen, size_t explen) +{ + struct emv_pk *pk; + + /* Not supported ATM */ + if (explen > 3) + return NULL; + + pk = calloc(1, sizeof(*pk)); + if (!pk) + return NULL; + + pk->mlen = modlen; + pk->elen = explen; + + pk->modulus = calloc(modlen, 1); + if (!pk->modulus) { + free(pk); + pk = NULL; + } + + return pk; +} + +void emv_pk_free(struct emv_pk *pk) +{ + if (!pk) + return; + + free(pk->modulus); + free(pk); +} + +static struct emv_pk *emv_pk_get_ca_pk_from_file(const char *fname, + const unsigned char *rid, + unsigned char idx) +{ + if (!fname) + return NULL; + + FILE *f = fopen(fname, "r"); + if (!f) { + perror("fopen"); + return NULL; + } + + while (!feof(f)) { + char buf[BUFSIZ]; + if (fgets(buf, sizeof(buf), f) == NULL) + break; + + struct emv_pk *pk = emv_pk_parse_pk(buf); + if (!pk) + continue; + if (memcmp(pk->rid, rid, 5) || pk->index != idx) { + emv_pk_free(pk); + continue; + } + + fclose(f); + + return pk; + } + + fclose(f); + + return NULL; +} + +char *emv_pk_get_ca_pk_file(const char *dirname, const unsigned char *rid, unsigned char idx) +{ + if (!dirname) + dirname = ".";//openemv_config_get_str("capk.dir", NULL); + + if (!dirname) + return NULL; + + char *filename; + int ret = asprintf(&filename, "%s/%02hhx%02hhx%02hhx%02hhx%02hhx_%02hhx.0", + dirname, + rid[0], + rid[1], + rid[2], + rid[3], + rid[4], + idx); + + if (ret <= 0) + return NULL; + + return filename; +} + +char *emv_pk_get_ca_pk_rid_file(const char *dirname, const unsigned char *rid) +{ + if (!dirname) + dirname = "."; //openemv_config_get_str("capk.dir", NULL); + + if (!dirname) + return NULL; + + char *filename; + int ret = asprintf(&filename, "%s/%02hhx%02hhx%02hhx%02hhx%02hhx.pks", + dirname, + rid[0], + rid[1], + rid[2], + rid[3], + rid[4]); + + if (ret <= 0) + return NULL; + + return filename; +} + +struct emv_pk *emv_pk_get_ca_pk(const unsigned char *rid, unsigned char idx) +{ + struct emv_pk *pk = NULL; + + if (!pk) { + char *fname = emv_pk_get_ca_pk_file(NULL, rid, idx); + if (fname) { + pk = emv_pk_get_ca_pk_from_file(fname, rid, idx); + free(fname); + } + } + + if (!pk) { + char *fname = emv_pk_get_ca_pk_rid_file(NULL, rid); + if (fname) { + pk = emv_pk_get_ca_pk_from_file(fname, rid, idx); + free(fname); + } + } + + if (!pk) { + const char *fname = "capk.txt"; //openemv_config_get_str("capk.file", NULL); + if (!fname) { + fprintf(stderr, "No CA PK file specified!\n"); + return NULL; + } + + pk = emv_pk_get_ca_pk_from_file(fname, rid, idx); + } + if (!pk) + return NULL; + + printf("Verifying CA PK for %02hhx:%02hhx:%02hhx:%02hhx:%02hhx IDX %02hhx %zd bits...", + pk->rid[0], + pk->rid[1], + pk->rid[2], + pk->rid[3], + pk->rid[4], + pk->index, + pk->mlen * 8); + if (emv_pk_verify(pk)) { + printf("OK\n"); + + return pk; + } + + printf("Failed!\n"); + emv_pk_free(pk); + + return NULL; +} diff --git a/client/emv/emv_pk.h b/client/emv/emv_pk.h new file mode 100644 index 00000000..e291a065 --- /dev/null +++ b/client/emv/emv_pk.h @@ -0,0 +1,48 @@ +/* + * libopenemv - a library to work with EMV family of smart cards + * Copyright (C) 2015 Dmitry Eremin-Solenikov + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ + +#ifndef EMV_PK_H +#define EMV_PK_H + +#include +#include + +struct emv_pk { + unsigned char rid[5]; + unsigned char index; + unsigned char serial[3]; + unsigned char pan[10]; + unsigned char hash_algo; + unsigned char pk_algo; + unsigned char hash[20]; + unsigned char exp[3]; + size_t elen; + size_t mlen; + unsigned char *modulus; + unsigned int expire; +}; + +#define EXPIRE(yy, mm, dd) 0x ## yy ## mm ## dd + +struct emv_pk *emv_pk_parse_pk(char *buf); +struct emv_pk *emv_pk_new(size_t modlen, size_t explen); +void emv_pk_free(struct emv_pk *pk); +char *emv_pk_dump_pk(const struct emv_pk *pk); +bool emv_pk_verify(const struct emv_pk *pk); + +char *emv_pk_get_ca_pk_file(const char *dirname, const unsigned char *rid, unsigned char idx); +char *emv_pk_get_ca_pk_rid_file(const char *dirname, const unsigned char *rid); +struct emv_pk *emv_pk_get_ca_pk(const unsigned char *rid, unsigned char idx); +#endif diff --git a/client/emv/emv_pki.c b/client/emv/emv_pki.c new file mode 100644 index 00000000..e4a9cc68 --- /dev/null +++ b/client/emv/emv_pki.c @@ -0,0 +1,390 @@ +/* + * libopenemv - a library to work with EMV family of smart cards + * Copyright (C) 2015 Dmitry Eremin-Solenikov + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "emv_pki.h" +#include "crypto.h" + +#include +#include +#include + +static const unsigned char empty_tlv_value[] = {}; +static const struct tlv empty_tlv = {.tag = 0x0, .len = 0, .value = empty_tlv_value}; + +static size_t emv_pki_hash_psn[256] = { 0, 0, 11, 2, 17, 2, }; + +static unsigned char *emv_pki_decode_message(const struct emv_pk *enc_pk, + uint8_t msgtype, + size_t *len, + const struct tlv *cert_tlv, + ... /* A list of tlv pointers, end with NULL */ + ) +{ + struct crypto_pk *kcp; + unsigned char *data; + size_t data_len; + va_list vl; + + if (!enc_pk) + return NULL; + + if (!cert_tlv) + return NULL; + + if (cert_tlv->len != enc_pk->mlen) + return NULL; + + kcp = crypto_pk_open(enc_pk->pk_algo, + enc_pk->modulus, enc_pk->mlen, + enc_pk->exp, enc_pk->elen); + if (!kcp) + return NULL; + + data = crypto_pk_encrypt(kcp, cert_tlv->value, cert_tlv->len, &data_len); + crypto_pk_close(kcp); + + if (data[data_len-1] != 0xbc || data[0] != 0x6a || data[1] != msgtype) { + free(data); + return NULL; + } + + size_t hash_pos = emv_pki_hash_psn[msgtype]; + if (hash_pos == 0 || hash_pos > data_len){ + free(data); + return NULL; + } + + struct crypto_hash *ch; + ch = crypto_hash_open(data[hash_pos]); + if (!ch) { + free(data); + return NULL; + } + + size_t hash_len = crypto_hash_get_size(ch); + crypto_hash_write(ch, data + 1, data_len - 2 - hash_len); + + va_start(vl, cert_tlv); + while (true) { + const struct tlv *add_tlv = va_arg(vl, const struct tlv *); + if (!add_tlv) + break; + + crypto_hash_write(ch, add_tlv->value, add_tlv->len); + } + va_end(vl); + + if (memcmp(data + data_len - 1 - hash_len, crypto_hash_read(ch), hash_len)) { + crypto_hash_close(ch); + free(data); + return NULL; + } + + crypto_hash_close(ch); + + *len = data_len - hash_len - 1; + + return data; +} + +static unsigned emv_cn_length(const struct tlv *tlv) +{ + int i; + + for (i = 0; i < tlv->len; i++) { + unsigned char c = tlv->value[i]; + + if (c >> 4 == 0xf) + return 2 * i; + + if ((c & 0xf) == 0xf) + return 2 * i + 1; + } + + return 2 * tlv->len; +} + +static unsigned char emv_cn_get(const struct tlv *tlv, unsigned pos) +{ + if (pos > tlv->len * 2) + return 0xf; + + unsigned char c = tlv->value[pos / 2]; + + if (pos % 2) + return c & 0xf; + else + return c >> 4; +} + +static struct emv_pk *emv_pki_decode_key(const struct emv_pk *enc_pk, + unsigned char msgtype, + const struct tlv *pan_tlv, + const struct tlv *cert_tlv, + const struct tlv *exp_tlv, + const struct tlv *rem_tlv, + const struct tlv *add_tlv + ) +{ + size_t pan_length; + unsigned char *data; + size_t data_len; + size_t pk_len; + + if (!cert_tlv || !exp_tlv || !pan_tlv) + return NULL; + + if (!rem_tlv) + rem_tlv = &empty_tlv; + + if (msgtype == 2) + pan_length = 4; + else if (msgtype == 4) + pan_length = 10; + else + return NULL; + + data = emv_pki_decode_message(enc_pk, msgtype, &data_len, + cert_tlv, + rem_tlv, + exp_tlv, + add_tlv, + NULL); + if (!data || data_len < 11 + pan_length) + return NULL; + + /* Perform the rest of checks here */ + + struct tlv pan2_tlv = { + .tag = 0x5a, + .len = pan_length, + .value = &data[2], + }; + unsigned pan_len = emv_cn_length(pan_tlv); + unsigned pan2_len = emv_cn_length(&pan2_tlv); + + if (((msgtype == 2) && (pan2_len < 4 || pan2_len > pan_len)) || + ((msgtype == 4) && (pan2_len != pan_len))) { + free(data); + + return NULL; + } + + unsigned i; + for (i = 0; i < pan2_len; i++) + if (emv_cn_get(pan_tlv, i) != emv_cn_get(&pan2_tlv, i)) { + free(data); + + return NULL; + } + + pk_len = data[9 + pan_length]; + if (pk_len > data_len - 11 - pan_length + rem_tlv->len) { + free(data); + return NULL; + } + + if (exp_tlv->len != data[10 + pan_length]) { + free(data); + return NULL; + } + + struct emv_pk *pk = emv_pk_new(pk_len, exp_tlv->len); + + memcpy(pk->rid, enc_pk->rid, 5); + pk->index = enc_pk->index; + + pk->hash_algo = data[7 + pan_length]; + pk->pk_algo = data[8 + pan_length]; + pk->expire = (data[3 + pan_length] << 16) | (data[2 + pan_length] << 8) | 0x31; + memcpy(pk->serial, data + 4 + pan_length, 3); + memcpy(pk->pan, data + 2, pan_length); + memset(pk->pan + pan_length, 0xff, 10 - pan_length); + + memcpy(pk->modulus, data + 11 + pan_length, + pk_len < data_len - (11 + pan_length) ? + pk_len : + data_len - (11 + pan_length)); + memcpy(pk->modulus + data_len - (11 + pan_length), rem_tlv->value, rem_tlv->len); + memcpy(pk->exp, exp_tlv->value, exp_tlv->len); + + free(data); + + return pk; +} + +struct emv_pk *emv_pki_recover_issuer_cert(const struct emv_pk *pk, struct tlvdb *db) +{ + return emv_pki_decode_key(pk, 2, + tlvdb_get(db, 0x5a, NULL), + tlvdb_get(db, 0x90, NULL), + tlvdb_get(db, 0x9f32, NULL), + tlvdb_get(db, 0x92, NULL), + NULL); +} + +struct emv_pk *emv_pki_recover_icc_cert(const struct emv_pk *pk, struct tlvdb *db, const struct tlv *sda_tlv) +{ + return emv_pki_decode_key(pk, 4, + tlvdb_get(db, 0x5a, NULL), + tlvdb_get(db, 0x9f46, NULL), + tlvdb_get(db, 0x9f47, NULL), + tlvdb_get(db, 0x9f48, NULL), + sda_tlv); +} + +struct emv_pk *emv_pki_recover_icc_pe_cert(const struct emv_pk *pk, struct tlvdb *db) +{ + return emv_pki_decode_key(pk, 4, + tlvdb_get(db, 0x5a, NULL), + tlvdb_get(db, 0x9f2d, NULL), + tlvdb_get(db, 0x9f2e, NULL), + tlvdb_get(db, 0x9f2f, NULL), + NULL); +} + +struct tlvdb *emv_pki_recover_dac(const struct emv_pk *enc_pk, const struct tlvdb *db, const struct tlv *sda_tlv) +{ + size_t data_len; + unsigned char *data = emv_pki_decode_message(enc_pk, 3, &data_len, + tlvdb_get(db, 0x93, NULL), + sda_tlv, + NULL); + + if (!data || data_len < 5) + return NULL; + + struct tlvdb *dac_db = tlvdb_fixed(0x9f45, 2, data+3); + + free(data); + + return dac_db; +} + +struct tlvdb *emv_pki_recover_idn(const struct emv_pk *enc_pk, const struct tlvdb *db, const struct tlv *dyn_tlv) +{ + size_t data_len; + unsigned char *data = emv_pki_decode_message(enc_pk, 5, &data_len, + tlvdb_get(db, 0x9f4b, NULL), + dyn_tlv, + NULL); + + if (!data || data_len < 3) + return NULL; + + if (data[3] < 2 || data[3] > data_len - 3) { + free(data); + return NULL; + } + + size_t idn_len = data[4]; + if (idn_len > data[3] - 1) { + free(data); + return NULL; + } + + struct tlvdb *idn_db = tlvdb_fixed(0x9f4c, idn_len, data + 5); + free(data); + + return idn_db; +} + +static bool tlv_hash(void *data, const struct tlv *tlv, int level, bool is_leaf) +{ + struct crypto_hash *ch = data; + size_t tag_len; + unsigned char *tag; + + if (tlv_is_constructed(tlv)) + return true; + + if (tlv->tag == 0x9f4b) + return true; + + tag = tlv_encode(tlv, &tag_len); + crypto_hash_write(ch, tag, tag_len); + free(tag); + + return true; +} + +struct tlvdb *emv_pki_perform_cda(const struct emv_pk *enc_pk, const struct tlvdb *db, + const struct tlvdb *this_db, + const struct tlv *pdol_data_tlv, + const struct tlv *crm1_tlv, + const struct tlv *crm2_tlv) +{ + const struct tlv *un_tlv = tlvdb_get(db, 0x9f37, NULL); + const struct tlv *cid_tlv = tlvdb_get(this_db, 0x9f27, NULL); + + if (!un_tlv || !cid_tlv) + return NULL; + + size_t data_len; + unsigned char *data = emv_pki_decode_message(enc_pk, 5, &data_len, + tlvdb_get(this_db, 0x9f4b, NULL), + un_tlv, + NULL); + if (!data || data_len < 3) + return NULL; + + if (data[3] < 30 || data[3] > data_len - 4) { + free(data); + return NULL; + } + + if (!cid_tlv || cid_tlv->len != 1 || cid_tlv->value[0] != data[5 + data[4]]) { + free(data); + return NULL; + } + + struct crypto_hash *ch; + ch = crypto_hash_open(enc_pk->hash_algo); + if (!ch) { + free(data); + return NULL; + } + + if (pdol_data_tlv) + crypto_hash_write(ch, pdol_data_tlv->value, pdol_data_tlv->len); + if (crm1_tlv) + crypto_hash_write(ch, crm1_tlv->value, crm1_tlv->len); + if (crm2_tlv) + crypto_hash_write(ch, crm2_tlv->value, crm2_tlv->len); + + tlvdb_visit(this_db, tlv_hash, ch, 0); + + if (memcmp(data + 5 + data[4] + 1 + 8, crypto_hash_read(ch), 20)) { + crypto_hash_close(ch); + free(data); + return NULL; + } + crypto_hash_close(ch); + + size_t idn_len = data[4]; + if (idn_len > data[3] - 1) { + free(data); + return NULL; + } + + struct tlvdb *idn_db = tlvdb_fixed(0x9f4c, idn_len, data + 5); + free(data); + + return idn_db; +} diff --git a/client/emv/emv_pki.h b/client/emv/emv_pki.h new file mode 100644 index 00000000..72d84cc4 --- /dev/null +++ b/client/emv/emv_pki.h @@ -0,0 +1,36 @@ +/* + * libopenemv - a library to work with EMV family of smart cards + * Copyright (C) 2015 Dmitry Eremin-Solenikov + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ + +#ifndef EMV_PKI_H +#define EMV_PKI_H + +#include "emv_pk.h" +#include "tlv.h" + +#include + +struct emv_pk *emv_pki_recover_issuer_cert(const struct emv_pk *pk, struct tlvdb *db); +struct emv_pk *emv_pki_recover_icc_cert(const struct emv_pk *pk, struct tlvdb *db, const struct tlv *sda_tlv); +struct emv_pk *emv_pki_recover_icc_pe_cert(const struct emv_pk *pk, struct tlvdb *db); + +struct tlvdb *emv_pki_recover_dac(const struct emv_pk *pk, const struct tlvdb *db, const struct tlv *sda_tlv); +struct tlvdb *emv_pki_recover_idn(const struct emv_pk *pk, const struct tlvdb *db, const struct tlv *dyn_tlv); +struct tlvdb *emv_pki_perform_cda(const struct emv_pk *enc_pk, const struct tlvdb *db, + const struct tlvdb *this_db, + const struct tlv *pdol_data_tlv, + const struct tlv *crm1_tlv, + const struct tlv *crm2_tlv); + +#endif diff --git a/client/emv/emv_pki_priv.c b/client/emv/emv_pki_priv.c new file mode 100644 index 00000000..a7043a77 --- /dev/null +++ b/client/emv/emv_pki_priv.c @@ -0,0 +1,281 @@ +/* + * libopenemv - a library to work with EMV family of smart cards + * Copyright (C) 2015 Dmitry Eremin-Solenikov + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "emv_pki_priv.h" +#include "crypto.h" + +#include +#include +#include + +struct emv_pk *emv_pki_make_ca(const struct crypto_pk *cp, + const unsigned char *rid, unsigned char index, + unsigned int expire, enum crypto_algo_hash hash_algo) +{ + size_t modlen, explen; + unsigned char *mod, *exp; + + if (!rid) + return NULL; + + mod = crypto_pk_get_parameter(cp, 0, &modlen); + exp = crypto_pk_get_parameter(cp, 1, &explen); + + if (!mod || !modlen || !exp || !explen) { + free(mod); + free(exp); + + return NULL; + } + + struct emv_pk *pk = emv_pk_new(modlen, explen); + memcpy(pk->rid, rid, 5); + pk->index = index; + pk->expire = expire; + pk->pk_algo = crypto_pk_get_algo(cp); + pk->hash_algo = hash_algo; + memcpy(pk->modulus, mod, modlen); + memcpy(pk->exp, exp, explen); + + free(mod); + free(exp); + + struct crypto_hash *ch = crypto_hash_open(pk->hash_algo); + if (!ch) + return false; + + crypto_hash_write(ch, pk->rid, sizeof(pk->rid)); + crypto_hash_write(ch, &pk->index, 1); + crypto_hash_write(ch, pk->modulus, pk->mlen); + crypto_hash_write(ch, pk->exp, pk->elen); + + unsigned char *h = crypto_hash_read(ch); + if (!h) { + crypto_hash_close(ch); + emv_pk_free(pk); + + return NULL; + } + + memcpy(pk->hash, h, crypto_hash_get_size(ch)); + crypto_hash_close(ch); + + return pk; +} + +static struct tlvdb *emv_pki_sign_message(const struct crypto_pk *cp, + tlv_tag_t cert_tag, tlv_tag_t rem_tag, + const unsigned char *msg, size_t msg_len, + ... /* A list of tlv pointers, end with NULL */ + ) +{ + size_t tmp_len = (crypto_pk_get_nbits(cp) + 7) / 8; + unsigned char *tmp = malloc(tmp_len); + if (!tmp) + return NULL; + + // XXX + struct crypto_hash *ch = crypto_hash_open(HASH_SHA_1); + if (!ch) { + free(tmp); + + return NULL; + } + + tmp[0] = 0x6a; + tmp[tmp_len - 1] = 0xbc; + + const unsigned char *rem; + size_t rem_len; + size_t hash_len = crypto_hash_get_size(ch); + size_t part_len = tmp_len - 2 - hash_len; + if (part_len < msg_len) { + memcpy(tmp + 1, msg, part_len); + rem = msg + part_len; + rem_len = msg_len - part_len; + } else { + memcpy(tmp + 1, msg, msg_len); + memset(tmp + 1 + msg_len, 0xbb, part_len - msg_len); + rem = NULL; + rem_len = 0; + } + crypto_hash_write(ch, tmp + 1, part_len); + crypto_hash_write(ch, rem, rem_len); + + va_list vl; + va_start(vl, msg_len); + while (true) { + const struct tlv *add_tlv = va_arg(vl, const struct tlv *); + if (!add_tlv) + break; + + crypto_hash_write(ch, add_tlv->value, add_tlv->len); + } + va_end(vl); + + unsigned char *h = crypto_hash_read(ch); + if (!h) { + crypto_hash_close(ch); + free(tmp); + + return NULL; + } + + memcpy(tmp + 1 + part_len, h, hash_len); + crypto_hash_close(ch); + + size_t cert_len; + unsigned char *cert = crypto_pk_decrypt(cp, tmp, tmp_len, &cert_len); + free(tmp); + + if (!cert) + return NULL; + + struct tlvdb *db = tlvdb_fixed(cert_tag, cert_len, cert); + free(cert); + if (!db) + return NULL; + + if (rem) { + struct tlvdb *rdb = tlvdb_fixed(rem_tag, rem_len, rem); + if (!rdb) { + tlvdb_free(db); + + return NULL; + } + tlvdb_add(db, rdb); + } + + return db; +} + +static struct tlvdb *emv_pki_sign_key(const struct crypto_pk *cp, + struct emv_pk *ipk, + unsigned char msgtype, + size_t pan_len, + tlv_tag_t cert_tag, + tlv_tag_t exp_tag, + tlv_tag_t rem_tag, + const struct tlv *add_tlv + ) +{ + unsigned pos = 0; + unsigned char *msg = malloc(1 + pan_len + 2 + 3 + 1 + 1 + 1 + 1 + ipk->mlen); + + if (!msg) + return NULL; + + msg[pos++] = msgtype; + memcpy(msg + pos, ipk->pan, pan_len); pos += pan_len; + msg[pos++] = (ipk->expire >> 8) & 0xff; + msg[pos++] = (ipk->expire >> 16) & 0xff; + memcpy(msg + pos, ipk->serial, 3); pos += 3; + msg[pos++] = ipk->hash_algo; + msg[pos++] = ipk->pk_algo; + msg[pos++] = ipk->mlen; + msg[pos++] = ipk->elen; + memcpy(msg + pos, ipk->modulus, ipk->mlen); + pos += ipk->mlen; + + struct tlvdb *exp_db = tlvdb_fixed(exp_tag, ipk->elen, ipk->exp); + if (!exp_db) { + free(msg); + + return NULL; + } + + struct tlvdb *db = emv_pki_sign_message(cp, + cert_tag, rem_tag, + msg, pos, + tlvdb_get(exp_db, exp_tag, NULL), + add_tlv, + NULL); + free(msg); + if (!db) + return NULL; + + tlvdb_add(db, exp_db); + + return db; +} + +struct tlvdb *emv_pki_sign_issuer_cert(const struct crypto_pk *cp, struct emv_pk *issuer_pk) +{ + return emv_pki_sign_key(cp, issuer_pk, 2, 4, 0x90, 0x9f32, 0x92, NULL); +} + +struct tlvdb *emv_pki_sign_icc_cert(const struct crypto_pk *cp, struct emv_pk *icc_pk, const struct tlv *sda_tlv) +{ + return emv_pki_sign_key(cp, icc_pk, 4, 10, 0x9f46, 0x9f47, 0x9f48, sda_tlv); +} + +struct tlvdb *emv_pki_sign_icc_pe_cert(const struct crypto_pk *cp, struct emv_pk *icc_pe_pk) +{ + return emv_pki_sign_key(cp, icc_pe_pk, 4, 10, 0x9f2d, 0x9f2e, 0x9f2f, NULL); +} + +struct tlvdb *emv_pki_sign_dac(const struct crypto_pk *cp, const struct tlv *dac_tlv, const struct tlv *sda_tlv) +{ + unsigned pos = 0; + unsigned char *msg = malloc(1+1+dac_tlv->len); + + if (!msg) + return NULL; + + msg[pos++] = 3; + msg[pos++] = HASH_SHA_1; + memcpy(msg+pos, dac_tlv->value, dac_tlv->len); + pos += dac_tlv->len; + + struct tlvdb *db = emv_pki_sign_message(cp, + 0x93, 0, + msg, pos, + sda_tlv, + NULL); + + free(msg); + + return db; +} + +struct tlvdb *emv_pki_sign_idn(const struct crypto_pk *cp, const struct tlv *idn_tlv, const struct tlv *dyn_tlv) +{ + unsigned pos = 0; + unsigned char *msg = malloc(1+1+1+1+idn_tlv->len); + + if (!msg) + return NULL; + + msg[pos++] = 5; + msg[pos++] = HASH_SHA_1; + msg[pos++] = idn_tlv->len + 1; + msg[pos++] = idn_tlv->len; + memcpy(msg+pos, idn_tlv->value, idn_tlv->len); + pos += idn_tlv->len; + + struct tlvdb *db = emv_pki_sign_message(cp, + 0x9f4b, 0, + msg, pos, + dyn_tlv, + NULL); + + free(msg); + + return db; +} diff --git a/client/emv/emv_pki_priv.h b/client/emv/emv_pki_priv.h new file mode 100644 index 00000000..4f1639d5 --- /dev/null +++ b/client/emv/emv_pki_priv.h @@ -0,0 +1,35 @@ +/* + * libopenemv - a library to work with EMV family of smart cards + * Copyright (C) 2015 Dmitry Eremin-Solenikov + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ + +#ifndef EMV_PKI_PRIV_H +#define EMV_PKI_PRIV_H + +#include "crypto.h" +#include "emv_pk.h" +#include "tlv.h" + +#include + +struct emv_pk *emv_pki_make_ca(const struct crypto_pk *cp, + const unsigned char *rid, unsigned char index, + unsigned int expire, enum crypto_algo_hash hash_algo); +struct tlvdb *emv_pki_sign_issuer_cert(const struct crypto_pk *cp, struct emv_pk *issuer_pk); +struct tlvdb *emv_pki_sign_icc_cert(const struct crypto_pk *cp, struct emv_pk *icc_pk, const struct tlv *sda_tlv); +struct tlvdb *emv_pki_sign_icc_pe_cert(const struct crypto_pk *cp, struct emv_pk *icc_pe_pk); + +struct tlvdb *emv_pki_sign_dac(const struct crypto_pk *cp, const struct tlv *dac_tlv, const struct tlv *sda_tlv); +struct tlvdb *emv_pki_sign_idn(const struct crypto_pk *cp, const struct tlv *idn_tlv, const struct tlv *dyn_tlv); + +#endif