Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for multiple on-card-applications #53

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 23 additions & 13 deletions OpenSCToken/Token.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,33 +22,43 @@

#include "libopensc/pkcs15.h"

/* in sync with SC_PKCS11_FRAMEWORK_DATA_MAX_NUM */
#define SC_TOKEN_APPS_MAX_NUM 4
struct token_apps {
struct sc_pkcs15_card * _Nullable p15card[SC_TOKEN_APPS_MAX_NUM];
};

#define TYPE_CERT 0x01
#define TYPE_PRIV 0x02
#define TYPE_AUTH 0x03

static NSData* _Nullable idToData(u8 type, struct sc_pkcs15_id * _Nullable p15id)
/* The converted data consists of app_index+type+p15id. This allows identifying which on-card-application has this object if there are multiple applications. And this allows differentiating different types of objects with the same p15id. */
static NSData* _Nullable idToData(u8 index, u8 type, struct sc_pkcs15_id * _Nullable p15id)
{
NSData *data = nil;
if (p15id) {
u8 *p = malloc(p15id->len+1);
u8 *p = malloc(p15id->len+2);
if (p) {
*p = type;
memcpy(p+1, p15id->value, p15id->len);
data = [NSData dataWithBytes:p length:p15id->len+1];
p[0] = index;
p[1] = type;
memcpy(&p[2], p15id->value, p15id->len);
data = [NSData dataWithBytes:p length:p15id->len+2];
free(p);
}
}
return data;
}

static struct sc_pkcs15_id dataToId(NSData* _Nonnull data)
static struct sc_pkcs15_id dataToId(NSData* _Nonnull data, u8 * _Nullable index)
{
struct sc_pkcs15_id p15id;
p15id.len = [data length];
memcpy(p15id.value, [data bytes], p15id.len);
if (p15id.len > 0) {
p15id.len--;
memmove(p15id.value, p15id.value+1, p15id.len);
struct sc_pkcs15_id p15id = {0};
size_t data_len = [data length];
const unsigned char *p = [data bytes];
if (data_len > 1 && p) {
if (index)
*index = p[0];
memcpy(p15id.value, &p[2], data_len-2);
p15id.len = data_len-2;
}
return p15id;
}
Expand Down Expand Up @@ -78,7 +88,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (readonly) OpenSCTokenDriver *driver;
@property (nonatomic, assign, nullable) struct sc_context *ctx;
@property (nonatomic, assign, nullable) struct sc_card *card;
@property (nonatomic, assign, nullable) struct sc_pkcs15_card *p15card;
@property (nonatomic, assign) struct token_apps apps;

@end

Expand Down
206 changes: 113 additions & 93 deletions OpenSCToken/Token.m
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,20 @@ - (nullable instancetype)initWithSmartCard:(TKSmartCard *)smartCard AID:(nullabl
struct sc_aid *aid = NULL;
struct sc_pkcs15_object *objs[32];
int r;
size_t i, cert_num;
NSMutableArray<TKTokenKeychainItem *> *items;
size_t i, app_index, cert_num;
NSMutableArray<TKTokenKeychainItem *> *items = [[NSMutableArray alloc] init];
NSString *instanceID = nil;
struct token_apps apps;

/* TODO: Move card and p15card to smartCard.context. smartCard.context would automatically be set to nil if the card gets reset or the state gets modified by a different session, see documentation for TKSmartCard */

self.card = NULL;
self.p15card = NULL;
self.ctx = NULL;
/* self.apps.p15card allows binding multiple on-card-application. index 0 always contains the "generic" app. */
for (app_index = 0; app_index < SC_TOKEN_APPS_MAX_NUM; app_index++) {
self.apps.p15card[app_index] = NULL;
apps.p15card[app_index] = NULL;
}

os_log(OS_LOG_DEFAULT, "%s", OPENSC_SCM_REVISION);

Expand Down Expand Up @@ -105,110 +110,123 @@ - (nullable instancetype)initWithSmartCard:(TKSmartCard *)smartCard AID:(nullabl

app_generic = sc_pkcs15_get_application_by_type(card, "generic");
aid = app_generic ? &app_generic->aid : NULL;
r = sc_pkcs15_bind(card, aid, &p15card);
LOG_TEST_GOTO_ERR(ctx, r, "sc_pkcs15_bind");

r = sc_pkcs15_get_objects(p15card, SC_PKCS15_TYPE_CERT_X509, objs, sizeof(objs)/(sizeof *objs));
LOG_TEST_GOTO_ERR(ctx, r, "sc_pkcs15_get_objects (SC_PKCS15_TYPE_CERT_X509)");
cert_num = (size_t) r;
items = [NSMutableArray arrayWithCapacity:cert_num];
for (i = 0; i < cert_num; i++) {
struct sc_pkcs15_cert_info *cert_info = objs[i]->data;
struct sc_pkcs15_cert *cert = NULL;
struct sc_pkcs15_object *prkey_obj = NULL;
int private_obj = objs[i]->flags & SC_PKCS15_CO_FLAG_PRIVATE;

r = sc_pkcs15_read_certificate(p15card, cert_info, private_obj, &cert);
if (r) {
sc_log(ctx, "sc_pkcs15_read_certificate: %s", sc_strerror(r));
continue;
}
NSData* certificateData = [NSData dataWithBytes:(const void *)cert->data.value length:sizeof(unsigned char)*cert->data.len];
NSData* certificateID = idToData(TYPE_CERT, &cert_info->id);
NSString *certificateName = [NSString stringWithUTF8String:objs[i]->label];
id certificate = CFBridgingRelease(SecCertificateCreateWithData(kCFAllocatorDefault, (CFDataRef)certificateData));
if (certificateData == nil || certificateID == nil || certificateName == nil || certificate == NULL) {
sc_pkcs15_free_certificate(cert);
continue;
}
TKTokenKeychainCertificate *certificateItem = [[TKTokenKeychainCertificate alloc] initWithCertificate:(__bridge SecCertificateRef)certificate objectID:certificateID];
if (certificateItem == nil) {
/* Bind all applications starting with the "generic" one */
for (app_index = 0; app_index < SC_TOKEN_APPS_MAX_NUM; app_index++) {
r = sc_pkcs15_bind(card, aid, &p15card);
apps.p15card[app_index] = p15card;
LOG_TEST_GOTO_ERR(ctx, r, "sc_pkcs15_bind");

r = sc_pkcs15_get_objects(p15card, SC_PKCS15_TYPE_CERT_X509, objs, sizeof(objs)/(sizeof *objs));
LOG_TEST_GOTO_ERR(ctx, r, "sc_pkcs15_get_objects (SC_PKCS15_TYPE_CERT_X509)");
cert_num = (size_t) r;
for (i = 0; i < cert_num; i++) {
struct sc_pkcs15_cert_info *cert_info = objs[i]->data;
struct sc_pkcs15_cert *cert = NULL;
struct sc_pkcs15_object *prkey_obj = NULL;
int private_obj = objs[i]->flags & SC_PKCS15_CO_FLAG_PRIVATE;

r = sc_pkcs15_read_certificate(p15card, cert_info, private_obj, &cert);
if (r) {
sc_log(ctx, "sc_pkcs15_read_certificate: %s", sc_strerror(r));
continue;
}
NSData* certificateData = [NSData dataWithBytes:(const void *)cert->data.value length:sizeof(unsigned char)*cert->data.len];
NSData* certificateID = idToData(app_index, TYPE_CERT, &cert_info->id);
NSString *certificateName = [NSString stringWithUTF8String:objs[i]->label];
id certificate = CFBridgingRelease(SecCertificateCreateWithData(kCFAllocatorDefault, (CFDataRef)certificateData));
if (certificateData == nil || certificateID == nil || certificateName == nil || certificate == NULL) {
sc_pkcs15_free_certificate(cert);
continue;
}
TKTokenKeychainCertificate *certificateItem = [[TKTokenKeychainCertificate alloc] initWithCertificate:(__bridge SecCertificateRef)certificate objectID:certificateID];
if (certificateItem == nil) {
sc_pkcs15_free_certificate(cert);
continue;
}
[certificateItem setName:certificateName];
[items addObject:certificateItem];
sc_pkcs15_free_certificate(cert);
continue;

// Create key item.
r = sc_pkcs15_find_prkey_by_id(p15card, &cert_info->id, &prkey_obj);
if (r) {
sc_log(ctx, "sc_pkcs15_find_prkey_by_id: %s", sc_strerror(r));
continue;
}
struct sc_pkcs15_prkey_info *prkey_info = (struct sc_pkcs15_prkey_info *) prkey_obj->data;
NSData* keyID = idToData(app_index, TYPE_PRIV, &prkey_info->id);
NSString *keyName = [NSString stringWithUTF8String:objs[i]->label];
TKTokenKeychainKey *keyItem = [[TKTokenKeychainKey alloc] initWithCertificate:(__bridge SecCertificateRef)certificate objectID:keyID];
if (keyID == nil || keyName == nil || keyItem == nil) {
continue;
}
[keyItem setName:keyName];

NSMutableDictionary<NSNumber *, TKTokenOperationConstraint> *constraints = [NSMutableDictionary dictionary];
TKTokenOperationConstraint constraint;
if (prkey_obj->auth_id.len == 0) {
/* true, indicating that the operation is always allowed, without any authentication necessary. */
constraint = @YES;
} else {
/* Any other property list compatible value defined by the implementation of the token extension. Any such constraint is required to stay constant for the entire lifetime of the token. */
constraint = idToData(app_index, TYPE_AUTH, &prkey_obj->auth_id);
}

if (USAGE_ANY_SIGN & prkey_info->usage) {
keyItem.canSign = YES;
constraints[@(TKTokenOperationSignData)] = constraint;
} else {
keyItem.canSign = NO;
}
keyItem.suitableForLogin = keyItem.canSign;

if (USAGE_ANY_DECIPHER & prkey_info->usage) {
keyItem.canDecrypt = YES;
constraints[@(TKTokenOperationDecryptData)] = constraint;
} else {
keyItem.canDecrypt = NO;
}

if (USAGE_ANY_AGREEMENT & prkey_info->usage) {
keyItem.canPerformKeyExchange = YES;
constraints[@(TKTokenOperationPerformKeyExchange)] = constraint;
} else {
keyItem.canPerformKeyExchange = NO;
}

keyItem.constraints = constraints;
[items addObject:keyItem];
}
[certificateItem setName:certificateName];
[items addObject:certificateItem];
sc_pkcs15_free_certificate(cert);

// Create key item.
r = sc_pkcs15_find_prkey_by_id(p15card, &cert_info->id, &prkey_obj);
if (r) {
sc_log(ctx, "sc_pkcs15_find_prkey_by_id: %s", sc_strerror(r));
continue;
}
struct sc_pkcs15_prkey_info *prkey_info = (struct sc_pkcs15_prkey_info *) prkey_obj->data;
NSData* keyID = idToData(TYPE_PRIV, &prkey_info->id);
NSString *keyName = [NSString stringWithUTF8String:objs[i]->label];
TKTokenKeychainKey *keyItem = [[TKTokenKeychainKey alloc] initWithCertificate:(__bridge SecCertificateRef)certificate objectID:keyID];
if (keyID == nil || keyName == nil || keyItem == nil) {
continue;
}
[keyItem setName:keyName];

NSMutableDictionary<NSNumber *, TKTokenOperationConstraint> *constraints = [NSMutableDictionary dictionary];
TKTokenOperationConstraint constraint;
if (prkey_obj->auth_id.len == 0) {
/* true, indicating that the operation is always allowed, without any authentication necessary. */
constraint = @YES;
} else {
/* Any other property list compatible value defined by the implementation of the token extension. Any such constraint is required to stay constant for the entire lifetime of the token. */
constraint = idToData(TYPE_AUTH, &prkey_obj->auth_id);
}

if (USAGE_ANY_SIGN & prkey_info->usage) {
keyItem.canSign = YES;
constraints[@(TKTokenOperationSignData)] = constraint;
} else {
keyItem.canSign = NO;
}
keyItem.suitableForLogin = keyItem.canSign;

if (USAGE_ANY_DECIPHER & prkey_info->usage) {
keyItem.canDecrypt = YES;
constraints[@(TKTokenOperationDecryptData)] = constraint;
} else {
keyItem.canDecrypt = NO;
}
/* some tokens do not report a serial number */
if (p15card->tokeninfo != NULL && p15card->tokeninfo->serial_number != NULL)
instanceID = [NSString stringWithUTF8String:p15card->tokeninfo->serial_number];
p15card->opts.use_pin_cache = 0;

if (USAGE_ANY_AGREEMENT & prkey_info->usage) {
keyItem.canPerformKeyExchange = YES;
constraints[@(TKTokenOperationPerformKeyExchange)] = constraint;
} else {
keyItem.canPerformKeyExchange = NO;
}

keyItem.constraints = constraints;
[items addObject:keyItem];
/* find the next application. Note that the "generic" application is at index 0, which means that the apps from card->app are taking the **subsequent** slots */
if (app_index >= card->app_count)
break;
aid = &card->app[app_index]->aid;
app_index++;
/* go to the beginning of the loop and try to bind the next application */
p15card = NULL;
}

/* some tokens do not report a serial number */
if (p15card->tokeninfo != NULL && p15card->tokeninfo->serial_number != NULL)
instanceID = [NSString stringWithUTF8String:p15card->tokeninfo->serial_number];
p15card->opts.use_pin_cache = 0;

if (self = [super initWithSmartCard:smartCard AID:AID instanceID:instanceID tokenDriver:tokenDriver]) {
[self.keychainContents fillWithItems:items];

self.ctx = ctx;
self.card = card;
self.p15card = p15card;
self.apps = apps;
} else {
goto err;
}

err:
if (!self.p15card && p15card)
sc_pkcs15_card_free(p15card);
for (app_index = 0; app_index < SC_TOKEN_APPS_MAX_NUM; app_index++) {
if (!self.apps.p15card[app_index] && apps.p15card[app_index])
sc_pkcs15_card_free(apps.p15card[app_index]);
}
if (!self.card && card)
sc_disconnect_card(card);
if (!self.ctx && ctx)
Expand All @@ -217,11 +235,13 @@ - (nullable instancetype)initWithSmartCard:(TKSmartCard *)smartCard AID:(nullabl
}

- (void)dealloc {
sc_pkcs15_card_free(self.p15card);
for (size_t app_index = 0; app_index < SC_TOKEN_APPS_MAX_NUM; app_index++) {
sc_pkcs15_card_free(self.apps.p15card[app_index]);
self.apps.p15card[app_index] = NULL;
}
sc_disconnect_card(self.card);
sc_release_context(self.ctx);
self.card = NULL;
self.p15card = NULL;
self.ctx = NULL;
}

Expand Down
Loading