1635 lines
50 KiB
C
1635 lines
50 KiB
C
/*
|
|
* card-openpgp.c: Support for OpenPGP card
|
|
*
|
|
* Copyright (C) 2003 Olaf Kirch <okir@suse.de>
|
|
*
|
|
* 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.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
/*
|
|
* Specifications:
|
|
* http://www.g10code.de/docs/openpgp-card-1.0.pdf (obsolete)
|
|
* http://www.g10code.de/docs/openpgp-card-1.1.pdf
|
|
* http://www.g10code.de/docs/openpgp-card-2.0.pdf
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
|
|
#include "internal.h"
|
|
#include "asn1.h"
|
|
#include "cardctl.h"
|
|
#include "errors.h"
|
|
|
|
static struct sc_atr_table pgp_atrs[] = {
|
|
{ "3b:fa:13:00:ff:81:31:80:45:00:31:c1:73:c0:01:00:00:90:00:b1", NULL, "OpenPGP card v1.0/1.1", SC_CARD_TYPE_OPENPGP_V1, 0, NULL },
|
|
{ "3b:da:18:ff:81:b1:fe:75:1f:03:00:31:c5:73:c0:01:40:00:90:00:0c", NULL, "CryptoStick v1.2 (OpenPGP v2.0)", SC_CARD_TYPE_OPENPGP_V2, 0, NULL },
|
|
{ NULL, NULL, NULL, 0, 0, NULL }
|
|
};
|
|
|
|
static struct sc_card_operations *iso_ops;
|
|
static struct sc_card_operations pgp_ops;
|
|
static struct sc_card_driver pgp_drv = {
|
|
"OpenPGP card",
|
|
"openpgp",
|
|
&pgp_ops,
|
|
NULL, 0, NULL
|
|
};
|
|
|
|
/*
|
|
* The OpenPGP card doesn't have a file system, instead everything
|
|
* is stored in data objects that are accessed through GET/PUT.
|
|
*
|
|
* However, much inside OpenSC's pkcs15 implementation is based on
|
|
* the assumption that we have a file system. So we fake one here.
|
|
*
|
|
* Selecting the MF causes us to select the OpenPGP AID.
|
|
*
|
|
* Everything else is mapped to "file" IDs.
|
|
*/
|
|
|
|
enum _type { /* DO type */
|
|
SIMPLE = SC_FILE_TYPE_WORKING_EF,
|
|
CONSTRUCTED = SC_FILE_TYPE_DF
|
|
};
|
|
|
|
enum _version { /* 2-byte BCD-alike encoded version number */
|
|
OPENPGP_CARD_1_0 = 0x0100,
|
|
OPENPGP_CARD_1_1 = 0x0101,
|
|
OPENPGP_CARD_2_0 = 0x0200
|
|
};
|
|
|
|
enum _access { /* access flags for the respective DO/file */
|
|
READ_NEVER = 0x0010,
|
|
READ_PIN1 = 0x0011,
|
|
READ_PIN2 = 0x0012,
|
|
READ_PIN3 = 0x0014,
|
|
READ_ALWAYS = 0x0018,
|
|
READ_MASK = 0x00FF,
|
|
WRITE_NEVER = 0x1000,
|
|
WRITE_PIN1 = 0x1100,
|
|
WRITE_PIN2 = 0x1200,
|
|
WRITE_PIN3 = 0x1400,
|
|
WRITE_ALWAYS = 0x1800,
|
|
WRITE_MASK = 0x1F00
|
|
};
|
|
|
|
enum _ext_caps { /* extended capabilities/features */
|
|
EXT_CAP_ALG_ATTR_CHANGEABLE = 0x0004,
|
|
EXT_CAP_PRIVATE_DO = 0x0008,
|
|
EXT_CAP_C4_CHANGEABLE = 0x0010,
|
|
EXT_CAP_KEY_IMPORT = 0x0020,
|
|
EXT_CAP_GET_CHALLENGE = 0x0040,
|
|
EXT_CAP_SM = 0x0080,
|
|
EXT_CAP_CHAINING = 0x1000,
|
|
EXT_CAP_APDU_EXT = 0x2000
|
|
};
|
|
|
|
enum _card_state {
|
|
CARD_STATE_UNKNOWN = 0x00,
|
|
CARD_STATE_INITIALIZATION = 0x03,
|
|
CARD_STATE_ACTIVATED = 0x05
|
|
};
|
|
|
|
enum _key_type {
|
|
KEY_SIGN = 1,
|
|
KEY_ENCR = 2,
|
|
KEY_AUTH = 3
|
|
};
|
|
|
|
struct blob {
|
|
struct blob * next; /* pointer to next sibling */
|
|
struct blob * parent; /* pointer to parent */
|
|
struct do_info *info;
|
|
|
|
sc_file_t * file;
|
|
unsigned int id;
|
|
int status;
|
|
|
|
unsigned char * data;
|
|
unsigned int len;
|
|
struct blob * files; /* pointer to 1st child */
|
|
};
|
|
|
|
struct do_info {
|
|
unsigned int id; /* ID of the DO in question */
|
|
|
|
enum _type type; /* constructed DO or not */
|
|
enum _access access; /* R/W acces levels for the DO */
|
|
|
|
/* function to get the DO from the card:
|
|
* only != NULL is DO if readable and not only a part of a constructed DO */
|
|
int (*get_fn)(sc_card_t *, unsigned int, u8 *, size_t);
|
|
/* function to write the DO to the card:
|
|
* only != NULL if DO is writeable under some conditions */
|
|
int (*put_fn)(sc_card_t *, unsigned int, const u8 *, size_t);
|
|
};
|
|
|
|
static int pgp_get_card_features(sc_card_t *card);
|
|
static int pgp_finish(sc_card_t *card);
|
|
static void pgp_iterate_blobs(struct blob *, int, void (*func)());
|
|
|
|
static int pgp_get_blob(sc_card_t *card, struct blob *blob,
|
|
unsigned int id, struct blob **ret);
|
|
static struct blob * pgp_new_blob(sc_card_t *, struct blob *, unsigned int, sc_file_t *);
|
|
static void pgp_free_blob(struct blob *);
|
|
static int pgp_get_pubkey(sc_card_t *, unsigned int,
|
|
u8 *, size_t);
|
|
static int pgp_get_pubkey_pem(sc_card_t *, unsigned int,
|
|
u8 *, size_t);
|
|
|
|
static struct do_info pgp1_objects[] = { /* OpenPGP card spec 1.1 */
|
|
{ 0x004f, SIMPLE, READ_ALWAYS | WRITE_NEVER, NULL, NULL },
|
|
{ 0x005b, SIMPLE, READ_ALWAYS | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x005e, SIMPLE, READ_ALWAYS | WRITE_PIN3, sc_get_data, sc_put_data },
|
|
{ 0x0065, CONSTRUCTED, READ_ALWAYS | WRITE_NEVER, sc_get_data, NULL },
|
|
{ 0x006e, CONSTRUCTED, READ_ALWAYS | WRITE_NEVER, sc_get_data, NULL },
|
|
{ 0x0073, CONSTRUCTED, READ_ALWAYS | WRITE_NEVER, NULL, NULL },
|
|
{ 0x007a, CONSTRUCTED, READ_ALWAYS | WRITE_NEVER, sc_get_data, NULL },
|
|
{ 0x0081, SIMPLE, READ_ALWAYS | WRITE_NEVER, NULL, NULL },
|
|
{ 0x0082, SIMPLE, READ_ALWAYS | WRITE_NEVER, NULL, NULL },
|
|
{ 0x0093, SIMPLE, READ_ALWAYS | WRITE_NEVER, NULL, NULL },
|
|
{ 0x00c0, SIMPLE, READ_ALWAYS | WRITE_NEVER, NULL, NULL },
|
|
{ 0x00c1, SIMPLE, READ_ALWAYS | WRITE_NEVER, NULL, NULL },
|
|
{ 0x00c2, SIMPLE, READ_ALWAYS | WRITE_NEVER, NULL, NULL },
|
|
{ 0x00c3, SIMPLE, READ_ALWAYS | WRITE_NEVER, NULL, NULL },
|
|
{ 0x00c4, SIMPLE, READ_ALWAYS | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x00c5, SIMPLE, READ_ALWAYS | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x00c6, SIMPLE, READ_ALWAYS | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x00c7, SIMPLE, READ_NEVER | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x00c8, SIMPLE, READ_NEVER | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x00c9, SIMPLE, READ_NEVER | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x00ca, SIMPLE, READ_NEVER | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x00cb, SIMPLE, READ_NEVER | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x00cc, SIMPLE, READ_NEVER | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x00cd, SIMPLE, READ_ALWAYS | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x00ce, SIMPLE, READ_NEVER | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x00cf, SIMPLE, READ_NEVER | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x00d0, SIMPLE, READ_NEVER | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x00e0, CONSTRUCTED, READ_NEVER | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x00e1, CONSTRUCTED, READ_NEVER | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x00e2, CONSTRUCTED, READ_NEVER | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x0101, SIMPLE, READ_ALWAYS | WRITE_PIN2, sc_get_data, sc_put_data },
|
|
{ 0x0102, SIMPLE, READ_ALWAYS | WRITE_PIN3, sc_get_data, sc_put_data },
|
|
{ 0x0103, SIMPLE, READ_PIN2 | WRITE_PIN2, sc_get_data, sc_put_data },
|
|
{ 0x0104, SIMPLE, READ_PIN3 | WRITE_PIN3, sc_get_data, sc_put_data },
|
|
{ 0x3f00, CONSTRUCTED, READ_ALWAYS | WRITE_NEVER, NULL, NULL },
|
|
{ 0x5f2d, SIMPLE, READ_ALWAYS | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x5f35, SIMPLE, READ_ALWAYS | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x5f50, SIMPLE, READ_ALWAYS | WRITE_PIN3, sc_get_data, sc_put_data },
|
|
{ 0x7f49, CONSTRUCTED, READ_ALWAYS | WRITE_NEVER, NULL, NULL },
|
|
{ 0xa400, CONSTRUCTED, READ_ALWAYS | WRITE_NEVER, pgp_get_pubkey, NULL },
|
|
{ 0xa401, SIMPLE, READ_ALWAYS | WRITE_NEVER, pgp_get_pubkey_pem, NULL },
|
|
{ 0xb600, CONSTRUCTED, READ_ALWAYS | WRITE_NEVER, pgp_get_pubkey, NULL },
|
|
{ 0xb601, SIMPLE, READ_ALWAYS | WRITE_NEVER, pgp_get_pubkey_pem, NULL },
|
|
{ 0xb800, CONSTRUCTED, READ_ALWAYS | WRITE_NEVER, pgp_get_pubkey, NULL },
|
|
{ 0xb801, SIMPLE, READ_ALWAYS | WRITE_NEVER, pgp_get_pubkey_pem, NULL },
|
|
{ 0, 0, 0, NULL, NULL },
|
|
};
|
|
|
|
static struct do_info pgp2_objects[] = { /* OpenPGP card spec 2.0 */
|
|
{ 0x004d, CONSTRUCTED, READ_NEVER | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x004f, SIMPLE, READ_ALWAYS | WRITE_NEVER, sc_get_data, NULL },
|
|
{ 0x005b, SIMPLE, READ_ALWAYS | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x005e, SIMPLE, READ_ALWAYS | WRITE_PIN3, sc_get_data, sc_put_data },
|
|
{ 0x0065, CONSTRUCTED, READ_ALWAYS | WRITE_NEVER, sc_get_data, NULL },
|
|
{ 0x006e, CONSTRUCTED, READ_ALWAYS | WRITE_NEVER, sc_get_data, NULL },
|
|
{ 0x0073, CONSTRUCTED, READ_ALWAYS | WRITE_NEVER, NULL, NULL },
|
|
{ 0x007a, CONSTRUCTED, READ_ALWAYS | WRITE_NEVER, sc_get_data, NULL },
|
|
{ 0x0081, SIMPLE, READ_ALWAYS | WRITE_NEVER, NULL, NULL },
|
|
{ 0x0082, SIMPLE, READ_ALWAYS | WRITE_NEVER, NULL, NULL },
|
|
{ 0x0093, SIMPLE, READ_ALWAYS | WRITE_NEVER, NULL, NULL },
|
|
{ 0x00c0, SIMPLE, READ_ALWAYS | WRITE_NEVER, NULL, NULL },
|
|
{ 0x00c1, SIMPLE, READ_ALWAYS | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x00c2, SIMPLE, READ_ALWAYS | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x00c3, SIMPLE, READ_ALWAYS | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x00c4, SIMPLE, READ_ALWAYS | WRITE_PIN3, sc_get_data, sc_put_data },
|
|
{ 0x00c5, SIMPLE, READ_ALWAYS | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x00c6, SIMPLE, READ_ALWAYS | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x00c7, SIMPLE, READ_NEVER | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x00c8, SIMPLE, READ_NEVER | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x00c9, SIMPLE, READ_NEVER | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x00ca, SIMPLE, READ_NEVER | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x00cb, SIMPLE, READ_NEVER | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x00cc, SIMPLE, READ_NEVER | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x00cd, SIMPLE, READ_ALWAYS | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x00ce, SIMPLE, READ_NEVER | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x00cf, SIMPLE, READ_NEVER | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x00d0, SIMPLE, READ_NEVER | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x00d1, SIMPLE, READ_NEVER | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x00d2, SIMPLE, READ_NEVER | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x00d3, SIMPLE, READ_NEVER | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x00f4, CONSTRUCTED, READ_NEVER | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x0101, SIMPLE, READ_ALWAYS | WRITE_PIN2, sc_get_data, sc_put_data },
|
|
{ 0x0102, SIMPLE, READ_ALWAYS | WRITE_PIN3, sc_get_data, sc_put_data },
|
|
{ 0x0103, SIMPLE, READ_PIN2 | WRITE_PIN2, sc_get_data, sc_put_data },
|
|
{ 0x0104, SIMPLE, READ_PIN3 | WRITE_PIN3, sc_get_data, sc_put_data },
|
|
{ 0x3f00, CONSTRUCTED, READ_ALWAYS | WRITE_NEVER, NULL, NULL },
|
|
{ 0x5f2d, SIMPLE, READ_ALWAYS | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x5f35, SIMPLE, READ_ALWAYS | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x5f48, CONSTRUCTED, READ_NEVER | WRITE_PIN3, NULL, sc_put_data },
|
|
{ 0x5f50, SIMPLE, READ_ALWAYS | WRITE_PIN3, sc_get_data, sc_put_data },
|
|
{ 0x5f52, SIMPLE, READ_ALWAYS | WRITE_NEVER, sc_get_data, NULL },
|
|
/* The 7F21 is constructed DO in spec, but in practice, its content can be retrieved
|
|
* as simple DO (no need to parse TLV). */
|
|
{ 0x7f21, SIMPLE, READ_ALWAYS | WRITE_PIN3, sc_get_data, sc_put_data },
|
|
{ 0x7f48, CONSTRUCTED, READ_NEVER | WRITE_NEVER, NULL, NULL },
|
|
{ 0x7f49, CONSTRUCTED, READ_ALWAYS | WRITE_NEVER, NULL, NULL },
|
|
{ 0xa400, CONSTRUCTED, READ_ALWAYS | WRITE_NEVER, pgp_get_pubkey, NULL },
|
|
{ 0xa401, SIMPLE, READ_ALWAYS | WRITE_NEVER, pgp_get_pubkey_pem, NULL },
|
|
{ 0xb600, CONSTRUCTED, READ_ALWAYS | WRITE_NEVER, pgp_get_pubkey, NULL },
|
|
{ 0xb601, SIMPLE, READ_ALWAYS | WRITE_NEVER, pgp_get_pubkey_pem, NULL },
|
|
{ 0xb800, CONSTRUCTED, READ_ALWAYS | WRITE_NEVER, pgp_get_pubkey, NULL },
|
|
{ 0xb801, SIMPLE, READ_ALWAYS | WRITE_NEVER, pgp_get_pubkey_pem, NULL },
|
|
{ 0, 0, 0, NULL, NULL },
|
|
};
|
|
|
|
/* The DO holding X.509 certificate is constructed but does not contain child DO.
|
|
* We should notice this when building fake file system later. */
|
|
#define DO_CERT 0x7f21
|
|
|
|
#define DRVDATA(card) ((struct pgp_priv_data *) ((card)->drv_data))
|
|
struct pgp_priv_data {
|
|
struct blob * mf;
|
|
struct blob * current; /* currently selected file */
|
|
|
|
enum _version bcd_version;
|
|
struct do_info *pgp_objects;
|
|
|
|
enum _card_state state; /* card state */
|
|
enum _ext_caps ext_caps; /* extended capabilities */
|
|
|
|
size_t max_challenge_size;
|
|
size_t max_cert_size;
|
|
|
|
sc_security_env_t sec_env;
|
|
};
|
|
|
|
|
|
/* ABI: check if card's ATR matches one of driver's */
|
|
static int
|
|
pgp_match_card(sc_card_t *card)
|
|
{
|
|
int i;
|
|
|
|
i = _sc_match_atr(card, pgp_atrs, &card->type);
|
|
if (i >= 0) {
|
|
card->name = pgp_atrs[i].name;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* ABI: initialize driver */
|
|
static int
|
|
pgp_init(sc_card_t *card)
|
|
{
|
|
struct pgp_priv_data *priv;
|
|
sc_path_t aid;
|
|
sc_file_t *file = NULL;
|
|
struct do_info *info;
|
|
int r;
|
|
struct blob *child = NULL;
|
|
|
|
priv = calloc (1, sizeof *priv);
|
|
if (!priv)
|
|
return SC_ERROR_OUT_OF_MEMORY;
|
|
card->drv_data = priv;
|
|
|
|
card->cla = 0x00;
|
|
|
|
/* set pointer to correct list of card objects */
|
|
priv->pgp_objects = (card->type == SC_CARD_TYPE_OPENPGP_V2)
|
|
? pgp2_objects : pgp1_objects;
|
|
|
|
/* set detailed card version */
|
|
priv->bcd_version = (card->type == SC_CARD_TYPE_OPENPGP_V2)
|
|
? OPENPGP_CARD_2_0 : OPENPGP_CARD_1_1;
|
|
|
|
/* select application "OpenPGP" */
|
|
sc_format_path("D276:0001:2401", &aid);
|
|
aid.type = SC_PATH_TYPE_DF_NAME;
|
|
if ((r = iso_ops->select_file(card, &aid, &file)) < 0) {
|
|
pgp_finish(card);
|
|
return r;
|
|
}
|
|
|
|
/* read information from AID */
|
|
if (file && file->namelen == 16) {
|
|
/* OpenPGP card spec 1.1 & 2.0, section 4.2.1 & 4.1.2.1 */
|
|
priv->bcd_version = bebytes2ushort(file->name + 6);
|
|
/* kludge: get card's serial number from manufacturer ID + serial number */
|
|
memcpy(card->serialnr.value, file->name + 8, 6);
|
|
card->serialnr.len = 6;
|
|
}
|
|
|
|
/* change file path to MF for re-use in MF */
|
|
sc_format_path("3f00", &file->path);
|
|
|
|
/* set up the root of our fake file tree */
|
|
priv->mf = pgp_new_blob(card, NULL, 0x3f00, file);
|
|
if (!priv->mf) {
|
|
pgp_finish(card);
|
|
return SC_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
/* select MF */
|
|
priv->current = priv->mf;
|
|
|
|
/* Populate MF - add matching blobs listed in the pgp_objects table. */
|
|
for (info = priv->pgp_objects; (info != NULL) && (info->id > 0); info++) {
|
|
if (((info->access & READ_MASK) == READ_ALWAYS) &&
|
|
(info->get_fn != NULL)) {
|
|
child = pgp_new_blob(card, priv->mf, info->id, sc_file_new());
|
|
|
|
/* catch out of memory condition */
|
|
if (child == NULL) {
|
|
pgp_finish(card);
|
|
return SC_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* get card_features from ATR & DOs */
|
|
pgp_get_card_features(card);
|
|
|
|
return SC_SUCCESS;
|
|
}
|
|
|
|
|
|
/* internal: get features of the card: capabilitis, ... */
|
|
static int
|
|
pgp_get_card_features(sc_card_t *card)
|
|
{
|
|
struct pgp_priv_data *priv = DRVDATA (card);
|
|
unsigned char *hist_bytes = card->atr.value;
|
|
size_t atr_len = card->atr.len;
|
|
size_t i = 0;
|
|
struct blob *blob, *blob6e, *blob73;
|
|
|
|
/* parse card capabilities from historical bytes */
|
|
while ((i < atr_len) && (hist_bytes[i] != 0x73))
|
|
i++;
|
|
/* IS07816-4 hist bytes 3rd function table */
|
|
if ((hist_bytes[i] == 0x73) && (atr_len > i+3)) {
|
|
/* bit 0x40 in byte 3 of TL 0x73 means "extended Le/Lc" */
|
|
if (hist_bytes[i+3] & 0x40) {
|
|
card->caps |= SC_CARD_CAP_APDU_EXT;
|
|
priv->ext_caps |= EXT_CAP_APDU_EXT;
|
|
}
|
|
/* bit 0x80 in byte 3 of TL 0x73 means "Command chaining" */
|
|
if (hist_bytes[i+3] & 0x80)
|
|
priv->ext_caps |= EXT_CAP_CHAINING;
|
|
}
|
|
if (priv->bcd_version >= OPENPGP_CARD_2_0) {
|
|
/* get card capabilities from "historical bytes" DO */
|
|
if ((pgp_get_blob(card, priv->mf, 0x5f52, &blob) >= 0) &&
|
|
(blob->data != NULL) && (blob->data[0] == 0x00)) {
|
|
while ((i < blob->len) && (blob->data[i] != 0x73))
|
|
i++;
|
|
/* IS07816-4 hist bytes 3rd function table */
|
|
if ((blob->data[i] == 0x73) && (blob->len > i+3)) {
|
|
/* bit 0x40 in byte 3 of TL 0x73 means "extended Le/Lc" */
|
|
if (blob->data[i+3] & 0x40) {
|
|
card->caps |= SC_CARD_CAP_APDU_EXT;
|
|
priv->ext_caps |= EXT_CAP_APDU_EXT;
|
|
}
|
|
/* bit 0x80 in byte 3 of TL 0x73 means "Command chaining" */
|
|
if (hist_bytes[i+3] & 0x80)
|
|
priv->ext_caps |= EXT_CAP_CHAINING;
|
|
}
|
|
|
|
/* get card status from historical bytes status indicator */
|
|
if ((blob->data[0] == 0x00) && (blob->len >= 4))
|
|
priv->state = blob->data[blob->len-3];
|
|
}
|
|
}
|
|
|
|
if ((pgp_get_blob(card, priv->mf, 0x006e, &blob6e) >= 0) &&
|
|
(pgp_get_blob(card, blob6e, 0x0073, &blob73) >= 0)) {
|
|
|
|
/* get "extended capabilities" DO */
|
|
if ((pgp_get_blob(card, blob73, 0x00c0, &blob) >= 0) &&
|
|
(blob->data != NULL) && (blob->len > 0)) {
|
|
/* in v2.0 bit 0x04 in first byte means "algorithm attributes changeable */
|
|
if ((blob->data[0] & 0x04) && (card->type == SC_CARD_TYPE_OPENPGP_V2))
|
|
priv->ext_caps |= EXT_CAP_ALG_ATTR_CHANGEABLE;
|
|
/* bit 0x08 in first byte means "support for private use DOs" */
|
|
if (blob->data[0] & 0x08)
|
|
priv->ext_caps |= EXT_CAP_PRIVATE_DO;
|
|
/* bit 0x10 in first byte means "support for CHV status byte changeable" */
|
|
if (blob->data[0] & 0x10)
|
|
priv->ext_caps |= EXT_CAP_C4_CHANGEABLE;
|
|
/* bit 0x20 in first byte means "support for Key Import" */
|
|
if (blob->data[0] & 0x20)
|
|
priv->ext_caps |= EXT_CAP_KEY_IMPORT;
|
|
/* bit 0x40 in first byte means "support for Get Challenge" */
|
|
if (blob->data[0] & 0x40) {
|
|
card->caps |= SC_CARD_CAP_RNG;
|
|
priv->ext_caps |= EXT_CAP_GET_CHALLENGE;
|
|
}
|
|
/* in v2.0 bit 0x80 in first byte means "support Secure Messaging" */
|
|
if ((blob->data[0] & 0x80) && (card->type == SC_CARD_TYPE_OPENPGP_V2))
|
|
priv->ext_caps |= EXT_CAP_SM;
|
|
|
|
if ((priv->bcd_version >= OPENPGP_CARD_2_0) && (blob->len >= 10)) {
|
|
/* max. challenge size is at bytes 3-4 */
|
|
priv->max_challenge_size = bebytes2ushort(blob->data + 2);
|
|
/* max. cert size it at bytes 5-6 */
|
|
priv->max_cert_size = bebytes2ushort(blob->data + 4);
|
|
/* max. send/receive sizes are at bytes 7-8 resp. 9-10 */
|
|
card->max_send_size = bebytes2ushort(blob->data + 6);
|
|
card->max_recv_size = bebytes2ushort(blob->data + 8);
|
|
}
|
|
}
|
|
|
|
/* get max. PIN length from "CHV status bytes" DO */
|
|
if ((pgp_get_blob(card, blob73, 0x00c4, &blob) >= 0) &&
|
|
(blob->data != NULL) && (blob->len > 1)) {
|
|
/* 2nd byte in "CHV status bytes" DO means "max. PIN length" */
|
|
card->max_pin_len = blob->data[1];
|
|
}
|
|
|
|
/* get supported algorithms & key lengths from "algorithm attributes" DOs */
|
|
for (i = 0x00c1; i <= 0x00c3; i++) {
|
|
unsigned long flags;
|
|
|
|
/* Is this correct? */
|
|
/* OpenPGP card spec 1.1 & 2.0, section 2.1 */
|
|
flags = SC_ALGORITHM_RSA_RAW;
|
|
/* OpenPGP card spec 1.1 & 2.0, section 7.2.9 & 7.2.10 */
|
|
flags |= SC_ALGORITHM_RSA_PAD_PKCS1;
|
|
flags |= SC_ALGORITHM_RSA_HASH_NONE;
|
|
|
|
if ((pgp_get_blob(card, blob73, i, &blob) >= 0) &&
|
|
(blob->data != NULL) && (blob->len >= 4)) {
|
|
if (blob->data[0] == 0x01) { /* Algorithm ID [RFC4880]: RSA */
|
|
unsigned int keylen = bebytes2ushort(blob->data + 1);
|
|
|
|
_sc_card_add_rsa_alg(card, keylen, flags, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return SC_SUCCESS;
|
|
}
|
|
|
|
|
|
/* ABI: terminate driver */
|
|
static int
|
|
pgp_finish(sc_card_t *card)
|
|
{
|
|
if (card != NULL) {
|
|
struct pgp_priv_data *priv = DRVDATA (card);
|
|
|
|
if (priv != NULL) {
|
|
/* delete fake file hierarchy */
|
|
pgp_iterate_blobs(priv->mf, 99, pgp_free_blob);
|
|
|
|
/* delete private data */
|
|
free(priv);
|
|
}
|
|
card->drv_data = NULL;
|
|
}
|
|
return SC_SUCCESS;
|
|
}
|
|
|
|
|
|
/* internal: fill a blob's data */
|
|
static int
|
|
pgp_set_blob(struct blob *blob, const u8 *data, size_t len)
|
|
{
|
|
if (blob->data)
|
|
free(blob->data);
|
|
blob->data = NULL;
|
|
blob->len = 0;
|
|
blob->status = 0;
|
|
|
|
if (len > 0) {
|
|
void *tmp = malloc(len);
|
|
|
|
if (tmp == NULL)
|
|
return SC_ERROR_OUT_OF_MEMORY;
|
|
|
|
blob->data = tmp;
|
|
blob->len = len;
|
|
memcpy(blob->data, data, len);
|
|
}
|
|
|
|
if (blob->file)
|
|
blob->file->size = len;
|
|
|
|
return SC_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Internal: Implement Access Control List for emulated file.
|
|
* The Access Control is derived from the DO access permission.
|
|
**/
|
|
static int
|
|
pgp_attach_acl(sc_card_t *card, sc_file_t *file, struct do_info *info)
|
|
{
|
|
sc_acl_entry_t *acl;
|
|
unsigned int method = SC_AC_NONE;
|
|
unsigned long key_ref = 0;
|
|
|
|
/* Write access */
|
|
switch (info->access & WRITE_MASK) {
|
|
case WRITE_NEVER:
|
|
method = SC_AC_NEVER;
|
|
break;
|
|
case WRITE_PIN1:
|
|
method = SC_AC_CHV;
|
|
key_ref = 0x01;
|
|
break;
|
|
case WRITE_PIN2:
|
|
method = SC_AC_CHV;
|
|
key_ref = 0x01;
|
|
break;
|
|
case WRITE_PIN3:
|
|
method = SC_AC_CHV;
|
|
key_ref = 0x01;
|
|
break;
|
|
}
|
|
|
|
if (method != SC_AC_NONE || key_ref != 0) {
|
|
sc_file_add_acl_entry(file, SC_AC_OP_WRITE, method, key_ref);
|
|
sc_file_add_acl_entry(file, SC_AC_OP_UPDATE, method, key_ref);
|
|
sc_file_add_acl_entry(file, SC_AC_OP_DELETE, method, key_ref);
|
|
sc_file_add_acl_entry(file, SC_AC_OP_CREATE, method, key_ref);
|
|
}
|
|
|
|
method = SC_AC_NONE;
|
|
key_ref = 0;
|
|
/* Read access */
|
|
switch (info->access & READ_MASK) {
|
|
case READ_NEVER:
|
|
method = SC_AC_NEVER;
|
|
break;
|
|
case READ_PIN1:
|
|
method = SC_AC_CHV;
|
|
key_ref = 0x01;
|
|
break;
|
|
case READ_PIN2:
|
|
method = SC_AC_CHV;
|
|
key_ref = 0x01;
|
|
break;
|
|
case READ_PIN3:
|
|
method = SC_AC_CHV;
|
|
key_ref = 0x01;
|
|
break;
|
|
}
|
|
|
|
if (method != SC_AC_NONE || key_ref != 0) {
|
|
sc_file_add_acl_entry(file, SC_AC_OP_READ, method, key_ref);
|
|
}
|
|
}
|
|
|
|
/* internal: append a blob to the list of children of a given parent blob */
|
|
static struct blob *
|
|
pgp_new_blob(sc_card_t *card, struct blob *parent, unsigned int file_id,
|
|
sc_file_t *file)
|
|
{
|
|
struct blob *blob = NULL;
|
|
|
|
if (file == NULL)
|
|
return NULL;
|
|
|
|
if ((blob = calloc(1, sizeof(struct blob))) != NULL) {
|
|
struct pgp_priv_data *priv = DRVDATA (card);
|
|
struct do_info *info;
|
|
|
|
blob->file = file;
|
|
|
|
blob->file->type = SC_FILE_TYPE_WORKING_EF; /* default */
|
|
blob->file->ef_structure = SC_FILE_EF_TRANSPARENT;
|
|
blob->file->id = file_id;
|
|
|
|
blob->id = file_id;
|
|
blob->parent = parent;
|
|
|
|
if (parent != NULL) {
|
|
struct blob **p;
|
|
|
|
/* set file's path = parent's path + file's id */
|
|
blob->file->path = parent->file->path;
|
|
sc_append_file_id(&blob->file->path, file_id);
|
|
|
|
/* append blob to list of parent's children */
|
|
for (p = &parent->files; *p != NULL; p = &(*p)->next)
|
|
;
|
|
*p = blob;
|
|
}
|
|
else {
|
|
u8 id_str[2];
|
|
|
|
/* no parent: set file's path = file's id */
|
|
sc_format_path(ushort2bebytes(id_str, file_id), &blob->file->path);
|
|
}
|
|
|
|
/* find matching DO info: set file type depending on it */
|
|
for (info = priv->pgp_objects; (info != NULL) && (info->id > 0); info++) {
|
|
if (info->id == file_id) {
|
|
blob->info = info;
|
|
blob->file->type = blob->info->type;
|
|
pgp_attach_acl(card, blob->file, info);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return blob;
|
|
}
|
|
|
|
|
|
/* internal: free a blob including its content */
|
|
static void
|
|
pgp_free_blob(struct blob *blob)
|
|
{
|
|
if (blob) {
|
|
if (blob->parent) {
|
|
struct blob **p;
|
|
|
|
/* remove blob from list of parent's children */
|
|
for (p = &blob->parent->files; *p != NULL && *p != blob; p = &(*p)->next)
|
|
;
|
|
if (*p == blob)
|
|
*p = blob->next;
|
|
}
|
|
|
|
if (blob->file)
|
|
sc_file_free(blob->file);
|
|
if (blob->data)
|
|
free(blob->data);
|
|
free(blob);
|
|
}
|
|
}
|
|
|
|
|
|
/* internal: iterate through the blob tree, calling a function for each blob */
|
|
static void
|
|
pgp_iterate_blobs(struct blob *blob, int level, void (*func)())
|
|
{
|
|
if (blob) {
|
|
if (level > 0) {
|
|
struct blob *child = blob->files;
|
|
|
|
while (child != NULL) {
|
|
struct blob *next = child->next;
|
|
|
|
pgp_iterate_blobs(child, level-1, func);
|
|
child = next;
|
|
}
|
|
}
|
|
func(blob);
|
|
}
|
|
}
|
|
|
|
|
|
/* internal: read a blob's contents from card */
|
|
static int
|
|
pgp_read_blob(sc_card_t *card, struct blob *blob)
|
|
{
|
|
if (blob->data != NULL)
|
|
return SC_SUCCESS;
|
|
if (blob->info == NULL)
|
|
return blob->status;
|
|
|
|
if (blob->info->get_fn) { /* readable, top-level DO */
|
|
u8 buffer[2048];
|
|
size_t buf_len = (card->caps & SC_CARD_CAP_APDU_EXT)
|
|
? sizeof(buffer) : 256;
|
|
int r = blob->info->get_fn(card, blob->id, buffer, buf_len);
|
|
|
|
if (r < 0) { /* an error occurred */
|
|
blob->status = r;
|
|
return r;
|
|
}
|
|
|
|
return pgp_set_blob(blob, buffer, r);
|
|
}
|
|
else { /* un-readable DO or part of a constructed DO */
|
|
return SC_SUCCESS;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* internal: Enumerate contents of a data blob.
|
|
* The OpenPGP card has a TLV encoding according ASN.1 BER-encoding rules.
|
|
*/
|
|
static int
|
|
pgp_enumerate_blob(sc_card_t *card, struct blob *blob)
|
|
{
|
|
const u8 *in;
|
|
int r;
|
|
|
|
if (blob->files != NULL)
|
|
return SC_SUCCESS;
|
|
|
|
if ((r = pgp_read_blob(card, blob)) < 0)
|
|
return r;
|
|
|
|
in = blob->data;
|
|
|
|
while ((int) blob->len > (in - blob->data)) {
|
|
unsigned int cla, tag, tmptag;
|
|
size_t len;
|
|
const u8 *data = in;
|
|
struct blob *new;
|
|
|
|
r = sc_asn1_read_tag(&data, blob->len - (in - blob->data),
|
|
&cla, &tag, &len);
|
|
if (r < 0) {
|
|
sc_debug(card->ctx, SC_LOG_DEBUG_NORMAL,
|
|
"Unexpected end of contents\n");
|
|
return SC_ERROR_OBJECT_NOT_VALID;
|
|
}
|
|
|
|
/* undo ASN1's split of tag & class */
|
|
for (tmptag = tag; tmptag > 0x0FF; tmptag >>= 8) {
|
|
cla <<= 8;
|
|
}
|
|
tag |= cla;
|
|
|
|
/* create fake file system hierarchy by
|
|
* using constructed DOs as DF */
|
|
if ((new = pgp_new_blob(card, blob, tag, sc_file_new())) == NULL)
|
|
return SC_ERROR_OUT_OF_MEMORY;
|
|
pgp_set_blob(new, data, len);
|
|
in = data + len;
|
|
}
|
|
|
|
return SC_SUCCESS;
|
|
}
|
|
|
|
|
|
/* internal: find a blob by ID below a given parent, filling its contents when necessary */
|
|
static int
|
|
pgp_get_blob(sc_card_t *card, struct blob *blob, unsigned int id,
|
|
struct blob **ret)
|
|
{
|
|
struct blob *child;
|
|
int r;
|
|
|
|
if ((r = pgp_enumerate_blob(card, blob)) < 0)
|
|
return r;
|
|
|
|
for (child = blob->files; child; child = child->next) {
|
|
if (child->id == id) {
|
|
(void) pgp_read_blob(card, child);
|
|
*ret = child;
|
|
return SC_SUCCESS;
|
|
}
|
|
}
|
|
|
|
return SC_ERROR_FILE_NOT_FOUND;
|
|
}
|
|
|
|
/* Internal: search recursively for a blob by ID below a given root */
|
|
static int
|
|
pgp_seek_blob(sc_card_t *card, struct blob *root, unsigned int id,
|
|
struct blob **ret)
|
|
{
|
|
struct blob *child;
|
|
int r;
|
|
|
|
if ((r = pgp_get_blob(card, root, id, ret)) == 0)
|
|
/* The sought blob is right under root */
|
|
return r;
|
|
|
|
/* Not found, seek deeper */
|
|
for (child = root->files; child; child = child->next) {
|
|
/* The DO of SIMPLE type or the DO holding certificate
|
|
* does not contain children */
|
|
if (child->info->type == SIMPLE || child->id == DO_CERT)
|
|
continue;
|
|
r = pgp_seek_blob(card, child, id, ret);
|
|
if (r == 0)
|
|
return r;
|
|
}
|
|
|
|
return SC_ERROR_FILE_NOT_FOUND;
|
|
}
|
|
|
|
/* internal: find a blob by tag - pgp_seek_blob with optimizations */
|
|
static struct blob *
|
|
pgp_find_blob(sc_card_t *card, unsigned int tag)
|
|
{
|
|
struct pgp_priv_data *priv = DRVDATA(card);
|
|
struct blob *blob = NULL;
|
|
int r;
|
|
|
|
/* Check if current selected blob is which we want to test*/
|
|
if (priv->current->id == tag) {
|
|
return priv->current;
|
|
}
|
|
/* Look for the blob representing the DO */
|
|
r = pgp_seek_blob(card, priv->mf, tag, &blob);
|
|
if (r < 0) {
|
|
sc_log(card->ctx, "Failed to seek the blob representing the tag %04X. Error %d.", tag, r);
|
|
return NULL;
|
|
}
|
|
return blob;
|
|
}
|
|
|
|
/**
|
|
* Strip out the parts of PKCS15 file layout in the path. Get the reduced version
|
|
* which is understood by the OpenPGP card driver.
|
|
* Return the index whose preceding part will be ignored.
|
|
**/
|
|
static unsigned int pgp_strip_path(sc_card_t *card, const sc_path_t *path)
|
|
{
|
|
unsigned int start_point = 0;
|
|
/* start_point will move through the path string */
|
|
if (path->value == NULL || path->len == 0)
|
|
return 0;
|
|
|
|
/* Ignore 3F00 (MF) at the beginning */
|
|
start_point = (memcmp(path->value, "\x3f\x00", 2) == 0) ? 2 : 0;
|
|
/* Strip path of PKCS15-AppDF (5015) */
|
|
start_point += (memcmp(path->value + start_point, "\x50\x15", 2) == 0) ? 2 : 0;
|
|
return start_point;
|
|
}
|
|
|
|
/* ABI: SELECT FILE */
|
|
static int
|
|
pgp_select_file(sc_card_t *card, const sc_path_t *path, sc_file_t **ret)
|
|
{
|
|
struct pgp_priv_data *priv = DRVDATA(card);
|
|
struct blob *blob;
|
|
unsigned int path_start = 0;
|
|
unsigned int n;
|
|
|
|
LOG_FUNC_CALLED(card->ctx);
|
|
|
|
if (path->type == SC_PATH_TYPE_DF_NAME)
|
|
LOG_FUNC_RETURN(card->ctx, iso_ops->select_file(card, path, ret));
|
|
|
|
if (path->len < 2 || (path->len & 1))
|
|
LOG_TEST_RET(card->ctx, SC_ERROR_INVALID_ARGUMENTS,
|
|
"invalid path length");
|
|
|
|
if (path->type == SC_PATH_TYPE_FILE_ID && path->len != 2)
|
|
LOG_TEST_RET(card->ctx, SC_ERROR_INVALID_ARGUMENTS,
|
|
"invalid path type");
|
|
|
|
/* ignore explicitely mentioned MF at the path's beginning */
|
|
path_start = pgp_strip_path(card, path);
|
|
|
|
/* starting with the MF ... */
|
|
blob = priv->mf;
|
|
/* ... recurse through the tree following the path */
|
|
for (n = path_start; n < path->len; n += 2) {
|
|
unsigned int id = bebytes2ushort(path->value + n);
|
|
int r = pgp_get_blob(card, blob, id, &blob);
|
|
|
|
if (r < 0) { /* failure */
|
|
priv->current = NULL;
|
|
LOG_FUNC_RETURN(card->ctx, r);
|
|
}
|
|
}
|
|
|
|
/* success: select file = set "current" pointer to blob found */
|
|
priv->current = blob;
|
|
|
|
if (ret)
|
|
sc_file_dup(ret, blob->file);
|
|
|
|
LOG_FUNC_RETURN(card->ctx, SC_SUCCESS);
|
|
}
|
|
|
|
|
|
/* ABI: LIST FILES */
|
|
static int
|
|
pgp_list_files(sc_card_t *card, u8 *buf, size_t buflen)
|
|
{
|
|
struct pgp_priv_data *priv = DRVDATA(card);
|
|
struct blob *blob;
|
|
unsigned int k;
|
|
int r;
|
|
|
|
LOG_FUNC_CALLED(card->ctx);
|
|
|
|
/* jump to selected file */
|
|
blob = priv->current;
|
|
|
|
if (blob->file->type != SC_FILE_TYPE_DF)
|
|
LOG_TEST_RET(card->ctx, SC_ERROR_OBJECT_NOT_VALID,
|
|
"invalid file type");
|
|
|
|
if ((r = pgp_enumerate_blob(card, blob)) < 0)
|
|
LOG_FUNC_RETURN(card->ctx, r);
|
|
|
|
for (k = 0, blob = blob->files; blob != NULL; blob = blob->next) {
|
|
if (blob->info != NULL && (blob->info->access & READ_MASK) != READ_NEVER) {
|
|
if (k + 2 > buflen)
|
|
LOG_FUNC_RETURN(card->ctx, SC_ERROR_BUFFER_TOO_SMALL);
|
|
|
|
ushort2bebytes(buf + k, blob->id);
|
|
k += 2;
|
|
}
|
|
}
|
|
|
|
LOG_FUNC_RETURN(card->ctx, k);
|
|
}
|
|
|
|
|
|
/* ABI: READ BINARY */
|
|
static int
|
|
pgp_read_binary(sc_card_t *card, unsigned int idx,
|
|
u8 *buf, size_t count, unsigned long flags)
|
|
{
|
|
struct pgp_priv_data *priv = DRVDATA(card);
|
|
struct blob *blob;
|
|
int r;
|
|
|
|
LOG_FUNC_CALLED(card->ctx);
|
|
|
|
/* jump to selected file */
|
|
blob = priv->current;
|
|
|
|
if (blob == NULL)
|
|
LOG_FUNC_RETURN(card->ctx, SC_ERROR_FILE_NOT_FOUND);
|
|
|
|
if (blob->file->type != SC_FILE_TYPE_WORKING_EF)
|
|
LOG_FUNC_RETURN(card->ctx, SC_ERROR_FILE_NOT_FOUND);
|
|
|
|
if ((r = pgp_read_blob(card, blob)) < 0)
|
|
LOG_FUNC_RETURN(card->ctx, r);
|
|
|
|
if (idx > blob->len)
|
|
LOG_FUNC_RETURN(card->ctx, SC_ERROR_INCORRECT_PARAMETERS);
|
|
|
|
if (idx + count > blob->len)
|
|
count = blob->len - idx;
|
|
memcpy(buf, blob->data + idx, count);
|
|
|
|
LOG_FUNC_RETURN(card->ctx, count);
|
|
}
|
|
|
|
|
|
/* ABI: WRITE BINARY */
|
|
static int
|
|
pgp_write_binary(sc_card_t *card, unsigned int idx,
|
|
const u8 *buf, size_t count, unsigned long flags)
|
|
{
|
|
LOG_FUNC_RETURN(card->ctx, SC_ERROR_NOT_SUPPORTED);
|
|
}
|
|
|
|
|
|
/* internal: get public key from card: as DF + sub-wEFs */
|
|
static int
|
|
pgp_get_pubkey(sc_card_t *card, unsigned int tag, u8 *buf, size_t buf_len)
|
|
{
|
|
sc_apdu_t apdu;
|
|
u8 idbuf[2];
|
|
int r;
|
|
|
|
sc_log(card->ctx, "called, tag=%04x\n", tag);
|
|
|
|
sc_format_apdu(card, &apdu, SC_APDU_CASE_4, 0x47, 0x81, 0);
|
|
apdu.lc = 2;
|
|
apdu.data = ushort2bebytes(idbuf, tag);
|
|
apdu.datalen = 2;
|
|
apdu.le = ((buf_len >= 256) && !(card->caps & SC_CARD_CAP_APDU_EXT)) ? 256 : buf_len;
|
|
apdu.resp = buf;
|
|
apdu.resplen = buf_len;
|
|
|
|
r = sc_transmit_apdu(card, &apdu);
|
|
LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
|
|
|
|
r = sc_check_sw(card, apdu.sw1, apdu.sw2);
|
|
LOG_TEST_RET(card->ctx, r, "Card returned error");
|
|
|
|
LOG_FUNC_RETURN(card->ctx, apdu.resplen);
|
|
}
|
|
|
|
|
|
/* internal: get public key from card: as one wEF */
|
|
static int
|
|
pgp_get_pubkey_pem(sc_card_t *card, unsigned int tag, u8 *buf, size_t buf_len)
|
|
{
|
|
struct pgp_priv_data *priv = DRVDATA(card);
|
|
struct blob *blob, *mod_blob, *exp_blob;
|
|
sc_pkcs15_pubkey_t pubkey;
|
|
u8 *data;
|
|
size_t len;
|
|
int r;
|
|
|
|
sc_log(card->ctx, "called, tag=%04x\n", tag);
|
|
|
|
if ((r = pgp_get_blob(card, priv->mf, tag & 0xFFFE, &blob)) < 0
|
|
|| (r = pgp_get_blob(card, blob, 0x7F49, &blob)) < 0
|
|
|| (r = pgp_get_blob(card, blob, 0x0081, &mod_blob)) < 0
|
|
|| (r = pgp_get_blob(card, blob, 0x0082, &exp_blob)) < 0
|
|
|| (r = pgp_read_blob(card, mod_blob)) < 0
|
|
|| (r = pgp_read_blob(card, exp_blob)) < 0)
|
|
LOG_TEST_RET(card->ctx, r, "error getting elements");
|
|
|
|
memset(&pubkey, 0, sizeof(pubkey));
|
|
pubkey.algorithm = SC_ALGORITHM_RSA;
|
|
pubkey.u.rsa.modulus.data = mod_blob->data;
|
|
pubkey.u.rsa.modulus.len = mod_blob->len;
|
|
pubkey.u.rsa.exponent.data = exp_blob->data;
|
|
pubkey.u.rsa.exponent.len = exp_blob->len;
|
|
|
|
r = sc_pkcs15_encode_pubkey(card->ctx, &pubkey, &data, &len);
|
|
LOG_TEST_RET(card->ctx, r, "public key encoding failed");
|
|
|
|
if (len > buf_len)
|
|
len = buf_len;
|
|
memcpy(buf, data, len);
|
|
free(data);
|
|
|
|
LOG_FUNC_RETURN(card->ctx, len);
|
|
}
|
|
|
|
|
|
/* ABI: GET DATA */
|
|
static int
|
|
pgp_get_data(sc_card_t *card, unsigned int tag, u8 *buf, size_t buf_len)
|
|
{
|
|
sc_apdu_t apdu;
|
|
int r;
|
|
|
|
LOG_FUNC_CALLED(card->ctx);
|
|
|
|
sc_format_apdu(card, &apdu, SC_APDU_CASE_2, 0xCA, tag >> 8, tag);
|
|
apdu.le = ((buf_len >= 256) && !(card->caps & SC_CARD_CAP_APDU_EXT)) ? 256 : buf_len;
|
|
apdu.resp = buf;
|
|
apdu.resplen = buf_len;
|
|
|
|
r = sc_transmit_apdu(card, &apdu);
|
|
LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
|
|
|
|
r = sc_check_sw(card, apdu.sw1, apdu.sw2);
|
|
LOG_TEST_RET(card->ctx, r, "Card returned error");
|
|
|
|
LOG_FUNC_RETURN(card->ctx, apdu.resplen);
|
|
}
|
|
|
|
/* ABI: PUT DATA */
|
|
static int
|
|
pgp_put_data(sc_card_t *card, unsigned int tag, const u8 *buf, size_t buf_len)
|
|
{
|
|
sc_apdu_t apdu;
|
|
struct pgp_priv_data *priv = DRVDATA(card);
|
|
struct blob *affected_blob = NULL;
|
|
u8 ins = 0xDA;
|
|
u8 p1 = tag >> 8;
|
|
u8 p2 = tag;
|
|
int r;
|
|
|
|
LOG_FUNC_CALLED(card->ctx);
|
|
|
|
/* Check if the tag is writable */
|
|
affected_blob = pgp_find_blob(card, tag);
|
|
if (affected_blob == NULL || (affected_blob->info->access & WRITE_MASK) == WRITE_NEVER) {
|
|
sc_log(card->ctx, "The %04X DO is not writable.", tag);
|
|
return SC_ERROR_NOT_ALLOWED;
|
|
}
|
|
|
|
/* Check data size.
|
|
* We won't check other DOs than 7F21 (certificate), because their capacity
|
|
* is hard-codded and may change in various version of the card. If we check here,
|
|
* the driver may be sticked to a limit version number of card.
|
|
* 7F21 size is soft-coded, so we can check it. */
|
|
if (tag == DO_CERT && buf_len > priv->max_cert_size) {
|
|
sc_log(card->ctx, "Data exceeds DO limit. It should be smaller than %d bytes.", priv->max_cert_size);
|
|
return SC_ERROR_WRONG_LENGTH;
|
|
}
|
|
|
|
/* Extended Header list (004D DO) needs a variant of PUT DATA command */
|
|
if (tag == 0x004D) {
|
|
ins = 0xDB;
|
|
p1 = 0x3F;
|
|
p2 = 0xFF;
|
|
}
|
|
|
|
/* Build APDU */
|
|
/* Large data can be sent via extended APDU, if card supports */
|
|
if (buf_len > 256 && card->caps & SC_CARD_CAP_APDU_EXT) {
|
|
sc_format_apdu(card, &apdu, SC_APDU_CASE_3_EXT, ins, p1, p2);
|
|
}
|
|
/* Card/Reader does not support extended, use command chaining, if supported */
|
|
else if (buf_len > 256 && priv->ext_caps & EXT_CAP_CHAINING) {
|
|
sc_format_apdu(card, &apdu, SC_APDU_CASE_3, ins, p1, p2);
|
|
apdu.flags |= SC_APDU_FLAGS_CHAINING;
|
|
/* FIXME: The case of command chaining is not tested. */
|
|
}
|
|
else if (buf_len <= 256) {
|
|
sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, ins, p1, p2);
|
|
}
|
|
else {
|
|
sc_log(card->ctx, "Data is too big to send.");
|
|
return SC_ERROR_INVALID_DATA;
|
|
}
|
|
|
|
apdu.data = buf;
|
|
apdu.datalen = buf_len;
|
|
apdu.lc = buf_len;
|
|
|
|
if (buf == NULL && buf_len == 0) {
|
|
/* Erase DO content.
|
|
*
|
|
* We won't call sc_transmit_apdu() in order to bypass
|
|
* the check of APDU, because sc_transmit_apdu() does not allow
|
|
* null data. */
|
|
r = sc_lock(card); /* acquire card lock*/
|
|
sc_log(card->ctx, "card->reader->ops->transmit");
|
|
r = card->reader->ops->transmit(card->reader, &apdu);
|
|
/* all done => release lock */
|
|
if (sc_unlock(card) != SC_SUCCESS)
|
|
sc_debug(card->ctx, SC_LOG_DEBUG_NORMAL, "sc_unlock failed");
|
|
|
|
return r;
|
|
}
|
|
/* Send APDU to card */
|
|
r = sc_transmit_apdu(card, &apdu);
|
|
LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
|
|
/* Check response */
|
|
r = sc_check_sw(card, apdu.sw1, apdu.sw2);
|
|
|
|
/* Instruct more in case of error */
|
|
if (r == SC_ERROR_SECURITY_STATUS_NOT_SATISFIED) {
|
|
sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Please verify PIN first.");
|
|
}
|
|
LOG_TEST_RET(card->ctx, r, "Card returned error");
|
|
|
|
/* Update the corresponding file */
|
|
sc_log(card->ctx, "To update the corresponding blob data");
|
|
r = pgp_set_blob(affected_blob, buf, buf_len);
|
|
if (r < 0)
|
|
sc_log(card->ctx, "Failed to update the blob %04X. Error %d.", affected_blob->id, r);
|
|
/* The pgp_update_tag_blob()'s failure won't affect */
|
|
|
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, buf_len);
|
|
}
|
|
|
|
|
|
/* ABI: PIN cmd: verify/change/unblock a PIN */
|
|
static int
|
|
pgp_pin_cmd(sc_card_t *card, struct sc_pin_cmd_data *data, int *tries_left)
|
|
{
|
|
LOG_FUNC_CALLED(card->ctx);
|
|
|
|
if (data->pin_type != SC_AC_CHV)
|
|
LOG_TEST_RET(card->ctx, SC_ERROR_INVALID_ARGUMENTS,
|
|
"invalid PIN type");
|
|
|
|
/* In general, the PIN Reference is extracted from the key-id, for
|
|
* example, CHV0 -> Ref=0, CHV1 -> Ref=1.
|
|
* However, in the case of OpenGPG, the PIN Ref to compose APDU
|
|
* must be 81, 82, 83.
|
|
* So, if we receive Ref=1, Ref=2, we must convert to 81, 82...
|
|
* In OpenPGP ver 1, the PINs are named CHV1, CHV2, CHV3. In ver 2, they
|
|
* are named PW1, PW3 (PW1 operates in 2 modes). However, the PIN references (P2 in APDU)
|
|
* are the same between 2 version:
|
|
* 81 (CHV1 or PW1), 82 (CHV2 or PW1-mode 2), 83 (CHV3 or PW3).
|
|
*
|
|
* Note that if this function is called from sc_pkcs15_verify_pin() in pkcs15-pin.c,
|
|
* the Ref is already 81, 82, 83.
|
|
*/
|
|
|
|
/* Convert the PIN Reference if needed */
|
|
data->pin_reference |= 0x80;
|
|
/* Ensure pin_reference is 81, 82, 83 */
|
|
if (!(data->pin_reference == 0x81 || data->pin_reference == 0x82 || data->pin_reference == 0x83)) {
|
|
LOG_TEST_RET(card->ctx, SC_ERROR_INVALID_ARGUMENTS,
|
|
"key-id should be 1, 2, 3.");
|
|
}
|
|
LOG_FUNC_RETURN(card->ctx, iso_ops->pin_cmd(card, data, tries_left));
|
|
}
|
|
|
|
|
|
/* ABI: set security environment */
|
|
static int
|
|
pgp_set_security_env(sc_card_t *card,
|
|
const sc_security_env_t *env, int se_num)
|
|
{
|
|
struct pgp_priv_data *priv = DRVDATA(card);
|
|
|
|
LOG_FUNC_CALLED(card->ctx);
|
|
|
|
if ((env->flags & SC_SEC_ENV_ALG_PRESENT) && (env->algorithm != SC_ALGORITHM_RSA))
|
|
LOG_TEST_RET(card->ctx, SC_ERROR_INVALID_ARGUMENTS,
|
|
"only RSA algorithm supported");
|
|
|
|
if (!(env->flags & SC_SEC_ENV_KEY_REF_PRESENT) || (env->key_ref_len != 1))
|
|
LOG_TEST_RET(card->ctx, SC_ERROR_INVALID_ARGUMENTS,
|
|
"exactly one key reference required");
|
|
|
|
if (env->flags & SC_SEC_ENV_FILE_REF_PRESENT)
|
|
LOG_TEST_RET(card->ctx, SC_ERROR_INVALID_ARGUMENTS,
|
|
"passing file references not supported");
|
|
|
|
switch (env->operation) {
|
|
case SC_SEC_OPERATION_SIGN:
|
|
if (env->key_ref[0] != 0x00 && env->key_ref[0] != 0x02) {
|
|
LOG_TEST_RET(card->ctx, SC_ERROR_NOT_SUPPORTED,
|
|
"Key reference not compatible with "
|
|
"requested usage");
|
|
}
|
|
break;
|
|
case SC_SEC_OPERATION_DECIPHER:
|
|
if (env->key_ref[0] != 0x01) {
|
|
LOG_TEST_RET(card->ctx, SC_ERROR_NOT_SUPPORTED,
|
|
"Key reference not compatible with "
|
|
"requested usage");
|
|
}
|
|
break;
|
|
default:
|
|
LOG_TEST_RET(card->ctx, SC_ERROR_INVALID_ARGUMENTS,
|
|
"invalid operation");
|
|
}
|
|
|
|
priv->sec_env = *env;
|
|
|
|
LOG_FUNC_RETURN(card->ctx, SC_SUCCESS);
|
|
}
|
|
|
|
|
|
/* ABI: COMPUTE DIGITAL SIGNATURE */
|
|
static int
|
|
pgp_compute_signature(sc_card_t *card, const u8 *data,
|
|
size_t data_len, u8 * out, size_t outlen)
|
|
{
|
|
struct pgp_priv_data *priv = DRVDATA(card);
|
|
sc_security_env_t *env = &priv->sec_env;
|
|
sc_apdu_t apdu;
|
|
int r;
|
|
|
|
LOG_FUNC_CALLED(card->ctx);
|
|
|
|
if (env->operation != SC_SEC_OPERATION_SIGN)
|
|
LOG_TEST_RET(card->ctx, SC_ERROR_INVALID_ARGUMENTS,
|
|
"invalid operation");
|
|
|
|
switch (env->key_ref[0]) {
|
|
case 0x00: /* signature key */
|
|
/* PSO SIGNATURE */
|
|
sc_format_apdu(card, &apdu, SC_APDU_CASE_4, 0x2A, 0x9E, 0x9A);
|
|
break;
|
|
case 0x02: /* authentication key */
|
|
/* INTERNAL AUTHENTICATE */
|
|
sc_format_apdu(card, &apdu, SC_APDU_CASE_4, 0x88, 0, 0);
|
|
break;
|
|
case 0x01:
|
|
default:
|
|
LOG_TEST_RET(card->ctx, SC_ERROR_INVALID_ARGUMENTS,
|
|
"invalid key reference");
|
|
}
|
|
|
|
apdu.lc = data_len;
|
|
apdu.data = data;
|
|
apdu.datalen = data_len;
|
|
apdu.le = ((outlen >= 256) && !(card->caps & SC_CARD_CAP_APDU_EXT)) ? 256 : outlen;
|
|
apdu.resp = out;
|
|
apdu.resplen = outlen;
|
|
|
|
r = sc_transmit_apdu(card, &apdu);
|
|
LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
|
|
|
|
r = sc_check_sw(card, apdu.sw1, apdu.sw2);
|
|
LOG_TEST_RET(card->ctx, r, "Card returned error");
|
|
|
|
LOG_FUNC_RETURN(card->ctx, apdu.resplen);
|
|
}
|
|
|
|
|
|
/* ABI: DECIPHER */
|
|
static int
|
|
pgp_decipher(sc_card_t *card, const u8 *in, size_t inlen,
|
|
u8 *out, size_t outlen)
|
|
{
|
|
struct pgp_priv_data *priv = DRVDATA(card);
|
|
sc_security_env_t *env = &priv->sec_env;
|
|
sc_apdu_t apdu;
|
|
u8 *temp = NULL;
|
|
int r;
|
|
|
|
LOG_FUNC_CALLED(card->ctx);
|
|
|
|
/* There's some funny padding indicator that must be
|
|
* prepended... hmm. */
|
|
if (!(temp = malloc(inlen + 1)))
|
|
LOG_FUNC_RETURN(card->ctx, SC_ERROR_OUT_OF_MEMORY);
|
|
temp[0] = '\0';
|
|
memcpy(temp + 1, in, inlen);
|
|
in = temp;
|
|
inlen += 1;
|
|
|
|
if (env->operation != SC_SEC_OPERATION_DECIPHER) {
|
|
free(temp);
|
|
LOG_TEST_RET(card->ctx, SC_ERROR_INVALID_ARGUMENTS,
|
|
"invalid operation");
|
|
}
|
|
|
|
switch (env->key_ref[0]) {
|
|
case 0x01: /* Decryption key */
|
|
/* PSO DECIPHER */
|
|
sc_format_apdu(card, &apdu, SC_APDU_CASE_4, 0x2A, 0x80, 0x86);
|
|
break;
|
|
case 0x00: /* signature key */
|
|
case 0x02: /* authentication key */
|
|
default:
|
|
free(temp);
|
|
LOG_TEST_RET(card->ctx, SC_ERROR_INVALID_ARGUMENTS,
|
|
"invalid key reference");
|
|
}
|
|
|
|
apdu.lc = inlen;
|
|
apdu.data = in;
|
|
apdu.datalen = inlen;
|
|
apdu.le = ((outlen >= 256) && !(card->caps & SC_CARD_CAP_APDU_EXT)) ? 256 : outlen;
|
|
apdu.resp = out;
|
|
apdu.resplen = outlen;
|
|
|
|
r = sc_transmit_apdu(card, &apdu);
|
|
free(temp);
|
|
LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
|
|
|
|
r = sc_check_sw(card, apdu.sw1, apdu.sw2);
|
|
LOG_TEST_RET(card->ctx, r, "Card returned error");
|
|
|
|
LOG_FUNC_RETURN(card->ctx, apdu.resplen);
|
|
}
|
|
|
|
/**
|
|
* Internal: Parse response data of key generation and update blob.
|
|
**/
|
|
static int
|
|
pgp_parse_and_update_pubkey_info(sc_card_t *card, u8* data, size_t data_len,
|
|
sc_cardctl_openpgp_keygen_info_t *key_info)
|
|
{
|
|
struct pgp_priv_data *priv = DRVDATA(card);
|
|
struct blob *kinfo_blob;
|
|
struct blob *modulus_blob;
|
|
struct blob *exponent_blob;
|
|
u8 *in = data;
|
|
int r;
|
|
|
|
LOG_FUNC_CALLED(card->ctx);
|
|
while (data_len > (in - data)) {
|
|
unsigned int cla, tag, tmptag;
|
|
size_t len;
|
|
u8 *part = in;
|
|
|
|
/* Parse TLV structure */
|
|
r = sc_asn1_read_tag((const u8**)&part,
|
|
data_len - (in - data),
|
|
&cla, &tag, &len);
|
|
LOG_TEST_RET(card->ctx, r, "Unexpected end of contents.");
|
|
/* Undo ASN1's split of tag & class */
|
|
for (tmptag = tag; tmptag > 0x0FF; tmptag >>= 8) {
|
|
cla <<= 8;
|
|
}
|
|
tag |= cla;
|
|
|
|
if (tag == 0x7f49) {
|
|
r = pgp_get_blob(card, priv->mf, tag, &kinfo_blob);
|
|
LOG_TEST_RET(card->ctx, r, "Can not get the blob storing pubkey info.");
|
|
}
|
|
else if (tag == 0x0081) {
|
|
/* Set the output data */
|
|
if (key_info->modulus) {
|
|
memcpy(key_info->modulus, part, len);
|
|
key_info->modulus_len = len;
|
|
}
|
|
/* Update the corresponding blob content */
|
|
r = pgp_get_blob(card, kinfo_blob, tag, &modulus_blob);
|
|
LOG_TEST_RET(card->ctx, r, "Can not get the blob storing modulus info.");
|
|
pgp_set_blob(modulus_blob, part, len);
|
|
}
|
|
else if (tag == 0x0082) {
|
|
/* Set the output data */
|
|
if (key_info->exponent) {
|
|
memcpy(key_info->exponent, part, len);
|
|
key_info->exponent_len = len;
|
|
}
|
|
/* Update the corresponding blob content */
|
|
r = pgp_get_blob(card, kinfo_blob, tag, &exponent_blob);
|
|
LOG_TEST_RET(card->ctx, r, "Can not get the blob storing exponent info.");
|
|
pgp_set_blob(exponent_blob, part, len);
|
|
}
|
|
|
|
/* Go to next part to parse */
|
|
in = part + len;
|
|
}
|
|
LOG_FUNC_RETURN(card->ctx, r);
|
|
}
|
|
|
|
/**
|
|
* Generate key
|
|
**/
|
|
static int pgp_gen_key(sc_card_t *card, sc_cardctl_openpgp_keygen_info_t *key_info)
|
|
{
|
|
struct pgp_priv_data *priv = DRVDATA(card);
|
|
struct blob *algo_blob;
|
|
sc_apdu_t apdu;
|
|
unsigned int modulus_bitlen;
|
|
u8 apdu_case;
|
|
int r = SC_SUCCESS;
|
|
|
|
if (key_info->keytype == SC_OPENPGP_KEY_SIGN)
|
|
apdu.data = "\xb6";
|
|
else if (key_info->keytype == SC_OPENPGP_KEY_ENCR)
|
|
apdu.data = "\xb8";
|
|
else if (key_info->keytype == SC_OPENPGP_KEY_AUTH)
|
|
apdu.data = "\xa4";
|
|
else {
|
|
sc_log(card->ctx, "Unknown key type %X.", key_info->keytype);
|
|
LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_ARGUMENTS);
|
|
}
|
|
apdu.datalen = 2; /* Data = B600 */
|
|
apdu.lc = 2;
|
|
|
|
/* Get supported modulus length, to specify Le for APDU */
|
|
r = pgp_get_blob(card, priv->mf, (0x00C0 | key_info->keytype), &algo_blob);
|
|
LOG_TEST_RET(card->ctx, r, "Don't know supported modulus length");
|
|
modulus_bitlen = bebytes2ushort(algo_blob->data + 1); /* The modulus length is coded in byte 2 & 3 */
|
|
|
|
/* Test whether we will need extended length mode. 1900 is an
|
|
* arbitrary length which for sure fits into a short apdu.
|
|
* This idea is borrowed from GnuPG code. */
|
|
if (card->caps & SC_CARD_CAP_APDU_EXT && modulus_bitlen > 1900) {
|
|
apdu.le = card->max_recv_size;
|
|
apdu_case = SC_APDU_CASE_4_EXT;
|
|
}
|
|
else {
|
|
apdu.le = 256;
|
|
apdu_case = SC_APDU_CASE_4_SHORT;
|
|
}
|
|
|
|
/* Buffer to receive response */
|
|
apdu.resp = malloc(apdu.le);
|
|
if (apdu.resp == NULL) {
|
|
LOG_FUNC_RETURN(card->ctx, SC_ERROR_NOT_ENOUGH_MEMORY);
|
|
}
|
|
apdu.resplen = apdu.le;
|
|
|
|
sc_format_apdu(card, &apdu, apdu_case, 0x47, 0x80, 0);
|
|
r = sc_transmit_apdu(card, &apdu);
|
|
if (r < 0) {
|
|
sc_log(card->ctx, "APDU transmit failed. Error %s.", sc_strerror(r));
|
|
goto finish;
|
|
}
|
|
|
|
/* Check response */
|
|
r = sc_check_sw(card, apdu.sw1, apdu.sw2);
|
|
/* Instruct more in case of error */
|
|
if (r == SC_ERROR_SECURITY_STATUS_NOT_SATISFIED) {
|
|
sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Please verify PIN first.");
|
|
goto finish;
|
|
}
|
|
|
|
/* Parse the returned data (pubkey info) and update blob */
|
|
r = pgp_parse_and_update_pubkey_info(card, apdu.resp, apdu.resplen, key_info);
|
|
|
|
finish:
|
|
free(apdu.resp);
|
|
LOG_FUNC_RETURN(card->ctx, r);
|
|
}
|
|
|
|
/* ABI: card ctl: perform special card-specific operations */
|
|
static int pgp_card_ctl(sc_card_t *card, unsigned long cmd, void *ptr)
|
|
{
|
|
int r;
|
|
|
|
LOG_FUNC_CALLED(card->ctx);
|
|
|
|
switch(cmd) {
|
|
case SC_CARDCTL_GET_SERIALNR:
|
|
memmove((sc_serial_number_t *) ptr, &card->serialnr, sizeof(card->serialnr));
|
|
LOG_FUNC_RETURN(card->ctx, SC_SUCCESS);
|
|
break;
|
|
|
|
case SC_CARDCTL_OPENPGP_GENERATE_KEY:
|
|
r = pgp_gen_key(card, (sc_cardctl_openpgp_keygen_info_t *) ptr);
|
|
LOG_FUNC_RETURN(card->ctx, r);
|
|
}
|
|
|
|
LOG_FUNC_RETURN(card->ctx, SC_ERROR_NOT_SUPPORTED);
|
|
}
|
|
|
|
/* ABI: Delete file */
|
|
static int pgp_delete_file(sc_card_t *card, const sc_path_t *path)
|
|
{
|
|
struct pgp_priv_data *priv = DRVDATA(card);
|
|
sc_file_t *file;
|
|
struct blob *affected_blob;
|
|
u8 *data;
|
|
size_t len;
|
|
int r;
|
|
|
|
LOG_FUNC_CALLED(card->ctx);
|
|
/* In sc_pkcs15init_delete_by_path(), the path type was set to SC_PATH_TYPE_FILE_ID */
|
|
|
|
r = pgp_select_file(card, path, &file);
|
|
LOG_TEST_RET(card->ctx, r, "Cannot select file.");
|
|
affected_blob = priv->current;
|
|
len = affected_blob->len;
|
|
|
|
/* Create zero-filled buffer to put to DO.
|
|
* Though the spec says that PUT DATA with Lc=0 can erase the DO,
|
|
* but this format of APDU is not allowed by OpenSC and in fact,
|
|
* my CryptoStick responds "64 00" (execution error).
|
|
* So, to erase DO, we will put all zeros to it. */
|
|
data = malloc(affected_blob->len);
|
|
if (data == NULL)
|
|
LOG_FUNC_RETURN(card->ctx, SC_ERROR_OUT_OF_MEMORY);
|
|
memset(data, 0, len);
|
|
|
|
r = pgp_put_data(card, file->id, data, len);
|
|
if (r < 0)
|
|
sc_log(card->ctx, "Failed to erase %04X DO: %s", file->id, sc_strerror(r));
|
|
|
|
free(data);
|
|
return r;
|
|
}
|
|
|
|
/* ABI: Update binary */
|
|
static int pgp_update_binary(sc_card_t *card, unsigned int idx,
|
|
const u8 *buf, size_t count, unsigned long flags)
|
|
{
|
|
struct pgp_priv_data *priv = DRVDATA(card);
|
|
struct blob *affected_blob = priv->current;
|
|
u8 *alldata;
|
|
size_t allength;
|
|
int r;
|
|
|
|
/* We will use PUT DATA to write to DO.
|
|
* This command does not support index, so we will write the overall data,
|
|
* in which the part before idx is get from old content of DO */
|
|
allength = idx + count;
|
|
alldata = malloc(allength);
|
|
if (alldata == NULL)
|
|
LOG_FUNC_RETURN(card->ctx, SC_ERROR_OUT_OF_MEMORY);
|
|
|
|
memset(alldata, 0, allength);
|
|
/* Copy the part before idx */
|
|
memcpy(alldata, affected_blob->data, MIN(idx, affected_blob->len));
|
|
/* Copy data need to be written */
|
|
memcpy(alldata, buf, count);
|
|
|
|
r = pgp_put_data(card, affected_blob->id, alldata, allength);
|
|
if (r < 0) {
|
|
sc_log(card->ctx, "Failed to update binary. %s", sc_strerror(r));
|
|
}
|
|
free(alldata);
|
|
return r;
|
|
}
|
|
|
|
/* ABI: driver binding stuff */
|
|
static struct sc_card_driver *
|
|
sc_get_driver(void)
|
|
{
|
|
struct sc_card_driver *iso_drv = sc_get_iso7816_driver();
|
|
|
|
iso_ops = iso_drv->ops;
|
|
|
|
pgp_ops = *iso_ops;
|
|
pgp_ops.match_card = pgp_match_card;
|
|
pgp_ops.init = pgp_init;
|
|
pgp_ops.finish = pgp_finish;
|
|
pgp_ops.select_file = pgp_select_file;
|
|
pgp_ops.list_files = pgp_list_files;
|
|
pgp_ops.read_binary = pgp_read_binary;
|
|
pgp_ops.write_binary = pgp_write_binary;
|
|
pgp_ops.pin_cmd = pgp_pin_cmd;
|
|
pgp_ops.get_data = pgp_get_data;
|
|
pgp_ops.put_data = pgp_put_data;
|
|
pgp_ops.set_security_env= pgp_set_security_env;
|
|
pgp_ops.compute_signature= pgp_compute_signature;
|
|
pgp_ops.decipher = pgp_decipher;
|
|
pgp_ops.card_ctl = pgp_card_ctl;
|
|
pgp_ops.delete_file = pgp_delete_file;
|
|
pgp_ops.update_binary = pgp_update_binary;
|
|
|
|
return &pgp_drv;
|
|
}
|
|
|
|
struct sc_card_driver *
|
|
sc_get_openpgp_driver(void)
|
|
{
|
|
return sc_get_driver();
|
|
}
|