1556 lines
44 KiB
C
1556 lines
44 KiB
C
|
/*
|
||
|
* card-cac.c: Support for CAC from NIST SP800-73
|
||
|
* card-default.c: Support for cards with no driver
|
||
|
*
|
||
|
* Copyright (C) 2001, 2002 Juha Yrjölä <juha.yrjola@iki.fi>
|
||
|
* Copyright (C) 2005,2006,2007,2008,2009,2010 Douglas E. Engert <deengert@anl.gov>
|
||
|
* Copyright (C) 2006, Identity Alliance, Thomas Harning <thomas.harning@identityalliance.com>
|
||
|
* Copyright (C) 2007, EMC, Russell Larner <rlarner@rsa.com>
|
||
|
* Copyright (C) 2016, Red Hat, Inc.
|
||
|
*
|
||
|
* CAC driver author: Robert Relyea <rrelyea@redhat.com>
|
||
|
*
|
||
|
* 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
|
||
|
*/
|
||
|
|
||
|
#if HAVE_CONFIG_H
|
||
|
#include "config.h"
|
||
|
#endif
|
||
|
|
||
|
#include <ctype.h>
|
||
|
#include <fcntl.h>
|
||
|
#include <limits.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
|
||
|
#ifdef _WIN32
|
||
|
#include <io.h>
|
||
|
#else
|
||
|
#include <unistd.h>
|
||
|
#endif
|
||
|
|
||
|
#ifdef ENABLE_OPENSSL
|
||
|
/* openssl only needed for card administration */
|
||
|
#include <openssl/evp.h>
|
||
|
#include <openssl/bio.h>
|
||
|
#include <openssl/pem.h>
|
||
|
#include <openssl/rand.h>
|
||
|
#include <openssl/rsa.h>
|
||
|
#endif /* ENABLE_OPENSSL */
|
||
|
|
||
|
#include "internal.h"
|
||
|
#include "simpletlv.h"
|
||
|
#include "cardctl.h"
|
||
|
#ifdef ENABLE_ZLIB
|
||
|
#include "compression.h"
|
||
|
#endif
|
||
|
#include "iso7816.h"
|
||
|
|
||
|
#define CAC_MAX_SIZE 4096 /* arbitrary, just needs to be 'large enough' */
|
||
|
/*
|
||
|
* CAC hardware and APDU constants
|
||
|
*/
|
||
|
#define CAC_MAX_CHUNK_SIZE 240
|
||
|
#define CAC_INS_GET_CERTIFICATE 0x36 /* CAC1 command to read a certificate */
|
||
|
#define CAC_INS_SIGN_DECRYPT 0x42 /* A crypto operation */
|
||
|
#define CAC_P1_STEP 0x80
|
||
|
#define CAC_P1_FINAL 0x00
|
||
|
#define CAC_INS_READ_FILE 0x52 /* read a TL or V file */
|
||
|
#define CAC_FILE_TAG 1
|
||
|
#define CAC_FILE_VALUE 2
|
||
|
/* TAGS in a TL file */
|
||
|
#define CAC_TAG_CERTIFICATE 0x70
|
||
|
#define CAC_TAG_CERTINFO 0x71
|
||
|
#define CAC_TAG_CUID 0xF0
|
||
|
#define CAC_TAG_CC_VERSION_NUMBER 0xF1
|
||
|
#define CAC_TAG_GRAMMAR_VERION_NUMBER 0xF2
|
||
|
#define CAC_TAG_CARDURL 0xF3
|
||
|
#define CAC_TAG_PKCS15 0xF4
|
||
|
#define CAC_TAG_ACCESS_CONTROL 0xF6
|
||
|
#define CAC_TAG_DATA_MODEL 0xF5
|
||
|
#define CAC_TAG_CARD_APDU 0xF7
|
||
|
#define CAC_TAG_REDIRECTION 0xFA
|
||
|
#define CAC_TAG_CAPABILITY_TUPLES 0xFB
|
||
|
#define CAC_TAG_STATUS_TUPLES 0xFC
|
||
|
#define CAC_TAG_NEXT_CCC 0xFD
|
||
|
#define CAC_TAG_ERROR_CODES 0xFE
|
||
|
#define CAC_APP_TYPE_GENERAL 0x01
|
||
|
#define CAC_APP_TYPE_SKI 0x02
|
||
|
#define CAC_APP_TYPE_PKI 0x04
|
||
|
|
||
|
/* hardware data structures (returned in the CCC) */
|
||
|
/* part of the card_url */
|
||
|
typedef struct cac_access_profile {
|
||
|
u8 GCACR_listID;
|
||
|
u8 GCACR_readTagListACRID;
|
||
|
u8 GCACR_updatevalueACRID;
|
||
|
u8 GCACR_readvalueACRID;
|
||
|
u8 GCACR_createACRID;
|
||
|
u8 GCACR_deleteACRID;
|
||
|
u8 CryptoACR_listID;
|
||
|
u8 CryptoACR_getChallengeACRID;
|
||
|
u8 CryptoACR_internalAuthenicateACRID;
|
||
|
u8 CryptoACR_pkiComputeACRID;
|
||
|
u8 CryptoACR_readTagListACRID;
|
||
|
u8 CryptoACR_updatevalueACRID;
|
||
|
u8 CryptoACR_readvalueACRID;
|
||
|
u8 CryptoACR_createACRID;
|
||
|
u8 CryptoACR_deleteACRID;
|
||
|
} cac_access_profile_t;
|
||
|
|
||
|
/* part of the card url */
|
||
|
typedef struct cac_access_key_info {
|
||
|
u8 keyFileID[2];
|
||
|
u8 keynumber;
|
||
|
} cac_access_key_info_t;
|
||
|
|
||
|
typedef struct cac_card_url {
|
||
|
u8 rid[5];
|
||
|
u8 cardApplicationType;
|
||
|
u8 objectID[2];
|
||
|
u8 applicationID[2];
|
||
|
cac_access_profile_t accessProfile;
|
||
|
u8 pinID; /* not used for VM cards */
|
||
|
cac_access_key_info_t accessKeyInfo; /* not used for VM cards */
|
||
|
u8 keyCryptoAlgorithm; /* not used for VM cards */
|
||
|
} cac_card_url_t;
|
||
|
|
||
|
typedef struct cac_cuid {
|
||
|
u8 gsc_rid[5];
|
||
|
u8 manufacturer_id;
|
||
|
u8 card_type;
|
||
|
u8 card_id;
|
||
|
} cac_cuid_t;
|
||
|
|
||
|
/* data structures to store meta data about CAC objects */
|
||
|
typedef struct cac_object {
|
||
|
const char *name;
|
||
|
int fd;
|
||
|
sc_path_t path;
|
||
|
} cac_object_t;
|
||
|
|
||
|
/*
|
||
|
* Flags for Current Selected Object Type
|
||
|
* CAC files are TLV files, with TL and V separated. For generic
|
||
|
* containers we reintegrate the TL anv V portions into a single
|
||
|
* file to read. Certs are also TLV files, but pkcs15 wants the
|
||
|
* actual certificate. At select time we know the patch which tells
|
||
|
* us what time of files we want to read. We remember that type
|
||
|
* so that read_binary can do the appropriate processing.
|
||
|
*/
|
||
|
#define CAC_OBJECT_TYPE_CERT 1
|
||
|
#define CAC_OBJECT_TYPE_TLV_FILE 4
|
||
|
|
||
|
/*
|
||
|
* CAC private data per card state
|
||
|
*/
|
||
|
typedef struct cac_private_data {
|
||
|
int object_type; /* select set this so we know how to read the file */
|
||
|
int cert_next; /* index number for the next certificate found in the list */
|
||
|
u8 *cache_buf; /* cached version of the currently selected file */
|
||
|
size_t cache_buf_len; /* length of the cached selected file */
|
||
|
int cached; /* is the cached selected file valid */
|
||
|
cac_cuid_t cuid; /* card unique ID from the CCC */
|
||
|
u8 *cac_id; /* card serial number */
|
||
|
size_t cac_id_len; /* card serial number len */
|
||
|
list_t pki_list; /* list of pki containers */
|
||
|
cac_object_t *pki_current; /* current pki object _ctl function */
|
||
|
list_t general_list; /* list of general containers */
|
||
|
cac_object_t *general_current; /* current object for _ctl function */
|
||
|
} cac_private_data_t;
|
||
|
|
||
|
#define CAC_DATA(card) ((cac_private_data_t*)card->drv_data)
|
||
|
|
||
|
int cac_list_compare_path(const void *a, const void *b)
|
||
|
{
|
||
|
if (a == NULL || b == NULL)
|
||
|
return 1;
|
||
|
return memcmp( &((cac_object_t *) a)->path,
|
||
|
&((cac_object_t *) b)->path, sizeof(sc_path_t));
|
||
|
}
|
||
|
|
||
|
/* For SimCList autocopy, we need to know the size of the data elements */
|
||
|
size_t cac_list_meter(const void *el) {
|
||
|
return sizeof(cac_object_t);
|
||
|
}
|
||
|
|
||
|
static cac_private_data_t *cac_new_private_data(void)
|
||
|
{
|
||
|
cac_private_data_t *priv;
|
||
|
priv = calloc(1, sizeof(cac_private_data_t));
|
||
|
list_init(&priv->pki_list);
|
||
|
list_attributes_comparator(&priv->pki_list, cac_list_compare_path);
|
||
|
list_attributes_copy(&priv->pki_list, cac_list_meter, 1);
|
||
|
list_init(&priv->general_list);
|
||
|
list_attributes_comparator(&priv->general_list, cac_list_compare_path);
|
||
|
list_attributes_copy(&priv->general_list, cac_list_meter, 1);
|
||
|
/* set other fields as appropriate */
|
||
|
|
||
|
return priv;
|
||
|
}
|
||
|
|
||
|
static void cac_free_private_data(cac_private_data_t *priv)
|
||
|
{
|
||
|
free(priv->cac_id);
|
||
|
free(priv->cache_buf);
|
||
|
list_destroy(&priv->pki_list);
|
||
|
list_destroy(&priv->general_list);
|
||
|
free(priv);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
static int cac_add_object_to_list(list_t *list, const cac_object_t *object)
|
||
|
{
|
||
|
if (list_append(list, object) < 0)
|
||
|
return SC_ERROR_UNKNOWN;
|
||
|
return SC_SUCCESS;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Set up the normal CAC paths
|
||
|
*/
|
||
|
#define CAC_TO_AID(x) x, sizeof(x)-1
|
||
|
|
||
|
#define CAC_2_RID "\xA0\x00\x00\x01\x16"
|
||
|
#define CAC_1_RID "\xA0\x00\x00\x00\x79"
|
||
|
#define CAC_1_CM_AID "\xA0\x00\x00\x00\x30\x00\00"
|
||
|
|
||
|
static const sc_path_t cac_CCC_Path = {
|
||
|
"", 0,
|
||
|
0,0,SC_PATH_TYPE_DF_NAME,
|
||
|
{ CAC_TO_AID(CAC_2_RID "\xDB\x00") }
|
||
|
};
|
||
|
|
||
|
#define MAX_CAC_SLOTS 10 /* arbitrary, just needs to be 'large enough' */
|
||
|
/* default certificate labels for the CAC card */
|
||
|
static const char *cac_labels[MAX_CAC_SLOTS] = {
|
||
|
"CAC ID Certificate",
|
||
|
"CAC Email Signature Certificate",
|
||
|
"CAC Email Encryption Certificate",
|
||
|
"CAC Cert 3",
|
||
|
"CAC Cert 4",
|
||
|
"CAC Cert 5",
|
||
|
"CAC Cert 6",
|
||
|
"CAC Cert 7",
|
||
|
"CAC Cert 8",
|
||
|
"CAC Cert 9"
|
||
|
};
|
||
|
|
||
|
/* template for a cac1 pki object */
|
||
|
static const cac_object_t cac_cac1_pki_obj = {
|
||
|
"CAC Certificate", 0x0, { { 0 }, 0, 0, 0, SC_PATH_TYPE_DF_NAME,
|
||
|
{ CAC_TO_AID(CAC_1_RID "\x01\x00") } }
|
||
|
};
|
||
|
|
||
|
/* template for cac1 cuid */
|
||
|
static const cac_cuid_t cac_cac1_cuid = {
|
||
|
{ 0xa0, 0x00, 0x00, 0x00, 0x79 },
|
||
|
2, 2, 0
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* CAC-1 general objectes defined in 4.3.1.2 of CAC Applet Developer Guide Version 1.0.
|
||
|
* doubles as a source for CAC-2 labels.
|
||
|
*/
|
||
|
static const cac_object_t cac_1_objects[] = {
|
||
|
{ "Person Instance", 0x200, { { 0 }, 0, 0, 0, SC_PATH_TYPE_DF_NAME,
|
||
|
{ CAC_TO_AID(CAC_1_RID "\x02\x00") }}},
|
||
|
{ "Personnel", 0x201, { { 0 }, 0, 0, 0, SC_PATH_TYPE_DF_NAME,
|
||
|
{ CAC_TO_AID(CAC_1_RID "\x02\x01") }}},
|
||
|
{ "Benefits", 0x202, { { 0 }, 0, 0, 0, SC_PATH_TYPE_DF_NAME,
|
||
|
{ CAC_TO_AID(CAC_1_RID "\x02\x02") }}},
|
||
|
{ "Other Benefits", 0x202, { { 0 }, 0, 0, 0, SC_PATH_TYPE_DF_NAME,
|
||
|
{ CAC_TO_AID(CAC_1_RID "\x02\x02") }}},
|
||
|
{ "PKI Credential", 0x2FD, { { 0 }, 0, 0, 0, SC_PATH_TYPE_DF_NAME,
|
||
|
{ CAC_TO_AID(CAC_1_RID "\x02\xFD") }}},
|
||
|
{ "PKI Certificate", 0x2FE, { { 0 }, 0, 0, 0, SC_PATH_TYPE_DF_NAME,
|
||
|
{ CAC_TO_AID(CAC_1_RID "\x02\xFE") }}},
|
||
|
};
|
||
|
|
||
|
static const int cac_1_object_count = sizeof(cac_1_objects)/sizeof(cac_1_objects[0]);
|
||
|
|
||
|
|
||
|
/*
|
||
|
* use the object id to find our object info on the object in our CAC-1 list
|
||
|
*/
|
||
|
static const cac_object_t *cac_find_obj_by_id(unsigned short object_id)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
for (i=0; i < cac_1_object_count; i++) {
|
||
|
if (cac_1_objects[i].fd == object_id) {
|
||
|
return &cac_1_objects[i];
|
||
|
}
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Lookup the path in the pki list to see if it is a cert path
|
||
|
*/
|
||
|
static int cac_is_cert(cac_private_data_t * priv, const sc_path_t *in_path)
|
||
|
{
|
||
|
cac_object_t test_obj;
|
||
|
test_obj.path = *in_path;
|
||
|
test_obj.path.index = 0;
|
||
|
test_obj.path.count = 0;
|
||
|
|
||
|
return (list_contains(&priv->pki_list, &test_obj) != 0);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Send a command and receive data.
|
||
|
*
|
||
|
* A caller may provide a buffer, and length to read. If not provided,
|
||
|
* an internal 4096 byte buffer is used, and a copy is returned to the
|
||
|
* caller. that need to be freed by the caller.
|
||
|
*
|
||
|
* modelled after a similiar function in card-piv.c
|
||
|
*/
|
||
|
|
||
|
static int cac_apdu_io(sc_card_t *card, int ins, int p1, int p2,
|
||
|
const u8 * sendbuf, size_t sendbuflen, u8 ** recvbuf,
|
||
|
size_t * recvbuflen)
|
||
|
{
|
||
|
int r;
|
||
|
sc_apdu_t apdu;
|
||
|
u8 rbufinitbuf[CAC_MAX_SIZE];
|
||
|
u8 *rbuf;
|
||
|
size_t rbuflen;
|
||
|
|
||
|
|
||
|
SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);
|
||
|
|
||
|
sc_debug(card->ctx, SC_LOG_DEBUG_NORMAL, "%02x %02x %02x %d : %d %d\n",
|
||
|
ins, p1, p2, sendbuflen, card->max_send_size, card->max_recv_size);
|
||
|
|
||
|
rbuf = rbufinitbuf;
|
||
|
rbuflen = sizeof(rbufinitbuf);
|
||
|
|
||
|
/* if caller provided a buffer and length */
|
||
|
if (recvbuf && *recvbuf && recvbuflen && *recvbuflen) {
|
||
|
rbuf = *recvbuf;
|
||
|
rbuflen = *recvbuflen;
|
||
|
}
|
||
|
|
||
|
sc_format_apdu(card, &apdu,
|
||
|
recvbuf ? SC_APDU_CASE_4_SHORT: SC_APDU_CASE_3_SHORT,
|
||
|
ins, p1, p2);
|
||
|
|
||
|
apdu.lc = sendbuflen;
|
||
|
apdu.datalen = sendbuflen;
|
||
|
apdu.data = sendbuf;
|
||
|
|
||
|
if (recvbuf) {
|
||
|
apdu.resp = rbuf;
|
||
|
apdu.le = (rbuflen > 255) ? 255 : rbuflen;
|
||
|
apdu.resplen = rbuflen;
|
||
|
} else {
|
||
|
apdu.resp = rbuf;
|
||
|
apdu.le = 0;
|
||
|
apdu.resplen = 0;
|
||
|
}
|
||
|
|
||
|
sc_debug(card->ctx, SC_LOG_DEBUG_NORMAL,"calling sc_transmit_apdu flags=%x le=%d, resplen=%d, resp=%p",
|
||
|
apdu.flags, apdu.le, apdu.resplen, apdu.resp);
|
||
|
|
||
|
/* with new adpu.c and chaining, this actually reads the whole object */
|
||
|
r = sc_transmit_apdu(card, &apdu);
|
||
|
|
||
|
sc_debug(card->ctx, SC_LOG_DEBUG_NORMAL,"result r=%d apdu.resplen=%d sw1=%02x sw2=%02x",
|
||
|
r, apdu.resplen, apdu.sw1, apdu.sw2);
|
||
|
if (r < 0) {
|
||
|
sc_debug(card->ctx, SC_LOG_DEBUG_NORMAL,"Transmit failed");
|
||
|
goto err;
|
||
|
}
|
||
|
|
||
|
if (apdu.sw1 == 0x61) {
|
||
|
r = sc_check_sw(card, apdu.sw1, apdu.sw2);
|
||
|
}
|
||
|
|
||
|
if (r < 0) {
|
||
|
sc_debug(card->ctx, SC_LOG_DEBUG_NORMAL, "Card returned error ");
|
||
|
goto err;
|
||
|
}
|
||
|
|
||
|
if (recvbuflen) {
|
||
|
if (recvbuf && *recvbuf == NULL) {
|
||
|
*recvbuf = malloc(apdu.resplen);
|
||
|
if (*recvbuf == NULL) {
|
||
|
r = SC_ERROR_OUT_OF_MEMORY;
|
||
|
goto err;
|
||
|
}
|
||
|
memcpy(*recvbuf, rbuf, apdu.resplen);
|
||
|
}
|
||
|
*recvbuflen = apdu.resplen;
|
||
|
r = *recvbuflen;
|
||
|
}
|
||
|
|
||
|
err:
|
||
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_NORMAL, r);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Read a CAC TLV file. Parameters specify if the TLV file is TL (Tag/Length) file or a V (value) file
|
||
|
*/
|
||
|
#define HIGH_BYTE_OF_SHORT(x) (((x)>> 8) & 0xff)
|
||
|
#define LOW_BYTE_OF_SHORT(x) ((x) & 0xff)
|
||
|
static int cac_read_file(sc_card_t *card, int file_type, u8 **out_buf, size_t *out_len)
|
||
|
{
|
||
|
u8 params[2];
|
||
|
u8 count[2];
|
||
|
u8 *out = NULL;
|
||
|
u8 *out_ptr;
|
||
|
size_t offset = 0;
|
||
|
size_t size = 0;
|
||
|
size_t left = 0;
|
||
|
size_t len;
|
||
|
int r;
|
||
|
|
||
|
params[0] = file_type;
|
||
|
params[1] = 2;
|
||
|
|
||
|
/* get the size */
|
||
|
len = sizeof(count);
|
||
|
out_ptr = count;
|
||
|
r = cac_apdu_io(card, CAC_INS_READ_FILE, 0, 0, ¶ms[0], sizeof(params), &out_ptr, &len);
|
||
|
if (r < 0)
|
||
|
goto fail;
|
||
|
|
||
|
left = size = lebytes2ushort(count);
|
||
|
sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "got %d bytes out_ptr=%lx count&=%lx count[0]=0x%02x count[1]=0x%02x, len=0x%04x (%d)",
|
||
|
len, (unsigned long) out_ptr, (unsigned long)&count, count[0], count[1], size, size);
|
||
|
out = out_ptr = malloc(size);
|
||
|
if (out == NULL) {
|
||
|
r = SC_ERROR_OUT_OF_MEMORY;
|
||
|
goto fail;
|
||
|
}
|
||
|
for (offset += 2; left > 0; offset += len, left -= len, out_ptr += len) {
|
||
|
len = MIN(left, CAC_MAX_CHUNK_SIZE);
|
||
|
params[1] = len;
|
||
|
r = cac_apdu_io(card, CAC_INS_READ_FILE, HIGH_BYTE_OF_SHORT(offset), LOW_BYTE_OF_SHORT(offset),
|
||
|
¶ms[0], sizeof(params), &out_ptr, &len);
|
||
|
if (r < 0) {
|
||
|
goto fail;
|
||
|
}
|
||
|
}
|
||
|
*out_len = size;
|
||
|
*out_buf = out;
|
||
|
return SC_SUCCESS;
|
||
|
fail:
|
||
|
if (out)
|
||
|
free(out);
|
||
|
*out_len = 0;
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* OLD cac read certificate, only use with CAC-1 card.
|
||
|
*/
|
||
|
static int cac_cac1_get_certificate(sc_card_t *card, u8 **out_buf, size_t *out_len)
|
||
|
{
|
||
|
u8 buf[CAC_MAX_SIZE];
|
||
|
u8 *out_ptr;
|
||
|
size_t size = 0;
|
||
|
size_t left = 0;
|
||
|
size_t len, next_len;
|
||
|
sc_apdu_t apdu;
|
||
|
int r;
|
||
|
|
||
|
|
||
|
/* get the size */
|
||
|
size = left = *out_buf ? *out_len : sizeof(buf);
|
||
|
out_ptr = *out_buf ? *out_buf : buf;
|
||
|
sc_format_apdu(card, &apdu, SC_APDU_CASE_2_SHORT, CAC_INS_GET_CERTIFICATE, 0, 0 );
|
||
|
next_len = MIN(left, 100);
|
||
|
for (; left > 0; left -= len, out_ptr += len) {
|
||
|
len = next_len;
|
||
|
apdu.resp = out_ptr;
|
||
|
apdu.le = len;
|
||
|
apdu.resplen = left;
|
||
|
|
||
|
r = sc_transmit_apdu(card, &apdu);
|
||
|
if (r < 0) {
|
||
|
break;
|
||
|
}
|
||
|
/* in the old CAC-1, 0x63 means 'more data' in addition to 'pin failed' */
|
||
|
if (apdu.sw1 != 0x63) {
|
||
|
/* we've either finished reading, or hit an error, break */
|
||
|
r = sc_check_sw(card, apdu.sw1, apdu.sw2);
|
||
|
left -= len;
|
||
|
break;
|
||
|
}
|
||
|
next_len = MIN(left,apdu.sw2);
|
||
|
}
|
||
|
if (r < 0) {
|
||
|
return r;
|
||
|
}
|
||
|
r = size - left;
|
||
|
if (*out_buf == NULL) {
|
||
|
*out_buf = malloc(r);
|
||
|
if (*out_buf == NULL) {
|
||
|
return SC_ERROR_OUT_OF_MEMORY;
|
||
|
}
|
||
|
memcpy(*out_buf, buf, r);
|
||
|
}
|
||
|
*out_len = r;
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
/* Create a fake tag/length file in Simple TLV for cac1 cards based on the val_len.
|
||
|
*/
|
||
|
int cac_cac1_get_cert_tag(sc_card_t *card, size_t val_len, u8 **tlp, size_t *tl_len_p)
|
||
|
{
|
||
|
static const u8 cac_cac1_cert_tag[] = { CAC_TAG_CERTINFO, 1, CAC_TAG_CERTIFICATE, 0xff, 0, 0 };
|
||
|
u8 *tl, *tl1, *tl2;
|
||
|
size_t tl_len;
|
||
|
int rv = SC_SUCCESS;
|
||
|
|
||
|
tl_len = sizeof(cac_cac1_cert_tag);
|
||
|
tl = malloc(tl_len);
|
||
|
if (tl == NULL)
|
||
|
return SC_ERROR_OUT_OF_MEMORY;
|
||
|
memcpy(tl, cac_cac1_cert_tag, tl_len);
|
||
|
|
||
|
rv = sc_simpletlv_put_tag(CAC_TAG_CERTINFO, 1, tl, tl_len, &tl1);
|
||
|
if (rv != SC_SUCCESS)
|
||
|
goto failure;
|
||
|
|
||
|
val_len -= 1; /* one byte is CERTINFO Value */
|
||
|
tl_len -= (tl1 - tl);
|
||
|
rv = sc_simpletlv_put_tag(CAC_TAG_CERTIFICATE, val_len, tl1, tl_len, &tl2);
|
||
|
if (rv != SC_SUCCESS)
|
||
|
goto failure;
|
||
|
|
||
|
*tlp = tl;
|
||
|
*tl_len_p = (tl2 - tl);
|
||
|
return SC_SUCCESS;
|
||
|
failure:
|
||
|
free(tl);
|
||
|
return rv;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Callers of this may be expecting a certificate,
|
||
|
* select file will have saved the object type for us
|
||
|
* as well as set that we want the cert from the object.
|
||
|
*/
|
||
|
static int cac_read_binary(sc_card_t *card, unsigned int idx,
|
||
|
unsigned char *buf, size_t count, unsigned long flags)
|
||
|
{
|
||
|
cac_private_data_t * priv = CAC_DATA(card);
|
||
|
int r = 0;
|
||
|
u8 *tl = NULL, *val = NULL;
|
||
|
u8 *tl_ptr, *val_ptr, *tlv_ptr, *tl_start;
|
||
|
u8 *cert_ptr;
|
||
|
size_t tl_len, val_len, tlv_len;
|
||
|
size_t len, tl_head_len, cert_len;
|
||
|
u8 cert_type, tag;
|
||
|
|
||
|
SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);
|
||
|
|
||
|
/* if we didn't return it all last time, return the remainder */
|
||
|
if (priv->cached) {
|
||
|
sc_debug(card->ctx, SC_LOG_DEBUG_NORMAL,"returning cached value idx=%d count=%d",idx, count);
|
||
|
if (idx > priv->cache_buf_len) {
|
||
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_NORMAL, SC_ERROR_FILE_END_REACHED);
|
||
|
}
|
||
|
len = MIN(count, priv->cache_buf_len-idx);
|
||
|
memcpy(buf, &priv->cache_buf[idx], len);
|
||
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_NORMAL, len);
|
||
|
}
|
||
|
|
||
|
sc_debug(card->ctx, SC_LOG_DEBUG_NORMAL,"clearing cache idx=%d count=%d",idx, count);
|
||
|
if (priv->cache_buf) {
|
||
|
free(priv->cache_buf);
|
||
|
priv->cache_buf = NULL;
|
||
|
priv->cache_buf_len = 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
if (priv->object_type <= 0)
|
||
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_NORMAL, SC_ERROR_INTERNAL);
|
||
|
|
||
|
if ((card->type == SC_CARD_TYPE_CAC_I) && (priv->object_type == CAC_OBJECT_TYPE_CERT)) {
|
||
|
/* SPICE smart card emulator only presents CAC-1 cards with the old CAC-1 interface as
|
||
|
* certs. If we are a cac 1 card, use the old interface */
|
||
|
r = cac_cac1_get_certificate(card, &val, &val_len);
|
||
|
if (r < 0)
|
||
|
goto done;
|
||
|
|
||
|
r = cac_cac1_get_cert_tag(card, val_len, &tl, &tl_len);
|
||
|
if (r < 0)
|
||
|
goto done;
|
||
|
} else {
|
||
|
r = cac_read_file(card, CAC_FILE_TAG, &tl, &tl_len);
|
||
|
if (r < 0) {
|
||
|
goto done;
|
||
|
}
|
||
|
|
||
|
r = cac_read_file(card, CAC_FILE_VALUE, &val, &val_len);
|
||
|
if (r < 0)
|
||
|
goto done;
|
||
|
}
|
||
|
|
||
|
switch (priv->object_type) {
|
||
|
case CAC_OBJECT_TYPE_TLV_FILE:
|
||
|
tlv_len = tl_len + val_len;
|
||
|
priv->cache_buf = malloc(tlv_len);
|
||
|
if (priv->cache_buf == NULL) {
|
||
|
r = SC_ERROR_OUT_OF_MEMORY;
|
||
|
goto done;
|
||
|
}
|
||
|
priv->cache_buf_len = tlv_len;
|
||
|
|
||
|
for (tl_ptr = tl, val_ptr=val, tlv_ptr = priv->cache_buf;
|
||
|
tl_len > 2 && val_len > 0 && tlv_len > 0;
|
||
|
val_len -= len, tlv_len -= len, val_ptr += len, tlv_ptr += len) {
|
||
|
/* get the tag and the length */
|
||
|
tl_start = tl_ptr;
|
||
|
if (sc_simpletlv_read_tag(&tl_ptr, tl_len, &tag, &len) != SC_SUCCESS)
|
||
|
break;
|
||
|
tl_head_len = (tl_ptr - tl_start);
|
||
|
sc_simpletlv_put_tag(tag, len, tlv_ptr, tlv_len, &tlv_ptr);
|
||
|
tlv_len -= tl_head_len;
|
||
|
tl_len -= tl_head_len;
|
||
|
|
||
|
/* don't crash on bad data */
|
||
|
if (val_len < len) {
|
||
|
len = val_len;
|
||
|
}
|
||
|
/* if we run out of return space, truncate */
|
||
|
if (tlv_len < len) {
|
||
|
len = tlv_len;
|
||
|
}
|
||
|
memcpy(tlv_ptr, val_ptr, len);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case CAC_OBJECT_TYPE_CERT:
|
||
|
/* read file */
|
||
|
sc_debug(card->ctx, SC_LOG_DEBUG_NORMAL," obj= cert_file, val_len=%d (0x%04x)", val_len, val_len);
|
||
|
cert_len = 0;
|
||
|
cert_ptr = NULL;
|
||
|
cert_type = 0;
|
||
|
tl_head_len = 2;
|
||
|
for (tl_ptr = tl, val_ptr=val; tl_len >= 2;
|
||
|
val_len -= len, val_ptr += len, tl_len -= tl_head_len) {
|
||
|
tl_start = tl_ptr;
|
||
|
if (sc_simpletlv_read_tag(&tl_ptr, tl_len, &tag, &len) != SC_SUCCESS)
|
||
|
break;
|
||
|
tl_head_len = tl_ptr - tl_start;
|
||
|
if (tag == CAC_TAG_CERTIFICATE) {
|
||
|
cert_len = len;
|
||
|
cert_ptr = val_ptr;
|
||
|
}
|
||
|
if (tag == CAC_TAG_CERTINFO) {
|
||
|
if ((len >= 1) && (val_len >=1)) {
|
||
|
cert_type = *val_ptr;
|
||
|
}
|
||
|
}
|
||
|
if ((val_len < len) || (tl_len < tl_head_len)) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
/* if the info byte is 1, then the cert is compressed, decompress it */
|
||
|
if ((cert_type & 0x3) == 1) {
|
||
|
#ifdef ENABLE_ZLIB
|
||
|
r = sc_decompress_alloc(&priv->cache_buf, &priv->cache_buf_len,
|
||
|
cert_ptr, cert_len, COMPRESSION_AUTO);
|
||
|
#else
|
||
|
sc_log(card->ctx, "PIV compression not supported, no zlib");
|
||
|
r = SC_ERROR_NOT_SUPPORTED;
|
||
|
#endif
|
||
|
if (r)
|
||
|
goto done;
|
||
|
} else {
|
||
|
priv->cache_buf = malloc(cert_len);
|
||
|
if (priv->cache_buf == NULL) {
|
||
|
r = SC_ERROR_OUT_OF_MEMORY;
|
||
|
goto done;
|
||
|
}
|
||
|
priv->cache_buf_len = cert_len;
|
||
|
memcpy(priv->cache_buf, cert_ptr, cert_len);
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
/* Unknown object type */
|
||
|
r = SC_ERROR_INTERNAL;
|
||
|
goto done;
|
||
|
}
|
||
|
|
||
|
/* OK we've read the data, now copy the required portion out to the callers buffer */
|
||
|
priv->cached = 1;
|
||
|
len = MIN(count, priv->cache_buf_len-idx);
|
||
|
memcpy(buf, &priv->cache_buf[idx], len);
|
||
|
r = len;
|
||
|
done:
|
||
|
if (tl)
|
||
|
free(tl);
|
||
|
if (val)
|
||
|
free(val);
|
||
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_NORMAL, r);
|
||
|
}
|
||
|
|
||
|
/* CAC driver is read only */
|
||
|
static int cac_write_binary(sc_card_t *card, unsigned int idx,
|
||
|
const u8 *buf, size_t count, unsigned long flags)
|
||
|
{
|
||
|
|
||
|
SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);
|
||
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_NORMAL, SC_ERROR_NOT_SUPPORTED);
|
||
|
}
|
||
|
|
||
|
/* initialize getting a list and return the number of elements in the list */
|
||
|
static int cac_get_init_and_get_count(list_t *list, cac_object_t **entry, int *countp)
|
||
|
{
|
||
|
*countp = list_size(list);
|
||
|
list_iterator_start(list);
|
||
|
*entry = list_iterator_next(list);
|
||
|
return SC_SUCCESS;
|
||
|
}
|
||
|
|
||
|
/* finalize the list iterator */
|
||
|
static int cac_final_iterator(list_t *list)
|
||
|
{
|
||
|
list_iterator_stop(list);
|
||
|
return SC_SUCCESS;
|
||
|
}
|
||
|
|
||
|
/* fill in the obj_info for the current object on the list and advance to the next object */
|
||
|
static int cac_fill_object_info(list_t *list, cac_object_t **entry, sc_pkcs15_data_info_t *obj_info)
|
||
|
{
|
||
|
memset(obj_info, 0, sizeof(sc_pkcs15_data_info_t));
|
||
|
if (*entry == NULL) {
|
||
|
return SC_ERROR_FILE_END_REACHED;
|
||
|
}
|
||
|
|
||
|
obj_info->path = (*entry)->path;
|
||
|
obj_info->path.count = CAC_MAX_SIZE-1; /* read something from the object */
|
||
|
obj_info->id.value[0] = ((*entry)->fd >> 8) & 0xff;
|
||
|
obj_info->id.value[1] = (*entry)->fd & 0xff;
|
||
|
obj_info->id.len = 2;
|
||
|
strncpy(obj_info->app_label, (*entry)->name, SC_PKCS15_MAX_LABEL_SIZE-1);
|
||
|
*entry = list_iterator_next(list);
|
||
|
return SC_SUCCESS;
|
||
|
}
|
||
|
|
||
|
static int cac_get_serial_nr_from_CUID(sc_card_t* card, sc_serial_number_t* serial)
|
||
|
{
|
||
|
cac_private_data_t * priv = CAC_DATA(card);
|
||
|
|
||
|
SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_NORMAL);
|
||
|
if (card->serialnr.len) {
|
||
|
*serial = card->serialnr;
|
||
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_NORMAL, SC_SUCCESS);
|
||
|
}
|
||
|
if (priv->cac_id_len) {
|
||
|
serial->len = MIN(priv->cac_id_len, SC_MAX_SERIALNR);
|
||
|
memcpy(serial->value, priv->cac_id, priv->cac_id_len);
|
||
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_NORMAL, SC_SUCCESS);
|
||
|
}
|
||
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_NORMAL, SC_ERROR_FILE_NOT_FOUND);
|
||
|
}
|
||
|
|
||
|
|
||
|
static int cac_card_ctl(sc_card_t *card, unsigned long cmd, void *ptr)
|
||
|
{
|
||
|
cac_private_data_t * priv = CAC_DATA(card);
|
||
|
|
||
|
LOG_FUNC_CALLED(card->ctx);
|
||
|
sc_log(card->ctx, "cmd=%ld ptr=%p", cmd, ptr);
|
||
|
|
||
|
if (priv == NULL) {
|
||
|
LOG_FUNC_RETURN(card->ctx, SC_ERROR_INTERNAL);
|
||
|
}
|
||
|
switch(cmd) {
|
||
|
case SC_CARDCTL_GET_SERIALNR:
|
||
|
return cac_get_serial_nr_from_CUID(card, (sc_serial_number_t *) ptr);
|
||
|
case SC_CARDCTL_CAC_INIT_GET_GENERIC_OBJECTS:
|
||
|
return cac_get_init_and_get_count(&priv->general_list, &priv->general_current, (int *)ptr);
|
||
|
case SC_CARDCTL_CAC_INIT_GET_CERT_OBJECTS:
|
||
|
return cac_get_init_and_get_count(&priv->pki_list, &priv->pki_current, (int *)ptr);
|
||
|
case SC_CARDCTL_CAC_GET_NEXT_GENERIC_OBJECT:
|
||
|
return cac_fill_object_info(&priv->general_list, &priv->general_current, (sc_pkcs15_data_info_t *)ptr);
|
||
|
case SC_CARDCTL_CAC_GET_NEXT_CERT_OBJECT:
|
||
|
return cac_fill_object_info(&priv->pki_list, &priv->pki_current, (sc_pkcs15_data_info_t *)ptr);
|
||
|
case SC_CARDCTL_CAC_FINAL_GET_GENERIC_OBJECTS:
|
||
|
return cac_final_iterator(&priv->general_list);
|
||
|
case SC_CARDCTL_CAC_FINAL_GET_CERT_OBJECTS:
|
||
|
return cac_final_iterator(&priv->pki_list);
|
||
|
}
|
||
|
|
||
|
LOG_FUNC_RETURN(card->ctx, SC_ERROR_NOT_SUPPORTED);
|
||
|
}
|
||
|
|
||
|
static int cac_get_challenge(sc_card_t *card, u8 *rnd, size_t len)
|
||
|
{
|
||
|
u8 rbuf[8];
|
||
|
u8 *rbufp = NULL;
|
||
|
size_t rbuflen = 0;
|
||
|
int r;
|
||
|
|
||
|
SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);
|
||
|
|
||
|
sc_debug(card->ctx, SC_LOG_DEBUG_NORMAL,"challenge len=%d",len);
|
||
|
|
||
|
r = sc_lock(card);
|
||
|
if (r != SC_SUCCESS)
|
||
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_NORMAL, r);
|
||
|
|
||
|
|
||
|
/* CAC requires 8 byte response */
|
||
|
while (len > 0) {
|
||
|
size_t n;
|
||
|
|
||
|
rbufp = &rbuf[0];
|
||
|
rbuflen = sizeof(rbuf);
|
||
|
r = cac_apdu_io(card, 0x84, 0x00, 0x00, NULL, 0, &rbufp, &rbuflen);
|
||
|
if (r < 0) {
|
||
|
sc_unlock(card);
|
||
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_NORMAL, r);
|
||
|
}
|
||
|
n = len > rbuflen ? rbuflen : len;
|
||
|
memcpy(rnd, rbufp, n);
|
||
|
len -= n;
|
||
|
rnd += n;
|
||
|
}
|
||
|
|
||
|
r = sc_unlock(card);
|
||
|
|
||
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_NORMAL, r);
|
||
|
|
||
|
}
|
||
|
|
||
|
static int cac_set_security_env(sc_card_t *card, const sc_security_env_t *env, int se_num)
|
||
|
{
|
||
|
int r = SC_SUCCESS;
|
||
|
|
||
|
SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);
|
||
|
|
||
|
sc_debug(card->ctx, SC_LOG_DEBUG_NORMAL,"flags=%08x op=%d alg=%d algf=%08x algr=%08x kr0=%02x, krfl=%d\n",
|
||
|
env->flags, env->operation, env->algorithm, env->algorithm_flags,
|
||
|
env->algorithm_ref, env->key_ref[0], env->key_ref_len);
|
||
|
|
||
|
if (env->algorithm != SC_ALGORITHM_RSA) {
|
||
|
r = SC_ERROR_NO_CARD_SUPPORT;
|
||
|
}
|
||
|
|
||
|
|
||
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, r);
|
||
|
}
|
||
|
|
||
|
|
||
|
static int cac_restore_security_env(sc_card_t *card, int se_num)
|
||
|
{
|
||
|
SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);
|
||
|
|
||
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_NORMAL, SC_SUCCESS);
|
||
|
}
|
||
|
|
||
|
|
||
|
static int cac_rsa_op(sc_card_t *card,
|
||
|
const u8 * data, size_t datalen,
|
||
|
u8 * out, size_t outlen)
|
||
|
{
|
||
|
int r;
|
||
|
u8 *outp, *rbuf;
|
||
|
size_t rbuflen, outplen;
|
||
|
|
||
|
SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);
|
||
|
sc_debug(card->ctx, SC_LOG_DEBUG_NORMAL,"datalen=%d outlen=%d\n", datalen, outlen);
|
||
|
|
||
|
outp = out;
|
||
|
outplen = outlen;
|
||
|
|
||
|
/* Not strictly necessary. This code requires the caller to have selected the correct PKI container
|
||
|
* and authenticated to that container with the verifyPin command... All of this under the reader lock.
|
||
|
* The PKCS #15 higher level driver code does all this correctly (it's the same for all cards, just
|
||
|
* different sets of APDU's that need to be called), so this call is really a little bit of paranoia */
|
||
|
r = sc_lock(card);
|
||
|
if (r != SC_SUCCESS)
|
||
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_NORMAL, r);
|
||
|
|
||
|
|
||
|
rbuf = NULL;
|
||
|
rbuflen = 0;
|
||
|
for (; datalen > CAC_MAX_CHUNK_SIZE; data += CAC_MAX_CHUNK_SIZE, datalen -= CAC_MAX_CHUNK_SIZE) {
|
||
|
r = cac_apdu_io(card, CAC_INS_SIGN_DECRYPT, CAC_P1_STEP, 0,
|
||
|
data, CAC_MAX_CHUNK_SIZE, &rbuf, &rbuflen);
|
||
|
if (r < 0) {
|
||
|
break;
|
||
|
}
|
||
|
if (rbuflen != 0) {
|
||
|
int n = MIN(rbuflen, outplen);
|
||
|
memcpy(outp,rbuf, n);
|
||
|
outp += n;
|
||
|
outplen -= n;
|
||
|
}
|
||
|
free(rbuf);
|
||
|
rbuf = NULL;
|
||
|
rbuflen = 0;
|
||
|
}
|
||
|
if (r < 0) {
|
||
|
goto err;
|
||
|
}
|
||
|
rbuf = NULL;
|
||
|
rbuflen = 0;
|
||
|
r = cac_apdu_io(card, CAC_INS_SIGN_DECRYPT, CAC_P1_FINAL, 0, data, datalen, &rbuf, &rbuflen);
|
||
|
if (r < 0) {
|
||
|
goto err;
|
||
|
}
|
||
|
if (rbuflen != 0) {
|
||
|
int n = MIN(rbuflen, outplen);
|
||
|
memcpy(outp,rbuf, n);
|
||
|
outp += n;
|
||
|
outplen -= n;
|
||
|
}
|
||
|
free(rbuf);
|
||
|
rbuf = NULL;
|
||
|
r = outlen-outplen;
|
||
|
|
||
|
err:
|
||
|
sc_unlock(card);
|
||
|
if (r < 0) {
|
||
|
sc_mem_clear(out, outlen);
|
||
|
}
|
||
|
if (rbuf) {
|
||
|
free(rbuf);
|
||
|
}
|
||
|
|
||
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_NORMAL, r);
|
||
|
}
|
||
|
|
||
|
static int cac_compute_signature(sc_card_t *card,
|
||
|
const u8 * data, size_t datalen,
|
||
|
u8 * out, size_t outlen)
|
||
|
{
|
||
|
SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);
|
||
|
|
||
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, cac_rsa_op(card, data, datalen, out, outlen));
|
||
|
}
|
||
|
|
||
|
static int cac_decipher(sc_card_t *card,
|
||
|
const u8 * data, size_t datalen,
|
||
|
u8 * out, size_t outlen)
|
||
|
{
|
||
|
SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);
|
||
|
|
||
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, cac_rsa_op(card, data, datalen, out, outlen));
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CAC cards use SC_PATH_SELECT_OBJECT_ID rather than SC_PATH_SELECT_FILE_ID. In order to use more
|
||
|
* of the PKCS #15 structure, we call the selection SC_PATH_SELECT_FILE_ID, but we set p1 to 2 instead
|
||
|
* of 0. Also cac1 does not do any FCI, but it doesn't understand not selecting it. It returns invalid INS
|
||
|
* if it doesn't like anything about the select, so we always 'request' FCI for CAC1
|
||
|
*
|
||
|
* The rest is just copied from iso7816_select_file
|
||
|
*/
|
||
|
static int cac_select_file_by_type(sc_card_t *card, const sc_path_t *in_path, sc_file_t **file_out, int type)
|
||
|
{
|
||
|
struct sc_context *ctx;
|
||
|
struct sc_apdu apdu;
|
||
|
unsigned char buf[SC_MAX_APDU_BUFFER_SIZE];
|
||
|
unsigned char pathbuf[SC_MAX_PATH_SIZE], *path = pathbuf;
|
||
|
int r, pathlen, pathtype;
|
||
|
struct sc_file *file = NULL;
|
||
|
cac_private_data_t * priv = CAC_DATA(card);
|
||
|
|
||
|
assert(card != NULL && in_path != NULL);
|
||
|
ctx = card->ctx;
|
||
|
|
||
|
SC_FUNC_CALLED(ctx, SC_LOG_DEBUG_VERBOSE);
|
||
|
|
||
|
memcpy(path, in_path->value, in_path->len);
|
||
|
pathlen = in_path->len;
|
||
|
pathtype = in_path->type;
|
||
|
|
||
|
sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE,"path->aid=%x %x %x %x %x %x %x len=%d, path->value = %x %x %x %x len=%d path->type=%d (%x)",
|
||
|
in_path->aid.value[0], in_path->aid.value[1], in_path->aid.value[2], in_path->aid.value[3],
|
||
|
in_path->aid.value[4], in_path->aid.value[5], in_path->aid.value[6], in_path->aid.len,
|
||
|
in_path->value[0], in_path->value[1], in_path->value[2], in_path->value[3], in_path->len,
|
||
|
in_path->type, in_path->type);
|
||
|
sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE,"file_out=%lx index=%d count=%d\n",(unsigned long) file_out,
|
||
|
in_path->index, in_path->count);
|
||
|
|
||
|
/* Sigh, sc_key_select expects paths to keys to have specific formats. There is no override.
|
||
|
* we have to add some bytes to the path to make it happy. A better fix would be to give sc_key_file
|
||
|
* a flag that says 'no, really this path is fine'. We only need to do this for private keys */
|
||
|
if ((pathlen > 2) && (pathlen <= 4) && memcmp(path, "\x3F\x00", 2) == 0) {
|
||
|
if (pathlen > 2) {
|
||
|
path += 2;
|
||
|
pathlen -= 2;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/* CAC has multiple different type of objects that aren't PKCS #15. When we read
|
||
|
* them we need convert them to something PKCS #15 would understand. Find the object
|
||
|
* and object type here:
|
||
|
*/
|
||
|
if (priv) { /* don't record anything if we haven't been initialized yet */
|
||
|
priv->object_type = CAC_OBJECT_TYPE_TLV_FILE;
|
||
|
if (cac_is_cert(priv, in_path)) {
|
||
|
priv->object_type = CAC_OBJECT_TYPE_CERT;
|
||
|
}
|
||
|
/* forget any old cacheed values */
|
||
|
if (priv->cache_buf) {
|
||
|
free(priv->cache_buf);
|
||
|
priv->cache_buf = NULL;
|
||
|
}
|
||
|
priv->cache_buf_len = 0;
|
||
|
priv->cached = 0;
|
||
|
}
|
||
|
|
||
|
if (in_path->aid.len) {
|
||
|
if (!pathlen) {
|
||
|
memcpy(path, in_path->aid.value, in_path->aid.len);
|
||
|
pathlen = in_path->aid.len;
|
||
|
pathtype = SC_PATH_TYPE_DF_NAME;
|
||
|
} else {
|
||
|
/* First, select the application */
|
||
|
sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE,"select application" );
|
||
|
sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0xA4, 4, 0);
|
||
|
apdu.data = in_path->aid.value;
|
||
|
apdu.datalen = in_path->aid.len;
|
||
|
apdu.lc = in_path->aid.len;
|
||
|
|
||
|
r = sc_transmit_apdu(card, &apdu);
|
||
|
LOG_TEST_RET(ctx, r, "APDU transmit failed");
|
||
|
r = sc_check_sw(card, apdu.sw1, apdu.sw2);
|
||
|
if (r)
|
||
|
LOG_FUNC_RETURN(ctx, r);
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
sc_format_apdu(card, &apdu, SC_APDU_CASE_4_SHORT, 0xA4, 0, 0);
|
||
|
|
||
|
switch (pathtype) {
|
||
|
/* ideally we would had SC_PATH_TYPE_OBJECT_ID and add code to the iso7816 select.
|
||
|
* Unfortunately we'd also need to update the caching code as well. For now just
|
||
|
* use FILE_ID and change p1 here */
|
||
|
case SC_PATH_TYPE_FILE_ID:
|
||
|
apdu.p1 = 2;
|
||
|
if (pathlen != 2)
|
||
|
return SC_ERROR_INVALID_ARGUMENTS;
|
||
|
break;
|
||
|
case SC_PATH_TYPE_DF_NAME:
|
||
|
apdu.p1 = 4;
|
||
|
break;
|
||
|
default:
|
||
|
LOG_FUNC_RETURN(ctx, SC_ERROR_INVALID_ARGUMENTS);
|
||
|
}
|
||
|
apdu.lc = pathlen;
|
||
|
apdu.data = path;
|
||
|
apdu.datalen = pathlen;
|
||
|
apdu.resp = buf;
|
||
|
apdu.resplen = sizeof(buf);
|
||
|
apdu.le = sc_get_max_recv_size(card) < 256 ? sc_get_max_recv_size(card) : 256;
|
||
|
|
||
|
if (file_out != NULL) {
|
||
|
apdu.p2 = 0; /* first record, return FCI */
|
||
|
}
|
||
|
else {
|
||
|
apdu.p2 = (type == SC_CARD_TYPE_CAC_I)? 0x00 : 0x0C;
|
||
|
}
|
||
|
|
||
|
r = sc_transmit_apdu(card, &apdu);
|
||
|
LOG_TEST_RET(ctx, r, "APDU transmit failed");
|
||
|
if (file_out == NULL) {
|
||
|
/* For some cards 'SELECT' can be only with request to return FCI/FCP. */
|
||
|
r = sc_check_sw(card, apdu.sw1, apdu.sw2);
|
||
|
if (apdu.sw1 == 0x6A && apdu.sw2 == 0x86) {
|
||
|
apdu.p2 = 0x00;
|
||
|
if (sc_transmit_apdu(card, &apdu) == SC_SUCCESS)
|
||
|
r = sc_check_sw(card, apdu.sw1, apdu.sw2);
|
||
|
}
|
||
|
if (apdu.sw1 == 0x61)
|
||
|
LOG_FUNC_RETURN(ctx, SC_SUCCESS);
|
||
|
LOG_FUNC_RETURN(ctx, r);
|
||
|
}
|
||
|
|
||
|
r = sc_check_sw(card, apdu.sw1, apdu.sw2);
|
||
|
if (r)
|
||
|
LOG_FUNC_RETURN(ctx, r);
|
||
|
|
||
|
/* CAC cards enver return FCI, fake one */
|
||
|
file = sc_file_new();
|
||
|
if (file == NULL)
|
||
|
LOG_FUNC_RETURN(ctx, SC_ERROR_OUT_OF_MEMORY);
|
||
|
file->path = *in_path;
|
||
|
file->size = CAC_MAX_SIZE; /* we don't know how big, just give a large size until we can read the file */
|
||
|
|
||
|
*file_out = file;
|
||
|
SC_FUNC_RETURN(ctx, SC_LOG_DEBUG_NORMAL, SC_SUCCESS);
|
||
|
|
||
|
}
|
||
|
|
||
|
static int cac_select_file(sc_card_t *card, const sc_path_t *in_path, sc_file_t **file_out)
|
||
|
{
|
||
|
return cac_select_file_by_type(card, in_path, file_out, card->type);
|
||
|
}
|
||
|
|
||
|
static int cac_finish(sc_card_t *card)
|
||
|
{
|
||
|
cac_private_data_t * priv = CAC_DATA(card);
|
||
|
|
||
|
SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);
|
||
|
if (priv) {
|
||
|
cac_free_private_data(priv);
|
||
|
}
|
||
|
return SC_SUCCESS;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* select the Card Capabilities Container on CAC-2 */
|
||
|
static int cac_select_CCC(sc_card_t *card)
|
||
|
{
|
||
|
return cac_select_file_by_type(card, &cac_CCC_Path, NULL, SC_CARD_TYPE_CAC_II);
|
||
|
}
|
||
|
|
||
|
static int cac_path_from_cardurl(sc_card_t *card, sc_path_t *path, cac_card_url_t *val, int len)
|
||
|
{
|
||
|
if (len < 10) {
|
||
|
return SC_ERROR_INVALID_DATA;
|
||
|
}
|
||
|
sc_mem_clear(path, sizeof(sc_path_t));
|
||
|
memcpy(path->aid.value, &val->rid, sizeof(val->rid));
|
||
|
memcpy(&path->aid.value[5], &val->applicationID, sizeof(val->applicationID));
|
||
|
path->aid.len = sizeof(val->rid) + sizeof(val->applicationID);
|
||
|
memcpy(path->value, &val->objectID, sizeof(val->objectID));
|
||
|
path->len = sizeof(val->objectID);
|
||
|
path->type = SC_PATH_TYPE_FILE_ID;
|
||
|
sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE,"path->aid=%x %x %x %x %x %x %x len=%d, path->value = %x %x len=%d path->type=%d (%x)",
|
||
|
path->aid.value[0], path->aid.value[1], path->aid.value[2], path->aid.value[3],
|
||
|
path->aid.value[4], path->aid.value[5], path->aid.value[6],
|
||
|
path->aid.len, path->value[0], path->value[1], path->len, path->type, path->type);
|
||
|
sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE,"rid=%x %x %x %x %x len=%d appid= %x %x len=%d objid= %x %x len=%d",
|
||
|
val->rid[0], val->rid[1], val->rid[2], val->rid[3], val->rid[4], sizeof(val->rid),
|
||
|
val->applicationID[0], val->applicationID[1], sizeof(val->applicationID),
|
||
|
val->objectID[0], val->objectID[1], sizeof(val->objectID));
|
||
|
|
||
|
return SC_SUCCESS;
|
||
|
}
|
||
|
|
||
|
static int cac_parse_cardurl(sc_card_t *card, cac_private_data_t *priv, cac_card_url_t *val, int len)
|
||
|
{
|
||
|
cac_object_t new_object;
|
||
|
const cac_object_t *obj;
|
||
|
unsigned short object_id;
|
||
|
int r;
|
||
|
|
||
|
r = cac_path_from_cardurl(card, &new_object.path, val, len);
|
||
|
if (r != SC_SUCCESS) {
|
||
|
return r;
|
||
|
}
|
||
|
switch (val->cardApplicationType) {
|
||
|
case CAC_APP_TYPE_PKI:
|
||
|
/* we don't want to overflow the cac_label array. This test could
|
||
|
* go way if we create a label function that will create a unique label
|
||
|
* from a cert index.
|
||
|
*/
|
||
|
if (priv->cert_next >= MAX_CAC_SLOTS)
|
||
|
break; /* don't fail just because we have more certs than we can support */
|
||
|
new_object.name = cac_labels[priv->cert_next];
|
||
|
new_object.fd = priv->cert_next+1;
|
||
|
sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE,"CARDURL: pki_object found, cert_next=%d (%s),", priv->cert_next, new_object.name);
|
||
|
cac_add_object_to_list(&priv->pki_list, &new_object);
|
||
|
priv->cert_next++;
|
||
|
break;
|
||
|
case CAC_APP_TYPE_GENERAL:
|
||
|
object_id = bebytes2ushort(val->objectID);
|
||
|
obj = cac_find_obj_by_id(object_id);
|
||
|
if (obj == NULL)
|
||
|
break; /* don't fail just because we don't recognize the object */
|
||
|
new_object.name = obj->name;
|
||
|
new_object.fd = 0;
|
||
|
sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE,"CARDURL: gen_object found, objectID=%x (%s),", object_id, new_object.name);
|
||
|
cac_add_object_to_list(&priv->general_list, &new_object);
|
||
|
break;
|
||
|
case CAC_APP_TYPE_SKI:
|
||
|
sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE,"CARDURL: ski_object found");
|
||
|
break;
|
||
|
default:
|
||
|
sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE,"CARDURL: unkown object_object found (type=0x%x)", val->cardApplicationType);
|
||
|
/* don't fail just because there is an unknown object in the CCC */
|
||
|
break;
|
||
|
}
|
||
|
return SC_SUCCESS;
|
||
|
}
|
||
|
|
||
|
static int cac_parse_cuid(sc_card_t *card, cac_private_data_t *priv, cac_cuid_t *val, size_t len)
|
||
|
{
|
||
|
size_t card_id_len;
|
||
|
|
||
|
if (len < sizeof(cac_cuid_t)) {
|
||
|
return SC_ERROR_INVALID_DATA;
|
||
|
}
|
||
|
|
||
|
sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "gsc_rid=%s", sc_dump_hex(val->gsc_rid, sizeof(val->gsc_rid)));
|
||
|
sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "manufacture id=%x", val->manufacturer_id);
|
||
|
sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "cac_type=%d", val->card_type);
|
||
|
card_id_len = len - (&val->card_id - (u8 *)val);
|
||
|
sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "card_id=%s (%d)",sc_dump_hex(&val->card_id, card_id_len),card_id_len);
|
||
|
priv->cuid = *val;
|
||
|
priv->cac_id = malloc(card_id_len);
|
||
|
if (priv->cac_id == NULL) {
|
||
|
return SC_ERROR_OUT_OF_MEMORY;
|
||
|
}
|
||
|
memcpy(priv->cac_id, &val->card_id, card_id_len);
|
||
|
priv->cac_id_len = card_id_len;
|
||
|
return SC_SUCCESS;
|
||
|
}
|
||
|
static int cac_process_CCC(sc_card_t *card, cac_private_data_t *priv);
|
||
|
|
||
|
static int cac_parse_CCC(sc_card_t *card, cac_private_data_t *priv, u8 *tl,
|
||
|
size_t tl_len, u8 *val, size_t val_len)
|
||
|
{
|
||
|
size_t len = 0;
|
||
|
u8 *tl_end = tl + tl_len;
|
||
|
u8 *val_end = val + val_len;
|
||
|
sc_path_t new_path;
|
||
|
int r;
|
||
|
|
||
|
|
||
|
for (; (tl < tl_end) && (val< val_end); val += len) {
|
||
|
/* get the tag and the length */
|
||
|
u8 tag;
|
||
|
if (sc_simpletlv_read_tag(&tl, tl_end - tl, &tag, &len) != SC_SUCCESS)
|
||
|
break;
|
||
|
switch (tag) {
|
||
|
case CAC_TAG_CUID:
|
||
|
sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE,"TAG:CUID");
|
||
|
r = cac_parse_cuid(card, priv, (cac_cuid_t *)val, len);
|
||
|
if (r < 0)
|
||
|
return r;
|
||
|
break;
|
||
|
case CAC_TAG_CC_VERSION_NUMBER:
|
||
|
case CAC_TAG_GRAMMAR_VERION_NUMBER:
|
||
|
/* ignore the version numbers for now */
|
||
|
sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE,"TAG:Version");
|
||
|
break;
|
||
|
case CAC_TAG_CARDURL:
|
||
|
sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE,"TAG:CARDURL");
|
||
|
r = cac_parse_cardurl(card, priv, (cac_card_url_t *)val, len);
|
||
|
if (r < 0)
|
||
|
return r;
|
||
|
break;
|
||
|
/*
|
||
|
* The following are really for file systems cards. This code only cares about CAC VM cards
|
||
|
*/
|
||
|
case CAC_TAG_PKCS15:
|
||
|
/* should verify that this is '0'. If it's not zero, we should drop out of here and
|
||
|
* let the PKCS 15 code handle this card */
|
||
|
sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE,"TAG:PKCS5");
|
||
|
break;
|
||
|
case CAC_TAG_DATA_MODEL:
|
||
|
case CAC_TAG_CARD_APDU:
|
||
|
case CAC_TAG_CAPABILITY_TUPLES:
|
||
|
case CAC_TAG_STATUS_TUPLES:
|
||
|
case CAC_TAG_REDIRECTION:
|
||
|
case CAC_TAG_ERROR_CODES:
|
||
|
sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE,"TAG:FSSpecific(0x%x)", tag);
|
||
|
break;
|
||
|
case CAC_TAG_ACCESS_CONTROL:
|
||
|
/* handle access control later */
|
||
|
sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE,"TAG:ACCESS Control");
|
||
|
break;
|
||
|
case CAC_TAG_NEXT_CCC:
|
||
|
sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE,"TAG:NEXT CCC");
|
||
|
r = cac_path_from_cardurl(card, &new_path, (cac_card_url_t *)val, len);
|
||
|
if (r < 0)
|
||
|
return r;
|
||
|
|
||
|
r = cac_select_file_by_type(card, &new_path, NULL, SC_CARD_TYPE_CAC_II);
|
||
|
if (r < 0)
|
||
|
return r;
|
||
|
|
||
|
r = cac_process_CCC(card, priv);
|
||
|
if (r < 0)
|
||
|
return r;
|
||
|
break;
|
||
|
default:
|
||
|
/* ignore tags we don't understand */
|
||
|
sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE,"TAG:Unknown (0x%x)",tag );
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
return SC_SUCCESS;
|
||
|
}
|
||
|
|
||
|
static int cac_process_CCC(sc_card_t *card, cac_private_data_t *priv)
|
||
|
{
|
||
|
u8 *tl = NULL, *val = NULL;
|
||
|
size_t tl_len, val_len;
|
||
|
int r;
|
||
|
|
||
|
|
||
|
r = cac_read_file(card, CAC_FILE_TAG, &tl, &tl_len);
|
||
|
if (r < 0)
|
||
|
goto done;
|
||
|
|
||
|
r = cac_read_file(card, CAC_FILE_VALUE, &val, &val_len);
|
||
|
if (r < 0)
|
||
|
goto done;
|
||
|
|
||
|
r = cac_parse_CCC(card, priv, tl, tl_len, val, val_len);
|
||
|
done:
|
||
|
if (tl)
|
||
|
free(tl);
|
||
|
if (val)
|
||
|
free(val);
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
/* select a CAC-1 pki applet by index */
|
||
|
static int cac_select_pki_applet(sc_card_t *card, int index)
|
||
|
{
|
||
|
sc_path_t applet_path = cac_cac1_pki_obj.path;
|
||
|
applet_path.aid.value[applet_path.aid.len-1] = index;
|
||
|
return cac_select_file_by_type(card, &applet_path, NULL, SC_CARD_TYPE_CAC_I);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Find the first existing CAC-1 applet. If none found, then this isn't a CAC-1
|
||
|
*/
|
||
|
static int cac_find_first_pki_applet(sc_card_t *card, int *index_out)
|
||
|
{
|
||
|
int r, i;
|
||
|
for (i=0; i < MAX_CAC_SLOTS; i++) {
|
||
|
r = cac_select_pki_applet(card, i);
|
||
|
if (r == SC_SUCCESS) {
|
||
|
*index_out = i;
|
||
|
return r;
|
||
|
}
|
||
|
}
|
||
|
return SC_ERROR_OBJECT_NOT_FOUND;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CAC-1 has been found, identify all the certs and general containers.
|
||
|
* This emulates CAC-2's CCC.
|
||
|
*/
|
||
|
static int cac_populate_cac_1(sc_card_t *card, int index, cac_private_data_t *priv)
|
||
|
{
|
||
|
int r, i;
|
||
|
cac_object_t pki_obj = cac_cac1_pki_obj;
|
||
|
u8 buf[100];
|
||
|
u8 *val;
|
||
|
size_t val_len;
|
||
|
|
||
|
/* populate PKI objects */
|
||
|
for (i=index; i < MAX_CAC_SLOTS; i++) {
|
||
|
r = cac_select_pki_applet(card, i);
|
||
|
if (r == SC_SUCCESS) {
|
||
|
pki_obj.name = cac_labels[i];
|
||
|
sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE,"CAC1: pki_object found, cert_next=%d (%s),", i, pki_obj.name);
|
||
|
pki_obj.path.aid.value[pki_obj.path.aid.len-1] = i;
|
||
|
pki_obj.fd = i+1; /* don't use id of zero */
|
||
|
cac_add_object_to_list(&priv->pki_list, &pki_obj);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* populate non-PKI objects */
|
||
|
for (i=0; i < cac_1_object_count; i++) {
|
||
|
r = cac_select_file_by_type(card, &cac_1_objects[i].path, NULL, SC_CARD_TYPE_CAC_I);
|
||
|
if (r == SC_SUCCESS) {
|
||
|
sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE,"CAC1: obj_object found, cert_next=%d (%s),", i, cac_1_objects[i].name);
|
||
|
cac_add_object_to_list(&priv->general_list, &cac_1_objects[i]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* create a cuid to simulate the cac 2 cuid.
|
||
|
*/
|
||
|
priv->cuid = cac_cac1_cuid;
|
||
|
/* create a serial number by hashing the first 100 bytes of the
|
||
|
* first certificate on the card */
|
||
|
r = cac_select_pki_applet(card, index);
|
||
|
if (r < 0) {
|
||
|
return r; /* shouldn't happen unless the card has been removed or is malfunctioning */
|
||
|
}
|
||
|
val = buf;
|
||
|
val_len = sizeof(buf);
|
||
|
r = cac_cac1_get_certificate(card, &val, &val_len);
|
||
|
if (r >= 0) {
|
||
|
priv->cac_id = malloc(20);
|
||
|
if (priv->cac_id == NULL) {
|
||
|
return SC_ERROR_OUT_OF_MEMORY;
|
||
|
}
|
||
|
#ifdef ENABLE_OPENSSL
|
||
|
SHA1(val, val_len, priv->cac_id);
|
||
|
priv->cac_id_len = 20;
|
||
|
#else
|
||
|
sc_log(card->ctx, "OpenSSL Required");
|
||
|
return SC_ERROR_NOT_SUPPORTED;
|
||
|
#endif /* ENABLE_OPENSSL */
|
||
|
}
|
||
|
return SC_SUCCESS;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Look for a CAC card. If it exists, initialize our data structures
|
||
|
*/
|
||
|
static int cac_find_and_initialize(sc_card_t *card, int initialize)
|
||
|
{
|
||
|
int r, index;
|
||
|
cac_private_data_t *priv = NULL;
|
||
|
|
||
|
/* already initialized? */
|
||
|
if (card->drv_data) {
|
||
|
return SC_SUCCESS;
|
||
|
}
|
||
|
|
||
|
/* is this a CAC-2 specified in NIST Interagency Report 6887 -
|
||
|
* "Government Smart Card Interoperability Specification v2.1 July 2003" */
|
||
|
r = cac_select_CCC(card);
|
||
|
if (r == SC_SUCCESS) {
|
||
|
sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "CCC found, is CAC-2");
|
||
|
if (!initialize) /* match card only */
|
||
|
return r;
|
||
|
|
||
|
priv = cac_new_private_data();
|
||
|
r = cac_process_CCC(card, priv);
|
||
|
if (r == SC_SUCCESS) {
|
||
|
card->type = SC_CARD_TYPE_CAC_II;
|
||
|
card->drv_data = priv;
|
||
|
return r;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* is this a CAC-1 specified in DoD "CAC Applet Developer Guide" version 1.0 September 2002 */
|
||
|
r = cac_find_first_pki_applet(card, &index);
|
||
|
if (r == SC_SUCCESS) {
|
||
|
sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "applet found, is CAC-1");
|
||
|
if (!initialize) /* match card only */
|
||
|
return r;
|
||
|
|
||
|
if (!priv) {
|
||
|
priv = cac_new_private_data();
|
||
|
}
|
||
|
r = cac_populate_cac_1(card, index, priv);
|
||
|
if (r == SC_SUCCESS) {
|
||
|
card->type = SC_CARD_TYPE_CAC_I;
|
||
|
card->drv_data = priv;
|
||
|
return r;
|
||
|
}
|
||
|
}
|
||
|
if (priv) {
|
||
|
cac_free_private_data(priv);
|
||
|
}
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* NOTE: returns a bool, 1 card matches, 0 it does not */
|
||
|
static int cac_match_card(sc_card_t *card)
|
||
|
{
|
||
|
int r;
|
||
|
SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);
|
||
|
/* Since we send an APDU, the card's logout function may be called...
|
||
|
* however it may be in dirty memory */
|
||
|
card->ops->logout = NULL;
|
||
|
|
||
|
r = cac_find_and_initialize(card, 0);
|
||
|
return (r == SC_SUCCESS); /* never match */
|
||
|
}
|
||
|
|
||
|
|
||
|
static int cac_init(sc_card_t *card)
|
||
|
{
|
||
|
int r;
|
||
|
unsigned long flags;
|
||
|
|
||
|
SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);
|
||
|
|
||
|
r = cac_find_and_initialize(card, 1);
|
||
|
if (r < 0) {
|
||
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_NORMAL, r);
|
||
|
}
|
||
|
flags = SC_ALGORITHM_RSA_RAW;
|
||
|
|
||
|
_sc_card_add_rsa_alg(card, 1024, flags, 0); /* manditory */
|
||
|
_sc_card_add_rsa_alg(card, 2048, flags, 0); /* optional */
|
||
|
_sc_card_add_rsa_alg(card, 3072, flags, 0); /* optional */
|
||
|
|
||
|
card->caps |= SC_CARD_CAP_RNG | SC_CARD_CAP_ISO7816_PIN_INFO;
|
||
|
|
||
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_NORMAL, SC_SUCCESS);
|
||
|
}
|
||
|
|
||
|
static int cac_pin_cmd(sc_card_t *card, struct sc_pin_cmd_data *data, int *tries_left)
|
||
|
{
|
||
|
/* CAC, like PIV needs Extra validation of (new) PIN during
|
||
|
* a PIN change request, to ensure it's not outside the
|
||
|
* FIPS 201 4.1.6.1 (numeric only) and * FIPS 140-2
|
||
|
* (6 character minimum) requirements.
|
||
|
*/
|
||
|
struct sc_card_driver *iso_drv = sc_get_iso7816_driver();
|
||
|
|
||
|
if (data->cmd == SC_PIN_CMD_CHANGE) {
|
||
|
int i = 0;
|
||
|
if (data->pin2.len < 6) {
|
||
|
return SC_ERROR_INVALID_PIN_LENGTH;
|
||
|
}
|
||
|
for(i=0; i < data->pin2.len; ++i) {
|
||
|
if (!isdigit(data->pin2.data[i])) {
|
||
|
return SC_ERROR_INVALID_DATA;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return iso_drv->ops->pin_cmd(card, data, tries_left);
|
||
|
}
|
||
|
|
||
|
static struct sc_card_operations cac_ops;
|
||
|
|
||
|
static struct sc_card_driver cac_drv = {
|
||
|
"Common Access Card (CAC)",
|
||
|
"cac",
|
||
|
&cac_ops,
|
||
|
NULL, 0, NULL
|
||
|
};
|
||
|
|
||
|
static struct sc_card_driver * sc_get_driver(void)
|
||
|
{
|
||
|
struct sc_card_driver *iso_drv = sc_get_iso7816_driver();
|
||
|
|
||
|
cac_ops = *iso_drv->ops;
|
||
|
cac_ops.match_card = cac_match_card;
|
||
|
cac_ops.init = cac_init;
|
||
|
cac_ops.finish = cac_finish;
|
||
|
|
||
|
cac_ops.select_file = cac_select_file; /* need to record object type */
|
||
|
cac_ops.get_challenge = cac_get_challenge;
|
||
|
cac_ops.read_binary = cac_read_binary;
|
||
|
cac_ops.write_binary = cac_write_binary;
|
||
|
cac_ops.set_security_env = cac_set_security_env;
|
||
|
cac_ops.restore_security_env = cac_restore_security_env;
|
||
|
cac_ops.compute_signature = cac_compute_signature;
|
||
|
cac_ops.decipher = cac_decipher;
|
||
|
cac_ops.card_ctl = cac_card_ctl;
|
||
|
cac_ops.pin_cmd = cac_pin_cmd;
|
||
|
|
||
|
return &cac_drv;
|
||
|
}
|
||
|
|
||
|
|
||
|
struct sc_card_driver * sc_get_cac_driver(void)
|
||
|
{
|
||
|
return sc_get_driver();
|
||
|
}
|