diff --git a/client/Makefile b/client/Makefile index 5f565ec..908e830 100644 --- a/client/Makefile +++ b/client/Makefile @@ -22,10 +22,10 @@ CFLAGS ?= -O2 -g COMMONCFLAGS=$(CFLAGS) -Wall -Wextra -std=c99 -pedantic -Wno-unused-parameter -CCFLAGS=$(COMMONCFLAGS) -I../npapi/include `pkg-config --cflags 'gtk+-2.0 >= 2.12' gdk-2.0 glib-2.0 openssl` -DGTK_DISABLE_DEPRECATED=1 -DGDK_DISABLE_DEPRECATED=1 -DG_DISABLE_DEPRECATED=1 -DGSEAL_ENABLE +CCFLAGS=$(COMMONCFLAGS) -I../npapi/include `pkg-config --cflags 'gtk+-2.0 >= 2.12' gdk-2.0 glib-2.0 libp11 openssl` -DGTK_DISABLE_DEPRECATED=1 -DGDK_DISABLE_DEPRECATED=1 -DG_DISABLE_DEPRECATED=1 -DGSEAL_ENABLE # You may have to add -lpthread after $(LDFLAGS) on OpenBSD LINKFLAGS=$(CFLAGS) $(LDFLAGS) -Wl,--as-needed -LIBS=`pkg-config --libs 'gtk+-2.0 >= 2.12' gdk-2.0 glib-2.0 gthread-2.0 openssl` +LIBS=`pkg-config --libs 'gtk+-2.0 >= 2.12' gdk-2.0 glib-2.0 gthread-2.0 libp11 openssl` # Files to be installed LIB_PATH=`../configure --internal--get-define=LIB_PATH` @@ -34,7 +34,7 @@ UI_PATH=`../configure --internal--get-define=UI_PATH` SIGNING_EXECUTABLE=`../configure --internal--get-define=SIGNING_EXECUTABLE` UI_GTK_XML=`../configure --internal--get-define=UI_GTK_XML` -OBJECTS=backend.o bankid.o pkcs12.o main.o misc.o pipe.o posix.o glibconfig.o gtk.o xmldsig.o secmem.o +OBJECTS=backend.o bankid.o pkcs11.o pkcs12.o main.o misc.o pipe.o posix.o glibconfig.o gtk.o xmldsig.o secmem.o all: sign @@ -44,6 +44,7 @@ glibconfig.o: platform.h gtk.o: ../common/biderror.h backend.h bankid.h platform.h misc.h main.o: ../common/biderror.h bankid.h misc.h secmem.h platform.h ../common/pipe.h misc.o: misc.h +pkcs11.o: backend.h backend_private.h misc.h pkcs12.o: backend.h backend_private.h misc.h pipe.o: ../common/pipe.h ../common/pipe.c posix.o: platform.h diff --git a/client/backend.c b/client/backend.c index fa9b2ad..51f1220 100644 --- a/client/backend.c +++ b/client/backend.c @@ -33,9 +33,9 @@ // Available backends +Backend *pkcs11_getBackend(); Backend *pkcs12_getBackend(); - static void addBackend(BackendNotifier *notifier, Backend *backend) { if (backend == NULL) return; @@ -65,6 +65,7 @@ BackendNotifier *backend_createNotifier(const char *subjectFilter, // Add all backends addBackend(notifier, pkcs12_getBackend()); + addBackend(notifier, pkcs11_getBackend()); return notifier; } @@ -101,6 +102,19 @@ TokenError backend_addFile(BackendNotifier *notifier, } /** + * Scan backends for tokens + */ +void backend_scanTokens(BackendNotifier *notifier) +{ + for (size_t i = 0; i < notifier->backendCount; i++) { + Backend *backend = notifier->backends[i]; + if (backend->scan) { + backend->scan(backend); + } + } +} + +/** * Gets the status of a token. */ TokenStatus token_getStatus(const Token *token) { diff --git a/client/backend.h b/client/backend.h index 26b5fcb..af2c1b3 100644 --- a/client/backend.h +++ b/client/backend.h @@ -74,6 +74,8 @@ BackendNotifier *backend_createNotifier(const char *subjectFilter, BackendNotifyFunction notifyFunction); void backend_freeNotifier(BackendNotifier *notifier); +void backend_scanTokens(); + /* Function to manually add files */ TokenError backend_addFile(BackendNotifier *notifier, const char *file, size_t length, void *tag); diff --git a/client/backend_private.h b/client/backend_private.h index f7e2eff..a9a9b40 100644 --- a/client/backend_private.h +++ b/client/backend_private.h @@ -32,9 +32,14 @@ #ifndef TokenType #define TokenType Token #endif +#ifndef BackendPrivateType + #define BackendPrivateType void +#endif + typedef struct _Backend Backend; struct _Backend { + BackendPrivateType *private; const BackendNotifier *notifier; /* Initialization and de-initialization */ @@ -47,6 +52,11 @@ struct _Backend { void (*freeToken)(TokenType *token); /** + * Scan tokens provided by this backend + */ + void (*scan)(Backend *backend); + + /** * Manually adds a file to the backend. May be NULL if not applicable */ TokenError (*addFile)(Backend *backend, const char *data, size_t length, diff --git a/client/bankid.c b/client/bankid.c index a8ef9bb..1a8285f 100644 --- a/client/bankid.c +++ b/client/bankid.c @@ -40,7 +40,7 @@ static const char defaultEmulatedVersion[] = EMULATED_VERSION; #define EXPIRY_RAND (rand() % 65535) -#define DEFAULT_EXPIRY (RELEASE_TIME + 30*24*3600) +#define DEFAULT_EXPIRY (RELEASE_TIME + 6*30*24*3600) /** * Returns the version string. The version string is identical to that of @@ -49,8 +49,20 @@ static const char defaultEmulatedVersion[] = EMULATED_VERSION; */ static char *getVersionString() { static const char template[] = - "Personal=%1$s&libCardSiemens_so=%1$s&libBranding_so=%1$s&libP11_so=%1$s&libtokenapi_so=%1$s&libCardSetec_so=%1$s&libCardPrisma_so=%1$s&libplugins_so=%1$s&libai_so=%1$s&personal_bin=%1$s&" - "platform=linux&distribution=ubuntu&os_version=unknown&best_before=%2$" PRId64 "&"; + "Personal=%1$s&" + "libai_so=%1$s&" + "libP11_so=%1$s&" + "libplugins_so=%1$s&" + "libtokenapi_so=%1$s&" + "libCardSiemens_so=%1$s&" + "libCardSetec_so=%1$s&" + "libCardPrisma_so=%1$s&" + "libBranding_so=%1$s&" + "personal_bin=%1$s&" + /* TODO: This should be generated from list of smartcards */ + "SmartCard_Reader=Handelsbanken card reader [MCI_OSR_0205] 00 00&" + "platform=linux&distribution=unknown&os_version=unknown&" + "best_before=%2$" PRId64 "&"; long lexpiry; int64_t expiry; diff --git a/client/main.c b/client/main.c index 72421c2..1b140a2 100644 --- a/client/main.c +++ b/client/main.c @@ -140,6 +140,7 @@ void pipeData() { notifyCallback); platform_setNotifier(notifier); platform_addKeyDirectories(); + backend_scanTokens(notifier); free(decodedSubjectFilter); if (message != NULL) { diff --git a/client/pkcs11.c b/client/pkcs11.c new file mode 100644 index 0000000..f107939 --- /dev/null +++ b/client/pkcs11.c @@ -0,0 +1,373 @@ +/* + + Copyright (c) 2009-2010 Samuel Lidén Borell + Copyright (c) 2010 Marcus Carlson + Copyright (c) 2010 Henrik Nordström + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +*/ + +#define _BSD_SOURCE 1 + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +typedef struct _PKCS11Token PKCS11Token; +typedef struct _PKCS11Private PKCS11Private; +#define TokenType PKCS11Token +#define BackendPrivateType PKCS11Private + +#include "../common/defines.h" +#include "misc.h" +#include "backend_private.h" + +typedef struct { + int refCount; + PKCS11_SLOT *slot; + PKCS11_CERT *certs; + unsigned int ncerts; +} SharedPKCS11; + +struct _PKCS11Token { + Token base; + SharedPKCS11 *sharedP11; + int index; +}; + +struct _PKCS11Private { + PKCS11_CTX *ctx; + unsigned int nslots; + PKCS11_SLOT *slots; +}; + +/** + * Releases a shared PKCS11 object. If nobody else is using the object then + * it's freed. + */ +static void pkcs11_release(SharedPKCS11 *sharedP11) { + if (--sharedP11->refCount == 0) { + // We're the last reference holder to release the P11 + // TODO: Do we need to free the certs somehow? + free(sharedP11); + } +} + +/** + * Returns the BASE64-encoded DER representation of a certificate. + */ +static char *der_encode(X509 *cert) { + unsigned char *der = NULL; + char *base64 = NULL; + int len; + + len = i2d_X509(cert, &der); + if (!der) return NULL; + base64 = base64_encode((const char*)der, len); + free(der); + return base64; +} + +/** + * Returns true if a certificate supports the given key usage (such as + * authentication or signing). + */ +static bool has_keyusage(X509 *cert, KeyUsage keyUsage) { + static const int openSSLUsages[] = { + X509v3_KU_KEY_CERT_SIGN, // KeyUsage_Issuing + X509v3_KU_NON_REPUDIATION, // KeyUsage_Signing + X509v3_KU_DIGITAL_SIGNATURE, // KeyUsage_Authentication + }; + ASN1_BIT_STRING *usage; + bool supported = false; + + usage = X509_get_ext_d2i(cert, NID_key_usage, NULL, NULL); + if (usage) { + supported = (usage->length > 0) && + ((usage->data[0] & openSSLUsages[keyUsage]) == openSSLUsages[keyUsage]); + ASN1_BIT_STRING_free(usage); + } + return supported; +} + +/** + * Gets a property of an X509_NAME, such as a subject name (NID_commonName), + */ +static char *getNamePropertyByNID(X509_NAME *name, int nid) { + char *text; + int length; + + length = X509_NAME_get_text_by_NID(name, nid, NULL, 0); + if (length < 0) return NULL; + + text = malloc(length+1); + text[0] = '\0'; // if the function would fail + X509_NAME_get_text_by_NID(name, nid, text, length+1); + return text; +} + +static bool matchSubjectFilter(const Backend *backend, X509_NAME *name) { + const char *subjectFilter = backend->notifier->subjectFilter; + if (!subjectFilter) return true; + + // TODO use OBJ_txt2nid and support arbitrary OIDs? + if ((strncmp(subjectFilter, "2.5.4.5=", 8) != 0) || + (strchr(subjectFilter, ',') != NULL)) { + // OID 2.5.4.5 (Serial number) is the only supported/allowed filter + return true; // Nothing to filter with + } + + const char *wantedSerial = subjectFilter + 8; + + char *actualSerial = getNamePropertyByNID(name, NID_serialNumber); + + bool ok = !strcmp(actualSerial, wantedSerial); + free(actualSerial); + return ok; +} + +/** + * Creates a PKCS11 Token structure. + */ +static PKCS11Token *createToken(const Backend *backend, SharedPKCS11 *sharedP11, + X509_NAME *id, void *tag, int index) { + PKCS11Token *token = calloc(1, sizeof(PKCS11Token)); + if (!token) return NULL; + token->base.backend = backend; + token->base.status = TokenStatus_NeedPassword; + token->base.displayName = getNamePropertyByNID(id, NID_commonName); + token->base.tag = tag; + token->index = index; + token->sharedP11 = sharedP11; + sharedP11->refCount++; + return token; +} + +static void _backend_freeToken(PKCS11Token *token) { + pkcs11_release(token->sharedP11); + free(token); +} + +static X509 *getCert(const SharedPKCS11 *SharedP11, int index) +{ + return SharedP11->certs[index].x509; +} + +static X509 *findCert(const SharedPKCS11 *SharedP11, + const X509_NAME *name, + const KeyUsage keyUsage) { + for (unsigned int i = 0; i < SharedP11->ncerts; i++) { + X509 *cert = getCert(SharedP11, i); + if (!X509_NAME_cmp(X509_get_subject_name(cert), name) && + has_keyusage(cert, keyUsage)) { + return cert; + } + } + return NULL; +} + +/** + * Returns a list of DER-BASE64 encoded certificates, from the subject + * to the root CA. This is actually wrong, since the root CA that's + * returned could be untrusted. However, at least my P12 has only one + * possible chain and the validation is done server-side, so this shouldn't + * be a problem. + */ +static TokenError _backend_getBase64Chain(const PKCS11Token *token, + char ***certs, size_t *count) { + + X509 *cert = getCert(token->sharedP11, token->index); + if (!cert) { + return TokenError_Unknown; + } + + *count = 1; + *certs = malloc(sizeof(char*)); + (*certs)[0] = der_encode(cert); + + X509_NAME *issuer = X509_get_issuer_name(cert); + while (issuer != NULL) { + cert = findCert(token->sharedP11, issuer, KeyUsage_Issuing); + if (!cert) break; + + issuer = X509_get_issuer_name(cert); + (*count)++; + *certs = realloc(*certs, *count * sizeof(char*)); + (*certs)[*count-1] = der_encode(cert); + } + + return TokenError_Success; +} + +#ifndef SHA1_LENGTH +#define SHA1_LENGTH 20 +#endif +static TokenError _backend_sign(PKCS11Token *token, + const char *message, size_t messagelen, + char **signature, size_t *siglen) { + + assert(message != NULL); + assert(signature != NULL); + assert(siglen != NULL); + + if (messagelen >= UINT_MAX) return TokenError_Unknown; + + if (token->sharedP11->slot->token->loginRequired) { + //TODO: token->base.password + if (PKCS11_login(token->sharedP11->slot, 0, NULL) != 0) + return TokenError_BadPassword; + } + + // Find the key for the token + PKCS11_CERT *cert = &token->sharedP11->certs[token->index]; + PKCS11_KEY *key = PKCS11_find_key(cert); + + if (!key) return TokenError_BadPassword; + + // Sign with the default crypto with SHA1 + unsigned char shasum[SHA1_LENGTH]; + SHA1((unsigned char*)message, messagelen, shasum); + unsigned int sigLen = 256; + *signature = malloc(sigLen); + int rc = PKCS11_sign(NID_sha1, shasum, SHA1_LENGTH, (unsigned char*)*signature, &sigLen, key); + *siglen = sigLen; + if (rc != 1) { + free(*signature); + *signature = NULL; + return TokenError_Unknown; + } + return TokenError_Success; +} + +/** + * Adds all subjects in a PKCS11 card and notifies the frontend of them. + */ +static void pkcs11_load_certs(Backend *backend, SharedPKCS11 *sharedP11) { + for (unsigned int i = 0; i < sharedP11->ncerts; i++) { + X509 *x = getCert(sharedP11, i); + + if (!has_keyusage(x, backend->notifier->keyUsage)) continue; + + X509_NAME *id = X509_get_subject_name(x); + if (!matchSubjectFilter(backend, id)) continue; + + PKCS11Token *token = createToken(backend, sharedP11, id, sharedP11->slot->description, i); + if (token) { + backend->notifier->notifyFunction((Token*)token, TokenChange_Added); + } + } +} + + +/** + * Scans a card and returns a parsed representation of the contents, with + * a reference count so it can be shared by multiple tokens. + */ +static void pkcs11_scan_card(Backend *backend, PKCS11_SLOT *slot) { + int rc; + unsigned int ncerts = 0; + PKCS11_CERT *certs; + + if (!slot->token) + return; + + // Scan card + rc = PKCS11_enumerate_certs(slot->token, &certs, &ncerts); + if (ncerts == 0) + return; + + // Create a reference counted object + SharedPKCS11 *sharedP11 = malloc(sizeof(SharedPKCS11)); + if (!sharedP11) { + // TODO: DO we need to free the certs somehow? + return; + } + sharedP11->refCount = 1; + sharedP11->slot = slot; + sharedP11->certs = certs; + sharedP11->ncerts = ncerts; + pkcs11_load_certs(backend, sharedP11); + pkcs11_release(sharedP11); +} + +static void _backend_scan(Backend *backend) { + /* Load certs from all tokens */ + for (unsigned int i = 0; i < backend->private->nslots; i++) { + if (backend->private->slots[i].token) { + pkcs11_scan_card(backend, &backend->private->slots[i]); + } + } +} + +static bool _backend_init(Backend *backend) { + backend->private = calloc(1, sizeof(*backend->private)); + OpenSSL_add_all_algorithms(); + backend->private->ctx = PKCS11_CTX_new(); + + /* load pkcs #11 module */ + if (PKCS11_CTX_load(backend->private->ctx, "/usr/lib64/opensc-pkcs11.so") != 0) { //TODO build parameter + fprintf(stderr, "loading pkcs11 engine failed: %s\n", + ERR_reason_error_string(ERR_get_error())); + PKCS11_CTX_free(backend->private->ctx); + return false; + } + + /* get information on all slots */ + if (PKCS11_enumerate_slots(backend->private->ctx, &backend->private->slots, &backend->private->nslots) < 0) { + fprintf(stderr, "no slots available\n"); + PKCS11_CTX_free(backend->private->ctx); + return false; + } + + return true; +} + +static void _backend_free(Backend *backend) { + PKCS11_release_all_slots(backend->private->ctx, backend->private->slots, backend->private->nslots); + PKCS11_CTX_free(backend->private->ctx); + free(backend->private); + EVP_cleanup(); +} + +/* Backend functions */ +static const Backend backend_template = { + .init = _backend_init, + .scan = _backend_scan, + .free = _backend_free, + .freeToken = _backend_freeToken, + .getBase64Chain = _backend_getBase64Chain, + .sign = _backend_sign, +}; + +Backend *pkcs11_getBackend() { + Backend *backend = malloc(sizeof(Backend)); + memcpy(backend, &backend_template, sizeof(Backend)); + return backend; +} + +