root/trunk/src/libopensc/card-openpgp.c

Revision 3149, 16.4 KB (checked in by nils, 20 months ago)

request at most for 256 bytes

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1/*
2 * card-openpgp.c: Support for OpenPGP card
3 *
4 * Copyright (C) 2003  Olaf Kirch <okir@suse.de>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19 */
20
21#include "internal.h"
22#include "asn1.h"
23#include "cardctl.h"
24#include <stdlib.h>
25#include <string.h>
26#include <ctype.h>
27
28static struct sc_atr_table pgp_atrs[] = {
29        { "3b:fa:13:00:ff:81:31:80:45:00:31:c1:73:c0:01:00:00:90:00:b1", NULL, NULL, SC_CARD_TYPE_OPENPGP_GENERIC, 0, NULL },
30        { NULL, NULL, NULL, 0, 0, NULL }
31};
32
33static struct sc_card_operations *iso_ops;
34static struct sc_card_operations pgp_ops;
35static struct sc_card_driver pgp_drv = {
36        "OpenPGP card",
37        "openpgp",
38        &pgp_ops,
39        NULL, 0, NULL
40};
41
42/*
43 * The OpenPGP card doesn't have a file system, instead everything
44 * is stored in data objects that are accessed through GET/PUT.
45 *
46 * However, much inside OpenSC's pkcs15 implementation is based on
47 * the assumption that we have a file system. So we fake one here.
48 *
49 * Selecting the MF causes us to select the OpenPGP AID.
50 *
51 * Everything else is mapped to "file" IDs.
52 */
53struct blob {
54        struct blob *   next;
55        struct blob *   parent;
56        struct do_info *info;
57
58        sc_file_t *     file;
59        unsigned int    id;
60        int             status;
61
62        unsigned char * data;
63        unsigned int    len;
64        struct blob *   files;
65};
66
67struct do_info {
68        unsigned int    id;
69        unsigned int    constructed : 1;
70        unsigned int    size;
71        int             (*get_fn)(sc_card_t *, unsigned int, u8 *, size_t);
72        int             (*put_fn)(sc_card_t *, unsigned int, const u8 *, size_t);
73};
74
75static struct blob *    pgp_new_blob(struct blob *, unsigned int, int,
76                                struct do_info *);
77static int              pgp_get_pubkey(sc_card_t *, unsigned int,
78                                u8 *, size_t);
79static int              pgp_get_pubkey_pem(sc_card_t *, unsigned int,
80                                u8 *, size_t);
81
82static struct do_info           pgp_objects[] = {
83      { 0x004f,         0, 0,   sc_get_data,    sc_put_data     },
84      { 0x005e,         1, 0,   sc_get_data,    sc_put_data     },
85      { 0x0065,         1, 0,   sc_get_data,    sc_put_data     },
86      { 0x006e,         1, 0,   sc_get_data,    sc_put_data     },
87      { 0x0073,         1, 0,   sc_get_data,    sc_put_data     },
88      { 0x007a,         1, 0,   sc_get_data,    sc_put_data     },
89      { 0x5f50,         0, 0,   sc_get_data,    sc_put_data     },
90      { 0xb600,         1, 0,   pgp_get_pubkey, NULL            },
91      { 0xb800,         1, 0,   pgp_get_pubkey, NULL            },
92      { 0xa400,         1, 0,   pgp_get_pubkey, NULL            },
93      { 0xb601,         0, 0,   pgp_get_pubkey_pem,NULL         },
94      { 0xb801,         0, 0,   pgp_get_pubkey_pem,NULL         },
95      { 0xa401,         0, 0,   pgp_get_pubkey_pem,NULL         },
96
97      { 0, 0, 0, NULL, NULL },
98};
99
100#define DRVDATA(card)        ((struct pgp_priv_data *) ((card)->drv_data))
101struct pgp_priv_data {
102        struct blob             mf;
103        struct blob *           current;
104
105        sc_security_env_t       sec_env;
106};
107
108
109static int
110pgp_match_card(sc_card_t *card)
111{
112        int i;
113
114        i = _sc_match_atr(card, pgp_atrs, &card->type);
115        if (i < 0)
116                return 0;
117        return 1;
118}
119
120static int
121pgp_init(sc_card_t *card)
122{
123        struct pgp_priv_data *priv;
124        unsigned long   flags;
125        sc_path_t       aid;
126        sc_file_t       *file = NULL;
127        struct do_info  *info;
128        int             r;
129
130        priv = (struct pgp_priv_data *) calloc (1, sizeof *priv);
131        if (!priv)
132                return SC_ERROR_OUT_OF_MEMORY;
133        card->name = "OpenPGP";
134        card->drv_data = priv;
135        card->cla = 0x00;
136
137        /* Is this correct? */
138        flags = SC_ALGORITHM_RSA_RAW;
139        flags |= SC_ALGORITHM_RSA_PAD_PKCS1;
140        flags |= SC_ALGORITHM_RSA_HASH_NONE;
141
142        /* Is this correct? */
143        _sc_card_add_rsa_alg(card, 512, flags, 0);
144        _sc_card_add_rsa_alg(card, 768, flags, 0);
145        _sc_card_add_rsa_alg(card, 1024, flags, 0);
146
147        sc_format_path("D276:0001:2401", &aid);
148        aid.type = SC_PATH_TYPE_DF_NAME;
149
150        if ((r = iso_ops->select_file(card, &aid, &file)) < 0)
151                return r;
152
153        sc_format_path("3f00", &file->path);
154        file->type = SC_FILE_TYPE_DF;
155        file->id = 0x3f00;
156
157        priv->mf.file = file;
158        priv->mf.id = 0x3F00;
159
160        priv->current = &priv->mf;
161
162        /* Populate MF - add all blobs listed in the pgp_objects
163         * table. */
164        for (info = pgp_objects; info->id > 0; info++) {
165                pgp_new_blob(&priv->mf, info->id,
166                                info->constructed? SC_FILE_TYPE_DF
167                                                 : SC_FILE_TYPE_WORKING_EF,
168                                info);
169        }
170        return 0;
171}
172
173static int
174pgp_finish(sc_card_t *card)
175{
176        struct pgp_priv_data *priv;
177
178        if (card == NULL)
179                return 0;
180        priv = DRVDATA (card);
181
182        /* XXX delete fake file hierarchy */
183
184        free(priv);
185        return 0;
186}
187
188static int
189pgp_set_blob(struct blob *blob, const u8 *data, size_t len)
190{
191        if (blob->data)
192                free(blob->data);
193        blob->len    = len;
194        blob->status = 0;
195        blob->data   = (unsigned char *) malloc(len);
196        memcpy(blob->data, data, len);
197
198        blob->file->size = len;
199        return 0;
200}
201
202static struct blob *
203pgp_new_blob(struct blob *parent, unsigned int file_id,
204                int file_type, struct do_info *info)
205{
206        sc_file_t       *file = sc_file_new();
207        struct blob     *blob, **p;
208
209        blob = (struct blob *) calloc(1, sizeof(*blob));
210        blob->parent = parent;
211        blob->id     = file_id;
212        blob->file   = file;
213        blob->info   = info;
214
215        file->type   = file_type;
216        file->path   = parent->file->path;
217        file->ef_structure = SC_FILE_EF_TRANSPARENT;
218        sc_append_file_id(&file->path, file_id);
219
220        for (p = &parent->files; *p; p = &(*p)->next)
221                ;
222        *p = blob;
223
224        return blob;
225}
226
227static int
228pgp_read_blob(sc_card_t *card, struct blob *blob)
229{
230        unsigned char   buffer[256];
231        int             r;
232
233        if (blob->data != NULL)
234                return 0;
235        if (blob->info == NULL)
236                return blob->status;
237
238        sc_ctx_suppress_errors_on(card->ctx);
239        r = blob->info->get_fn(card, blob->id, buffer, sizeof(buffer));
240        sc_ctx_suppress_errors_off(card->ctx);
241
242        if (r < 0) {
243                blob->status = r;
244                return r;
245        }
246
247        return pgp_set_blob(blob, buffer, r);
248}
249
250/*
251 * Enumerate contents of a data blob.
252 * The OpenPGP card has a funny TLV encoding.
253 */
254static int
255pgp_enumerate_blob(sc_card_t *card, struct blob *blob)
256{
257        const u8        *in, *end;
258        int             r;
259
260        if (blob->files != NULL)
261                return 0;
262
263        if ((r = pgp_read_blob(card, blob)) < 0)
264                return r;
265
266        in = blob->data;
267        end = blob->data + blob->len;
268        while (in < end) {
269                unsigned int    tag, len, type = SC_FILE_TYPE_WORKING_EF;
270                unsigned char   c;
271
272                c = *in++;
273                if (c == 0x00 || c == 0xFF)
274                        continue;
275
276                tag = c;
277                if (tag & 0x20)
278                        type = SC_FILE_TYPE_DF;
279                while ((c & 0x1f) == 0x1f) {
280                        if (in >= end)
281                                goto eoc;
282                        c = *in++;
283                        tag = (tag << 8) | c;
284                }
285
286                if (in >= end)
287                        goto eoc;
288                c = *in++;
289                if (c < 0x80) {
290                        len = c;
291                } else {
292                        len = 0;
293                        c &= 0x7F;
294                        while (c--) {
295                                if (in >= end)
296                                        goto eoc;
297                                len = (len << 8) | *in++;
298                        }
299                }
300
301                /* Don't search past end of content */
302                if (in + len > end)
303                        goto eoc;
304
305                pgp_set_blob(pgp_new_blob(blob, tag, type, NULL), in, len);
306                in += len;
307        }
308
309        return 0;
310
311eoc:    sc_error(card->ctx, "Unexpected end of contents\n");
312        return SC_ERROR_OBJECT_NOT_VALID;
313}
314
315static int
316pgp_get_blob(sc_card_t *card, struct blob *blob, unsigned int id,
317                struct blob **ret)
318{
319        struct blob             *child;
320        int                     r;
321
322        if ((r = pgp_enumerate_blob(card, blob)) < 0)
323                return r;
324
325        for (child = blob->files; child; child = child->next) {
326                if (child->id == id)
327                        break;
328        }
329
330        if (child != NULL) {
331                (void) pgp_read_blob(card, child);
332                *ret = child;
333                return 0;
334        }
335
336        return SC_ERROR_FILE_NOT_FOUND;
337}
338
339static int
340pgp_select_file(sc_card_t *card, const sc_path_t *path, sc_file_t **ret)
341{
342        struct pgp_priv_data *priv = DRVDATA(card);
343        struct blob     *blob;
344        sc_path_t       path_copy;
345        unsigned int    n;
346        int             r;
347
348        if (path->type == SC_PATH_TYPE_DF_NAME)
349                return iso_ops->select_file(card, path, ret);
350        if (path->type != SC_PATH_TYPE_PATH)
351                return SC_ERROR_INVALID_ARGUMENTS;
352        if (path->len < 2 || (path->len & 1))
353                return SC_ERROR_INVALID_ARGUMENTS;
354        if (!memcmp(path->value, "\x3f\x00", 2)) {
355                memcpy(path_copy.value, path->value + 2, path->len - 2);
356                path_copy.len = path->len - 2;
357                path = &path_copy;
358        }
359
360        blob = &priv->mf;
361        for (n = 0; n < path->len; n += 2) {
362                r = pgp_get_blob(card, blob,
363                                (path->value[n] << 8) | path->value[n+1],
364                                &blob);
365                if (r < 0) {
366                        priv->current = NULL;
367                        return r;
368                }
369        }
370
371        priv->current = blob;
372
373        if (ret)
374                sc_file_dup(ret, blob->file);
375        return 0;
376}
377
378static int
379pgp_list_files(sc_card_t *card, u8 *buf, size_t buflen)
380{
381        struct pgp_priv_data *priv = DRVDATA(card);
382        struct blob     *blob;
383        unsigned int    k;
384        int             r;
385
386        blob = priv->current;
387        if (blob->file->type != SC_FILE_TYPE_DF)
388                return SC_ERROR_OBJECT_NOT_VALID;
389
390        if ((r = pgp_enumerate_blob(card, blob)) < 0)
391                return r;
392
393        for (k = 0, blob = blob->files; blob; blob = blob->next) {
394                if (k + 2 > buflen)
395                        break;
396                buf[k++] = blob->id >> 8;
397                buf[k++] = blob->id;
398        }
399
400        return k;
401}
402
403static int
404pgp_read_binary(sc_card_t *card, unsigned int idx,
405                u8 *buf, size_t count, unsigned long flags)
406{
407        struct pgp_priv_data *priv = DRVDATA(card);
408        struct blob     *blob;
409        int             r;
410
411        if ((blob = priv->current) == NULL)
412                return SC_ERROR_FILE_NOT_FOUND;
413
414        if (blob->file->type != SC_FILE_TYPE_WORKING_EF)
415                return SC_ERROR_FILE_NOT_FOUND;
416
417        if ((r = pgp_read_blob(card, blob)) < 0)
418                return r;
419
420        if (idx > blob->len)
421                return SC_ERROR_INCORRECT_PARAMETERS;
422
423        if (idx + count > blob->len)
424                count = blob->len - idx;
425
426        memcpy(buf, blob->data + idx, count);
427        return count;
428}
429
430static int
431pgp_write_binary(sc_card_t *card, unsigned int idx,
432                const u8 *buf, size_t count, unsigned long flags)
433{
434        return SC_ERROR_NOT_SUPPORTED;
435}
436
437static int
438pgp_get_pubkey(sc_card_t *card, unsigned int tag, u8 *buf, size_t buf_len)
439{
440        sc_apdu_t       apdu;
441        u8              idbuf[2];
442        int             r;
443
444        sc_debug(card->ctx, "called, tag=%04x\n", tag);
445
446        idbuf[0] = tag >> 8;
447        idbuf[1] = tag;
448
449        sc_format_apdu(card, &apdu, SC_APDU_CASE_4_SHORT, 0x47, 0x81, 0);
450        apdu.lc = 2;
451        apdu.data = idbuf;
452        apdu.datalen = 2;
453        apdu.le = (buf_len > 256)? 256 : buf_len;
454        apdu.resp = buf;
455        apdu.resplen = buf_len;
456
457        r = sc_transmit_apdu(card, &apdu);
458        SC_TEST_RET(card->ctx, r, "APDU transmit failed");
459        r = sc_check_sw(card, apdu.sw1, apdu.sw2);
460        SC_TEST_RET(card->ctx, r, "Card returned error");
461
462        return apdu.resplen;
463}
464
465static int
466pgp_get_pubkey_pem(sc_card_t *card, unsigned int tag, u8 *buf, size_t buf_len)
467{
468        struct pgp_priv_data *priv = DRVDATA(card);
469        struct blob     *blob, *mod_blob, *exp_blob;
470        sc_pkcs15_pubkey_t pubkey;
471        u8              *data;
472        size_t          len;
473        int             r;
474
475        sc_debug(card->ctx, "called, tag=%04x\n", tag);
476       
477        if ((r = pgp_get_blob(card, &priv->mf, tag & 0xFFFE, &blob)) < 0
478         || (r = pgp_get_blob(card, blob, 0x7F49, &blob)) < 0
479         || (r = pgp_get_blob(card, blob, 0x0081, &mod_blob)) < 0
480         || (r = pgp_get_blob(card, blob, 0x0082, &exp_blob)) < 0
481         || (r = pgp_read_blob(card, mod_blob)) < 0
482         || (r = pgp_read_blob(card, exp_blob)) < 0)
483                return r;
484
485        memset(&pubkey, 0, sizeof(pubkey));
486        pubkey.algorithm = SC_ALGORITHM_RSA;
487        pubkey.u.rsa.modulus.data  = mod_blob->data;
488        pubkey.u.rsa.modulus.len   = mod_blob->len;
489        pubkey.u.rsa.exponent.data = exp_blob->data;
490        pubkey.u.rsa.exponent.len  = exp_blob->len;
491
492        if ((r = sc_pkcs15_encode_pubkey(card->ctx, &pubkey, &data, &len)) < 0)
493                return r;
494
495        if (len > buf_len)
496                len = buf_len;
497        memcpy(buf, data, len);
498        free(data);
499        return len;
500}
501
502static int
503pgp_get_data(sc_card_t *card, unsigned int tag, u8 *buf, size_t buf_len)
504{
505        sc_apdu_t       apdu;
506        int             r;
507
508        sc_format_apdu(card, &apdu, SC_APDU_CASE_2_SHORT,
509                                0xCA, tag >> 8, tag);
510        apdu.le = (buf_len <= 255)? buf_len : 256;
511        apdu.resp = buf;
512        apdu.resplen = buf_len;
513
514        r = sc_transmit_apdu(card, &apdu);
515        SC_TEST_RET(card->ctx, r, "APDU transmit failed");
516        r = sc_check_sw(card, apdu.sw1, apdu.sw2);
517        SC_TEST_RET(card->ctx, r, "Card returned error");
518
519        return apdu.resplen;
520}
521
522static int
523pgp_put_data(sc_card_t *card, unsigned int tag, const u8 *buf, size_t buf_len)
524{
525        return SC_ERROR_NOT_SUPPORTED;
526}
527
528static int
529pgp_pin_cmd(sc_card_t *card, struct sc_pin_cmd_data *data, int *tries_left)
530{
531        if (data->pin_type != SC_AC_CHV)
532                return SC_ERROR_INVALID_ARGUMENTS;
533
534        data->pin_reference |= 0x80;
535
536        return iso_ops->pin_cmd(card, data, tries_left);
537}
538
539static int
540pgp_set_security_env(sc_card_t *card,
541                const sc_security_env_t *env, int se_num)
542{
543        struct pgp_priv_data *priv = DRVDATA(card);
544
545        if (env->flags & SC_SEC_ENV_ALG_PRESENT) {
546                if (env->algorithm != SC_ALGORITHM_RSA)
547                        return SC_ERROR_INVALID_ARGUMENTS;
548        }
549        if (!(env->flags & SC_SEC_ENV_KEY_REF_PRESENT)
550         || env->key_ref_len != 1)
551                return SC_ERROR_INVALID_ARGUMENTS;
552        if (env->flags & SC_SEC_ENV_FILE_REF_PRESENT)
553                return SC_ERROR_INVALID_ARGUMENTS;
554
555        switch (env->operation) {
556        case SC_SEC_OPERATION_SIGN:
557                if (env->key_ref[0] != 0x00
558                 && env->key_ref[0] != 0x02) {
559                        sc_error(card->ctx,
560                                "Key reference not compatible with "
561                                "requested usage\n");
562                        return SC_ERROR_NOT_SUPPORTED;
563                }
564                break;
565        case SC_SEC_OPERATION_DECIPHER:
566                if (env->key_ref[0] != 0x01) {
567                        sc_error(card->ctx,
568                                "Key reference not compatible with "
569                                "requested usage\n");
570                        return SC_ERROR_NOT_SUPPORTED;
571                }
572                break;
573        default:
574                return SC_ERROR_INVALID_ARGUMENTS;
575        }
576
577        priv->sec_env = *env;
578        return 0;
579}
580
581static int
582pgp_compute_signature(sc_card_t *card, const u8 *data,
583                size_t data_len, u8 * out, size_t outlen)
584{
585        struct pgp_priv_data    *priv = DRVDATA(card);
586        sc_security_env_t       *env = &priv->sec_env;
587        sc_apdu_t               apdu;
588        int                     r;
589
590        if (env->operation != SC_SEC_OPERATION_SIGN)
591                return SC_ERROR_INVALID_ARGUMENTS;
592
593        switch (env->key_ref[0]) {
594        case 0x00: /* signature key */
595                /* PSO SIGNATURE */
596                sc_format_apdu(card, &apdu, SC_APDU_CASE_4_SHORT,
597                                0x2A, 0x9E, 0x9A);
598                break;
599        case 0x02: /* authentication key */
600                /* INTERNAL AUTHENTICATE */
601                sc_format_apdu(card, &apdu, SC_APDU_CASE_4_SHORT,
602                                0x88, 0, 0);
603                break;
604        case 0x01:
605                sc_error(card->ctx,
606                        "Invalid key reference (decipher only key)\n");
607                return SC_ERROR_INVALID_ARGUMENTS;
608        default:
609                sc_error(card->ctx, "Invalid key reference 0x%02x\n",
610                                env->key_ref[0]);
611                return SC_ERROR_INVALID_ARGUMENTS;
612        }
613
614        apdu.lc = data_len;
615        apdu.data = data;
616        apdu.datalen = data_len;
617        apdu.le      = outlen > 256 ? 256 : outlen;
618        apdu.resp    = out;
619        apdu.resplen = outlen;
620
621        r = sc_transmit_apdu(card, &apdu);
622        SC_TEST_RET(card->ctx, r, "APDU transmit failed");
623        r = sc_check_sw(card, apdu.sw1, apdu.sw2);
624        SC_TEST_RET(card->ctx, r, "Card returned error");
625
626        return apdu.resplen;
627}
628
629static int
630pgp_decipher(sc_card_t *card, const u8 *in, size_t inlen,
631                u8 *out, size_t outlen)
632{
633        struct pgp_priv_data    *priv = DRVDATA(card);
634        sc_security_env_t       *env = &priv->sec_env;
635        sc_apdu_t       apdu;
636        u8              *temp = NULL;
637        int             r;
638
639        /* There's some funny padding indicator that must be
640         * prepended... hmm. */
641        if (!(temp = (u8 *) malloc(inlen + 1)))
642                return SC_ERROR_OUT_OF_MEMORY;
643        temp[0] = '\0';
644        memcpy(temp + 1, in, inlen);
645        in = temp;
646        inlen += 1;
647
648        if (env->operation != SC_SEC_OPERATION_DECIPHER) {
649                free(temp);
650                return SC_ERROR_INVALID_ARGUMENTS;
651        }
652
653        switch (env->key_ref[0]) {
654        case 0x01: /* Decryption key */
655                /* PSO DECIPHER */
656                sc_format_apdu(card, &apdu, SC_APDU_CASE_4_SHORT,
657                                0x2A, 0x80, 0x86);
658                break;
659        case 0x00: /* signature key */
660        case 0x02: /* authentication key */
661                sc_error(card->ctx,
662                        "Invalid key reference (signature only key)\n");
663                free(temp);
664                return SC_ERROR_INVALID_ARGUMENTS;
665        default:
666                sc_error(card->ctx, "Invalid key reference 0x%02x\n",
667                                env->key_ref[0]);
668                free(temp);
669                return SC_ERROR_INVALID_ARGUMENTS;
670        }
671
672        apdu.lc = inlen;
673        apdu.data = in;
674        apdu.datalen = inlen;
675        apdu.le = 256;
676        apdu.resp = out;
677        apdu.resplen = outlen;
678
679        r = sc_transmit_apdu(card, &apdu);
680        free(temp);
681
682        SC_TEST_RET(card->ctx, r, "APDU transmit failed");
683        r = sc_check_sw(card, apdu.sw1, apdu.sw2);
684        SC_TEST_RET(card->ctx, r, "Card returned error");
685
686        return apdu.resplen;
687}
688
689/* Driver binding stuff */
690static struct sc_card_driver *
691sc_get_driver(void)
692{
693        struct sc_card_driver *iso_drv = sc_get_iso7816_driver();
694
695        iso_ops = iso_drv->ops;
696
697        pgp_ops = *iso_ops;
698        pgp_ops.match_card      = pgp_match_card;
699        pgp_ops.init            = pgp_init;
700        pgp_ops.finish          = pgp_finish;
701        pgp_ops.select_file     = pgp_select_file;
702        pgp_ops.list_files      = pgp_list_files;
703        pgp_ops.read_binary     = pgp_read_binary;
704        pgp_ops.write_binary    = pgp_write_binary;
705        pgp_ops.pin_cmd         = pgp_pin_cmd;
706        pgp_ops.get_data        = pgp_get_data;
707        pgp_ops.put_data        = pgp_put_data;
708        pgp_ops.set_security_env= pgp_set_security_env;
709        pgp_ops.compute_signature= pgp_compute_signature;
710        pgp_ops.decipher        = pgp_decipher;
711
712        return &pgp_drv;
713}
714
715struct sc_card_driver *
716sc_get_openpgp_driver(void)
717{
718        return sc_get_driver();
719}
Note: See TracBrowser for help on using the browser.