diff --git a/src/cert-gpgsm.c b/src/cert-gpgsm.c index 6e6f5a5..c3d8e31 100644 --- a/src/cert-gpgsm.c +++ b/src/cert-gpgsm.c @@ -1,631 +1,633 @@ /* cert-gpgsm.c - Scute certificate searching. * Copyright (C) 2006, 2007 g10 Code GmbH * * This file is part of Scute. * * Scute 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. * * Scute 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 program; if not, see . * SPDX-License-Identifier: LGPL-2.1-or-later */ #if HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include "agent.h" #include "cert.h" #include "support.h" #include "debug.h" /* The maximum length of a key listing line. We take the double of * the allowed Assuan line length plus some extra space to avoid a * memmove after a part of a line has been processed. */ #define MAX_LINE_LEN (ASSUAN_LINELENGTH*2 + 200) struct keylist_ctx { /* The pending line in an active key listing. */ char pending[MAX_LINE_LEN + 1]; unsigned int pending_len; /* The current certificate. */ struct cert cert; /* The caller's search callback, invoked for each certificate. */ cert_search_cb_t search_cb; void *search_cb_hook; }; /* Support macros */ #define atoi_1(p) (*(p) - '0' ) #define atoi_2(p) ((atoi_1(p) * 10) + atoi_1((p)+1)) #define atoi_4(p) ((atoi_2(p) * 100) + atoi_2((p)+2)) /*** Local prototypes ***/ static gpg_error_t export_cert (const char *fpr, struct cert *cert); /* Release allocated storage for the certificate CERT and reset the certificate. */ static void cert_reset (struct cert *cert) { if (cert->issuer_serial) free (cert->issuer_serial); if (cert->issuer_name) free (cert->issuer_name); if (cert->uid) free (cert->uid); if (cert->cert_der) free (cert->cert_der); memset (cert, '\0', sizeof (struct cert)); } /* Parse the string TIMESTAMP into a time_t. The string may either be seconds since Epoch or in the ISO 8601 format like "20390815T143012". Returns 0 for an empty string or seconds since Epoch. Leading spaces are skipped. If ENDP is not NULL, it will point to the next non-parsed character in TIMESTRING. */ static time_t parse_timestamp (const char *timestamp, char **endp) { /* Need to skip leading spaces, because that is what strtoul does but not our ISO 8601 checking code. */ while (*timestamp && *timestamp== ' ') timestamp++; if (!*timestamp) return 0; if (strlen (timestamp) >= 15 && timestamp[8] == 'T') { struct tm buf; int year; year = atoi_4 (timestamp); if (year < 1900) return (time_t)(-1); /* Fixme: We would better use a configure test to see whether mktime can handle dates beyond 2038. */ if (sizeof (time_t) <= 4 && year >= 2038) return (time_t)2145914603; /* 2037-12-31 23:23:23 */ memset (&buf, 0, sizeof buf); buf.tm_year = year - 1900; buf.tm_mon = atoi_2 (timestamp+4) - 1; buf.tm_mday = atoi_2 (timestamp+6); buf.tm_hour = atoi_2 (timestamp+9); buf.tm_min = atoi_2 (timestamp+11); buf.tm_sec = atoi_2 (timestamp+13); if (endp) *endp = (char*)(timestamp + 15); #ifdef HAVE_TIMEGM return timegm (&buf); #else /* FIXME: Need to set TZ to UTC, but that is not thread-safe. */ return mktime (&buf); #endif } else return (time_t)strtoul (timestamp, endp, 10); } /* Decode the C formatted string SRC and store the result in the buffer *DESTP which is LEN bytes long. If LEN is zero, then a large enough buffer is allocated with malloc and *DESTP is set to the result. Currently, LEN is only used to specify if allocation is desired or not, the caller is expected to make sure that *DESTP is large enough if LEN is not zero. */ static gpg_error_t decode_c_string (const char *src, char **destp, size_t len) { char *dest; /* Set up the destination buffer. */ if (len) { if (len < strlen (src) + 1) return gpg_error (GPG_ERR_INTERNAL); dest = *destp; } else { /* The converted string will never be larger than the original string. */ dest = malloc (strlen (src) + 1); if (!dest) return gpg_error_from_syserror (); *destp = dest; } /* Convert the string. */ while (*src) { if (*src != '\\') { *(dest++) = *(src++); continue; } switch (src[1]) { #define DECODE_ONE(match,result) \ case match: \ src += 2; \ *(dest++) = result; \ break; DECODE_ONE ('\'', '\''); DECODE_ONE ('\"', '\"'); DECODE_ONE ('\?', '\?'); DECODE_ONE ('\\', '\\'); DECODE_ONE ('a', '\a'); DECODE_ONE ('b', '\b'); DECODE_ONE ('f', '\f'); DECODE_ONE ('n', '\n'); DECODE_ONE ('r', '\r'); DECODE_ONE ('t', '\t'); DECODE_ONE ('v', '\v'); case 'x': { int val = xtoi_2 (&src[2]); if (val == -1) { /* Should not happen. */ *(dest++) = *(src++); *(dest++) = *(src++); if (*src) *(dest++) = *(src++); if (*src) *(dest++) = *(src++); } else { if (!val) { /* A binary zero is not representable in a C string. */ *(dest++) = '\\'; *(dest++) = '0'; } else *((unsigned char *) dest++) = val; src += 4; } } break; default: { /* Should not happen. */ *(dest++) = *(src++); *(dest++) = *(src++); } } } *(dest++) = 0; return 0; } /* Helper for keylist_cb. This fucntion is invoked for each complete * line assembled by keylist_cb. */ static gpg_error_t keylist_cb_line (struct keylist_ctx *ctx) { char *line; enum { RT_NONE, RT_CRT, RT_CRS, RT_FPR, RT_GRP, RT_UID } rectype = RT_NONE; #define NR_FIELDS 16 char *field[NR_FIELDS]; int fields = 0; struct cert *cert; /* Strip a trailing carriage return. */ if (ctx->pending_len > 0 && ctx->pending[ctx->pending_len - 1] == '\r') ctx->pending_len--; ctx->pending[ctx->pending_len - 1] = '\0'; ctx->pending_len = 0; cert = &ctx->cert; line = ctx->pending; while (line && fields < NR_FIELDS) { field[fields++] = line; line = strchr (line, ':'); if (line) *(line++) = '\0'; } if (!strcmp (field[0], "crt")) rectype = RT_CRT; else if (!strcmp (field[0], "crs")) rectype = RT_CRS; else if (!strcmp (field[0], "fpr")) rectype = RT_FPR; else if (!strcmp (field[0], "grp")) rectype = RT_GRP; else if (!strcmp (field[0], "uid")) rectype = RT_UID; else rectype = RT_NONE; switch (rectype) { case RT_CRT: case RT_CRS: /* Reinitialize CERT. */ if (cert->valid) { gpg_error_t err; /* Return the cert. */ err = export_cert (ctx->cert.fpr, &ctx->cert); if (!err) err = ctx->search_cb (ctx->search_cb_hook, &ctx->cert); if (err) return err; cert_reset (cert); } cert->valid = true; #if 0 /* Field 2 has the trust info. */ if (fields >= 2) set_mainkey_trust_info (key, field[1]); #endif /* Field 3 has the key length. */ if (fields >= 3) { int i = atoi (field[2]); /* Ignore invalid values. */ if (i > 1) cert->length = i; } /* Field 4 has the public key algorithm. */ if (fields >= 4) { int i = atoi (field[3]); if (i >= 1 && i < 128) cert->pubkey_algo = i; } /* Field 5 has the long keyid. Allow short key IDs for the output of an external keyserver listing. */ if (fields >= 5 && strlen (field[4]) <= sizeof (cert->keyid) - 1) strcpy (cert->keyid, field[4]); /* Field 6 has the timestamp (seconds). */ if (fields >= 6) cert->timestamp = parse_timestamp (field[5], NULL); /* Field 7 has the expiration time (seconds). */ if (fields >= 7) cert->expires = parse_timestamp (field[6], NULL); /* Field 8 has the X.509 serial number. */ if (fields >= 8) { cert->issuer_serial = strdup (field[7]); if (!cert->issuer_serial) return gpg_error_from_syserror (); } #if 0 /* Field 9 has the ownertrust. */ if (fields >= 9) set_ownertrust (key, field[8]); #endif /* Field 10 is the issuer name. */ if (fields >= 10) if (decode_c_string (field[9], &cert->issuer_name, 0)) return gpg_error (GPG_ERR_ENOMEM); /* FIXME */ /* Field 11 has the signature class. */ #if 0 /* Field 12 has the capabilities. */ if (fields >= 12) set_mainkey_capability (key, field[11]); #endif break; case RT_UID: if (cert->valid) { /* Field 2 has the trust info, and field 10 has the user ID. Note that more than one UID field can appear. We only remember the last one. It's not used anyway. */ if (fields >= 10 && !cert->uid) { if (decode_c_string (field[9], &cert->uid, 0)) return gpg_error (GPG_ERR_ENOMEM); /* FIXME */ } } break; case RT_FPR: if (cert->valid) { /* Field 10 has the fingerprint (take only the first one). */ if (fields >= 10 && strlen (field[9]) <= sizeof (cert->fpr) - 1) strcpy (cert->fpr, field[9]); /* Field 13 has the gpgsm chain ID (take only the first one). */ if (fields >= 13 && strlen (field[12]) <= sizeof (cert->chain_id) - 1) strcpy (cert->chain_id, field[12]); } break; case RT_GRP: if (cert->valid) { /* Field 10 has the key grip. */ + if (fields >= 10 && strlen (field[9]) <= sizeof (cert->grip) - 1) + strcpy (cert->grip, field[9]); } break; case RT_NONE: /* Unknown record. */ break; } return 0; } /* This is the data line callback handler provided to assuan_transact * in scute_gpgsm_search_certs_by_{grip,fpr}. It buffers incomplete * lines, and is also used to handle the EOF signal directly outside * of assuan_transact. */ static gpg_error_t keylist_cb (void *hook, const void *line_data, size_t line_len) { struct keylist_ctx *ctx = hook; const char *line = line_data; gpg_error_t err; if (!line) { /* This indicates an EOF. */ /* Check for a pending line, in case GPGSM didn't close with a newline. */ if (ctx->pending_len) { err = keylist_cb_line (ctx); if (err) return err; } /* Check for a pending certificate and return it. */ if (ctx->cert.valid) { err = export_cert (ctx->cert.fpr, &ctx->cert); if (!err) err = ctx->search_cb (ctx->search_cb_hook, &ctx->cert); } else err = 0; return err; } while (line_len) { if (*line == '\n') { err = keylist_cb_line (ctx); if (err) return err; } else { if (ctx->pending_len >= MAX_LINE_LEN) return gpg_error (GPG_ERR_LINE_TOO_LONG); ctx->pending[ctx->pending_len++] = *line; } line++; line_len--; } return 0; } struct export_hook { /* The exported data. */ char *buffer; /* The length of the exported data buffer. */ unsigned int buffer_len; /* The size of the allocated exported data buffer. */ unsigned int buffer_size; }; #define EXP_DATA_START 4096 static gpg_error_t export_cert_cb (void *hook, const void *line_data, size_t line_len) { struct export_hook *exp = hook; const char *line = line_data; if (exp->buffer_size - exp->buffer_len < line_len) { unsigned int new_buffer_size = exp->buffer_size ? (exp->buffer_size * 2) : EXP_DATA_START; char *new_buffer = realloc (exp->buffer, new_buffer_size); if (!new_buffer) return gpg_error_from_syserror (); exp->buffer = new_buffer; exp->buffer_size = new_buffer_size; } memcpy (exp->buffer + exp->buffer_len, line, line_len); exp->buffer_len += line_len; return 0; } /* Export the certifciate using a second assuan connection. This is * called during the key listing after a "crt" record has been * received. */ static gpg_error_t export_cert (const char *fpr, struct cert *cert) { gpg_error_t err; assuan_context_t ctx; const char *argv[] = { "gpgsm", "--server", NULL }; #define COMMANDLINELEN 80 char cmd[COMMANDLINELEN]; struct export_hook exp; err = assuan_new (&ctx); if (err) { DEBUG (DBG_CRIT, "failed to allocate assuan context: %s", gpg_strerror (err)); return err; } err = assuan_pipe_connect (ctx, get_gpgsm_path (), argv, NULL, NULL, NULL, 128); if (err) { assuan_release (ctx); DEBUG (DBG_CRIT, "spawning %s\n", get_gpgsm_path ()); return err; } exp.buffer = NULL; exp.buffer_len = 0; exp.buffer_size = 0; snprintf (cmd, sizeof (cmd), "EXPORT --data -- %s", cert->fpr); err = assuan_transact (ctx, cmd, export_cert_cb, &exp, NULL, NULL, NULL, NULL); assuan_release (ctx); if (!err) { cert->cert_der = exp.buffer; cert->cert_der_len = exp.buffer_len; } if (!err) err = scute_agent_is_trusted (fpr, &cert->is_trusted); return err; } /* Search for certificates using a key listing using PATTERN which is * described by MODE. Invoke SEARCH_CB for each certificate found. */ gpg_error_t scute_gpgsm_search_certs (enum keylist_modes mode, const char *pattern, cert_search_cb_t search_cb, void *search_cb_hook) { gpg_error_t err; assuan_context_t ctx; const char *argv[] = { "gpgsm", "--server", NULL }; char line[ASSUAN_LINELENGTH]; struct keylist_ctx keylist_ctx; err = assuan_new (&ctx); if (err) { DEBUG (DBG_CRIT, "failed to allocate assuan context: %s", gpg_strerror (err)); return err; } err = assuan_pipe_connect (ctx, get_gpgsm_path (), argv, NULL, NULL, NULL, 128); if (err) { assuan_release (ctx); DEBUG (DBG_CRIT, "failed to spawn %s\n", get_gpgsm_path ()); return err; } memset (&keylist_ctx, 0, sizeof keylist_ctx); keylist_ctx.search_cb = search_cb; keylist_ctx.search_cb_hook = search_cb_hook; err = assuan_transact (ctx, "OPTION with-key-data", NULL, NULL, NULL, NULL, NULL, NULL); if (err) goto leave; snprintf (line, sizeof line, "LISTKEYS %s%s", mode == KEYLIST_BY_GRIP? "&":"", pattern); err = assuan_transact (ctx, line, keylist_cb, &keylist_ctx, NULL, NULL, NULL, NULL); if (err) goto leave; /* Signal the EOF. This is not done by Assuan for us. */ err = keylist_cb (&keylist_ctx, NULL, 0); if (err) goto leave; leave: cert_reset (&keylist_ctx.cert); assuan_release (ctx); return err; } diff --git a/src/cert-object.c b/src/cert-object.c index d250dcd..a7199a3 100644 --- a/src/cert-object.c +++ b/src/cert-object.c @@ -1,783 +1,770 @@ /* cert-object.c - Convert a GPGSM certificate into a PKCS #11 object. * Copyright (C) 2006, 2007 g10 Code GmbH * * This file is part of Scute. * * Scute 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. * * Scute 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 program; if not, see . * SPDX-License-Identifier: LGPL-2.1-or-later */ #if HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include "cryptoki.h" #include "support.h" #include "cert.h" #include "debug.h" #define atoi_1(p) (*(p) - '0' ) #define atoi_2(p) ((atoi_1(p) * 10) + atoi_1((p)+1)) #define atoi_4(p) ((atoi_2(p) * 100) + atoi_2((p)+2)) #if 0 /* Currently not used. */ static bool time_to_ck_date (time_t *atime, CK_DATE *ckdate) { struct tm broken_time; int nr; if (!*atime) return false; #ifdef HAVE_LOCALTIME_R if (!localtime_r (atime, &broken_time)) return false; #else { /* FIXME: This is not thread-safe, but it minimizes risk. */ struct tm *b_time = localtime (atime); if (!b_time) return false; memcpy (&broken_time, b_time, sizeof (*b_time)); } #endif /* We can only represent years until 9999. */ if (!(broken_time.tm_year >= 0 && broken_time.tm_year <= 8099 && broken_time.tm_mon >= 0 && broken_time.tm_mon <= 11 && broken_time.tm_mday >= 1 && broken_time.tm_mday <= 31)) { DEBUG (DBG_INFO, "unrepresentable time %i-%i-%i", broken_time.tm_year, broken_time.tm_mon, broken_time.tm_mday); return false; } #define LAST_DIGIT(d) (((d) % 10) + '0') nr = broken_time.tm_year + 1900; ckdate->year[3] = LAST_DIGIT (nr); nr = nr / 10; ckdate->year[2] = LAST_DIGIT (nr); nr = nr / 10; ckdate->year[1] = LAST_DIGIT (nr); nr = nr / 10; ckdate->year[0] = LAST_DIGIT (nr); nr = broken_time.tm_mon + 1; ckdate->month[1] = LAST_DIGIT (nr); nr = nr / 10; ckdate->month[0] = LAST_DIGIT (nr); nr = broken_time.tm_mday; ckdate->day[1] = LAST_DIGIT (nr); nr = nr / 10; ckdate->day[0] = LAST_DIGIT (nr); return true; } #endif /*0*/ static gpg_error_t asn1_get_len (unsigned char **asn1, int *asn1_len, int *rlen) { unsigned char *ptr = *asn1; int len = *asn1_len; int cnt; int result = 0; if (len < 1) { DEBUG (DBG_INFO, "unexpected end of certificate"); return gpg_error (GPG_ERR_GENERAL); } if (*ptr & 0x80) { cnt = *ptr & 0x7f; ptr++; len--; } else cnt = 1; /* We only support a limited number of length bytes. */ if (cnt > 2) { DEBUG (DBG_INFO, "unsupported length field"); return gpg_error (GPG_ERR_GENERAL); } if (len < cnt) { DEBUG (DBG_INFO, "unexpected end of certificate"); return gpg_error (GPG_ERR_GENERAL); } while (cnt--) { result = (result << 8) | *ptr; ptr++; len--; } *asn1 = ptr; *asn1_len = len; *rlen = result; return 0; } /* A path to an ASN.1 element that can be looked up with asn1_get_element. The last element in the list is returned (that one should have ENTER being false. */ struct asn1_path { unsigned char tag; /* True if we should enter the element, false if we should skip it. */ bool enter; }; static gpg_error_t asn1_get_element (unsigned char *cert, int cert_len, unsigned char **sub_start, int *sub_len, struct asn1_path *path, int path_size) { gpg_error_t err; unsigned char *prev_certp = NULL; unsigned char *certp = cert; int cert_left = cert_len; int len; int i; for (i = 0; i < path_size; i++) { prev_certp = certp; if (cert_left < 1) { DEBUG (DBG_INFO, "unexpected end of certificate"); return gpg_error (GPG_ERR_GENERAL); } if (*certp != path[i].tag) { DEBUG (DBG_INFO, "wrong element in lookup path"); return gpg_error (GPG_ERR_GENERAL); } certp++; cert_left--; err = asn1_get_len (&certp, &cert_left, &len); if (err) return err; if (!path[i].enter) { if (cert_left < len) { DEBUG (DBG_INFO, "unexpected end of certificate"); return gpg_error (GPG_ERR_GENERAL); } certp += len; cert_left -= len; } else { /* Special code to deal with ASN.1 data encapsulated in a bit string. */ if (path[i].tag == '\x03') { if (cert_left < 1) { DEBUG (DBG_INFO, "unexpected end of certificate"); return gpg_error (GPG_ERR_GENERAL); } if (*certp != '\x00') { DEBUG (DBG_INFO, "expected binary encapsulation missing"); return gpg_error (GPG_ERR_GENERAL); } certp++; cert_left--; } } } /* We found the subject. */ *sub_start = prev_certp; *sub_len = certp - prev_certp; return 0; } static gpg_error_t asn1_get_issuer (unsigned char *cert, int cert_len, unsigned char **sub_start, int *sub_len) { /* The path to the issuer entry in the DER file. This is Sequence->Sequence->Version,Serial,AlgID,Issuer. */ struct asn1_path path[] = { { '\x30', true }, { '\x30', true }, { '\xa0', false }, { '\x02', false }, { '\x30', false }, { '\x30', false } }; return asn1_get_element (cert, cert_len, sub_start, sub_len, path, DIM (path)); } static gpg_error_t asn1_get_subject (unsigned char *cert, int cert_len, unsigned char **sub_start, int *sub_len) { /* The path to the subject entry in the DER file. This is Sequence->Sequence->Version,Serial,AlgID,Issuer,Time,Subject. */ struct asn1_path path[] = { { '\x30', true }, { '\x30', true }, { '\xa0', false }, { '\x02', false }, { '\x30', false }, { '\x30', false }, { '\x30', false }, { '\x30', false } }; return asn1_get_element (cert, cert_len, sub_start, sub_len, path, DIM (path)); } static gpg_error_t asn1_get_serial (unsigned char *cert, int cert_len, unsigned char **sub_start, int *sub_len) { /* The path to the serial entry in the DER file. This is Sequence->Sequence->Version,Serial. */ struct asn1_path path[] = { { '\x30', true }, { '\x30', true }, { '\xa0', false }, { '\x02', false } }; return asn1_get_element (cert, cert_len, sub_start, sub_len, path, DIM (path)); } static gpg_error_t asn1_get_modulus (unsigned char *cert, int cert_len, unsigned char **sub_start, int *sub_len) { gpg_error_t err; int len; struct asn1_path path[] = { { '\x30', true }, { '\x30', true }, { '\xa0', false }, { '\x02', false }, { '\x30', false }, { '\x30', false }, { '\x30', false }, { '\x30', false }, { '\x30', true }, { '\x30', false }, { '\x03', true }, { '\x30', true }, { '\x02', false } }; /* The path to the modulus entry in the DER file. This is Sequence->Sequence->Version,Serial,AlgID,Issuer,Time,Subject, Sequence->Sequence,Bitstring->Sequence->Integer,Integer */ err = asn1_get_element (cert, cert_len, sub_start, sub_len, path, DIM (path)); if (err) return err; if (*sub_len < 1) { DEBUG (DBG_INFO, "modulus too short"); return gpg_error (GPG_ERR_GENERAL); } (*sub_start)++; (*sub_len)--; err = asn1_get_len (sub_start, sub_len, &len); if (err) return err; /* PKCS #11 expects an unsigned big integer. */ while (**sub_start == '\x00' && *sub_len > 0) { (*sub_start)++; (*sub_len)--; } return 0; } static gpg_error_t asn1_get_public_exp (unsigned char *cert, int cert_len, unsigned char **sub_start, int *sub_len) { gpg_error_t err; int len; /* The path to the public exp entry in the DER file. This is Sequence->Sequence->Version,Serial,AlgID,Issuer,Time,Subject, Sequence->Sequence,Bitstring->Sequence->Integer,Integer */ struct asn1_path path[] = { { '\x30', true }, { '\x30', true }, { '\xa0', false }, { '\x02', false }, { '\x30', false }, { '\x30', false }, { '\x30', false }, { '\x30', false }, { '\x30', true }, { '\x30', false }, { '\x03', true }, { '\x30', true }, { '\x02', false }, { '\x02', false } }; err = asn1_get_element (cert, cert_len, sub_start, sub_len, path, DIM (path)); if (err) return err; if (*sub_len < 1) { DEBUG (DBG_INFO, "public exponent too short"); return gpg_error (GPG_ERR_GENERAL); } (*sub_start)++; (*sub_len)--; err = asn1_get_len (sub_start, sub_len, &len); if (err) return err; /* PKCS #11 expects an unsigned big integer. */ while (**sub_start == '\x00' && *sub_len > 0) { (*sub_start)++; (*sub_len)--; } return 0; } static gpg_error_t attr_one (CK_ATTRIBUTE_PTR attr, CK_ULONG *attr_count, - CK_ATTRIBUTE_TYPE type, CK_VOID_PTR val, CK_ULONG size) + CK_ATTRIBUTE_TYPE type, const CK_VOID_PTR val, CK_ULONG size) { CK_ULONG i = *attr_count; attr[i].type = type; attr[i].ulValueLen = size; attr[i].pValue = malloc (size); if (attr[i].pValue == NULL) { DEBUG (DBG_CRIT, "out of memory"); return gpg_error (GPG_ERR_ENOMEM); } memcpy (attr[i].pValue, val, size); (*attr_count)++; return 0; } static gpg_error_t attr_empty (CK_ATTRIBUTE_PTR attr, CK_ULONG *attr_count, CK_ATTRIBUTE_TYPE type) { CK_ULONG i = *attr_count; attr[i].type = type; attr[i].ulValueLen = 0; attr[i].pValue = NULL_PTR; (*attr_count)++; return 0; } void scute_attr_free (CK_ATTRIBUTE_PTR attr, CK_ULONG attr_count) { while (0 < attr_count--) free (attr[attr_count].pValue); } gpg_error_t scute_attr_cert (struct cert *cert, const char *grip, CK_ATTRIBUTE_PTR *attrp, CK_ULONG *attr_countp) { CK_RV err = 0; CK_ATTRIBUTE_PTR attr; CK_ULONG attr_count; unsigned char *subject_start; int subject_len; unsigned char *issuer_start; int issuer_len; unsigned char *serial_start; int serial_len; CK_OBJECT_CLASS obj_class = CKO_CERTIFICATE; CK_BBOOL obj_token = CK_TRUE; CK_BBOOL obj_private = CK_FALSE; CK_BBOOL obj_modifiable = CK_FALSE; CK_CERTIFICATE_TYPE obj_cert_type = CKC_X_509; CK_BBOOL obj_trusted = cert->is_trusted; CK_ULONG obj_cert_cat = 0; CK_BYTE obj_check_value[3] = { '\0', '\0', '\0' }; CK_DATE obj_start_date; CK_DATE obj_end_date; CK_ULONG obj_java_midp_sec_domain = 0; err = asn1_get_subject (cert->cert_der, cert->cert_der_len, &subject_start, &subject_len); if (err) { DEBUG (DBG_INFO, "rejecting certificate: could not get subject: %s", gpg_strerror (err)); return err; } err = asn1_get_issuer (cert->cert_der, cert->cert_der_len, &issuer_start, &issuer_len); if (err) { DEBUG (DBG_INFO, "rejecting certificate: could not get issuer: %s", gpg_strerror (err)); return err; } err = asn1_get_serial (cert->cert_der, cert->cert_der_len, &serial_start, &serial_len); if (err) { DEBUG (DBG_INFO, "rejecting certificate: could not get serial: %s", gpg_strerror (err)); return err; } #define NR_ATTR_CERT 20 attr = malloc (sizeof (CK_ATTRIBUTE) * NR_ATTR_CERT); attr_count = 0; if (!attr) { DEBUG (DBG_INFO, "out of memory"); return gpg_error (GPG_ERR_ENOMEM); } if (!err) err = attr_one (attr, &attr_count, CKA_CLASS, &obj_class, sizeof obj_class); if (!err) err = attr_one (attr, &attr_count, CKA_TOKEN, &obj_token, sizeof obj_token); if (!err) err = attr_one (attr, &attr_count, CKA_PRIVATE, &obj_private, sizeof obj_private); if (!err) err = attr_one (attr, &attr_count, CKA_MODIFIABLE, &obj_modifiable, sizeof obj_modifiable); if (!err) - { - if (*cert->grip) - err = attr_one (attr, &attr_count, CKA_ID, - cert->grip, strlen (cert->grip)); - else - err = attr_one (attr, &attr_count, CKA_ID, - "DummyLabel", 10); - } + err = attr_one (attr, &attr_count, CKA_ID, grip, strlen (grip)); + if (!err) err = attr_one (attr, &attr_count, CKA_CERTIFICATE_TYPE, &obj_cert_type, sizeof obj_cert_type); if (!err) err = attr_one (attr, &attr_count, CKA_TRUSTED, &obj_trusted, sizeof obj_trusted); if (!err) err = attr_one (attr, &attr_count, CKA_CERTIFICATE_CATEGORY, &obj_cert_cat, sizeof obj_cert_cat); /* FIXME: Calculate check_value. */ if (!err) err = attr_one (attr, &attr_count, CKA_CHECK_VALUE, &obj_check_value, sizeof obj_check_value); #if 0 if (time_to_ck_date (&cert->timestamp, &obj_start_date)) { if (!err) err = attr_one (attr, &attr_count, CKA_START_DATE, &obj_start_date, sizeof obj_start_date); } if (time_to_ck_date (&cert->expires, &obj_end_date)) { if (!err) err = attr_one (attr, &attr_count, CKA_END_DATE, &obj_end_date, sizeof obj_end_date); } #else /* For now, we disable these fields. We can parse them from the certificate just as the other data. However, we would like to avoid parsing the certificates at all, let's see how much functionality we really need in the PKCS#11 token first. */ (void)obj_start_date; (void)obj_end_date; if (!err) err = attr_empty (attr, &attr_count, CKA_START_DATE); if (!err) err = attr_empty (attr, &attr_count, CKA_END_DATE); #endif /* Note: This attribute is mandatory. Without it, Firefox client authentication won't work. */ if (!err) err = attr_one (attr, &attr_count, CKA_SUBJECT, subject_start, subject_len); if (!err) err = attr_one (attr, &attr_count, CKA_ISSUER, issuer_start, issuer_len); if (!err) err = attr_one (attr, &attr_count, CKA_SERIAL_NUMBER, serial_start, serial_len); if (!err) err = attr_one (attr, &attr_count, CKA_VALUE, cert->cert_der, cert->cert_der_len); if (!err) err = attr_empty (attr, &attr_count, CKA_URL); if (!err) err = attr_empty (attr, &attr_count, CKA_HASH_OF_SUBJECT_PUBLIC_KEY); if (!err) err = attr_empty (attr, &attr_count, CKA_HASH_OF_ISSUER_PUBLIC_KEY); if (!err) err = attr_one (attr, &attr_count, CKA_JAVA_MIDP_SECURITY_DOMAIN, &obj_java_midp_sec_domain, sizeof obj_java_midp_sec_domain); if (err) { DEBUG (DBG_INFO, "could not build certificate object: %s", gpg_strerror (err)); scute_attr_free (attr, attr_count); return err; } /* FIXME: Not completely safe. */ assert (NR_ATTR_CERT >= attr_count); *attrp = attr; *attr_countp = attr_count; return 0; } gpg_error_t scute_attr_prv (struct cert *cert, const char *grip, CK_ATTRIBUTE_PTR *attrp, CK_ULONG *attr_countp) { CK_RV err = 0; CK_ATTRIBUTE_PTR attr; CK_ULONG attr_count; unsigned char *subject_start; int subject_len; unsigned char *modulus_start; int modulus_len; unsigned char *public_exp_start; int public_exp_len; CK_OBJECT_CLASS obj_class = CKO_PRIVATE_KEY; CK_BBOOL obj_token = CK_TRUE; CK_BBOOL obj_private = CK_FALSE; CK_BBOOL obj_modifiable = CK_FALSE; CK_KEY_TYPE obj_key_type = CKK_RSA; CK_DATE obj_start_date; CK_DATE obj_end_date; CK_BBOOL obj_derive = CK_FALSE; CK_BBOOL obj_local = CK_FALSE; /* FIXME: Unknown. */ CK_MECHANISM_TYPE obj_key_gen = CKM_RSA_PKCS_KEY_PAIR_GEN; CK_MECHANISM_TYPE obj_mechanisms[] = { CKM_RSA_PKCS }; CK_BBOOL obj_sensitive = CK_TRUE; CK_BBOOL obj_decrypt = CK_FALSE; /* Updated below. */ CK_BBOOL obj_sign = CK_FALSE; /* Updated below. */ CK_BBOOL obj_sign_recover = CK_FALSE; CK_BBOOL obj_unwrap = CK_FALSE; CK_BBOOL obj_extractable = CK_FALSE; CK_BBOOL obj_always_sensitive = CK_TRUE; CK_BBOOL obj_never_extractable = CK_TRUE; CK_BBOOL obj_wrap_with_trusted = CK_FALSE; CK_BBOOL obj_always_authenticate = CK_FALSE; obj_sign = CK_TRUE; err = asn1_get_subject (cert->cert_der, cert->cert_der_len, &subject_start, &subject_len); if (err) { DEBUG (DBG_INFO, "rejecting certificate: could not get subject: %s", gpg_strerror (err)); return err; } err = asn1_get_modulus (cert->cert_der, cert->cert_der_len, &modulus_start, &modulus_len); if (err) { DEBUG (DBG_INFO, "rejecting certificate: could not get modulus: %s", gpg_strerror (err)); return err; } err = asn1_get_public_exp (cert->cert_der, cert->cert_der_len, &public_exp_start, &public_exp_len); if (err) { DEBUG (DBG_INFO, "rejecting certificate: could not get public exp: %s", gpg_strerror (err)); return err; } #define NR_ATTR_PRV 27 attr = malloc (sizeof (CK_ATTRIBUTE) * NR_ATTR_PRV); attr_count = 0; if (!attr) { DEBUG (DBG_INFO, "out of core"); return gpg_error (GPG_ERR_ENOMEM); } if (!err) err = attr_one (attr, &attr_count, CKA_CLASS, &obj_class, sizeof obj_class); if (!err) err = attr_one (attr, &attr_count, CKA_TOKEN, &obj_token, sizeof obj_token); if (!err) err = attr_one (attr, &attr_count, CKA_PRIVATE, &obj_private, sizeof obj_private); if (!err) err = attr_one (attr, &attr_count, CKA_MODIFIABLE, &obj_modifiable, sizeof obj_modifiable); if (!err) - { - if (*cert->grip) - err = attr_one (attr, &attr_count, CKA_ID, - cert->grip, strlen (cert->grip)); - else - err = attr_one (attr, &attr_count, CKA_ID, - "DummyLabel", 10); - } + err = attr_one (attr, &attr_count, CKA_ID, grip, strlen (grip)); if (!err) err = attr_one (attr, &attr_count, CKA_KEY_TYPE, &obj_key_type, sizeof obj_key_type); #if 0 /* For now, we disable these fields. We can parse them from the certificate just as the other data. However, we would like to avoid parsing the certificates at all, let's see how much functionality we really need in the PKCS#11 token first. */ /* This code currently only works for certificates retrieved through gpgsm. */ if (time_to_ck_date (&cert->timestamp, &obj_start_date)) { if (!err) err = attr_one (attr, &attr_count, CKA_START_DATE, &obj_start_date, sizeof obj_start_date); } if (time_to_ck_date (&cert->expires, &obj_end_date)) { if (!err) err = attr_one (attr, &attr_count, CKA_END_DATE, &obj_end_date, sizeof obj_end_date); } #else /* For now, we disable these fields. We can parse them from the certificate just as the other data. However, we would like to avoid parsing the certificates at all, let's see how much functionality we really need in the PKCS#11 token first. */ (void)obj_start_date; (void)obj_end_date; if (!err) err = attr_empty (attr, &attr_count, CKA_START_DATE); if (!err) err = attr_empty (attr, &attr_count, CKA_END_DATE); #endif if (!err) err = attr_one (attr, &attr_count, CKA_DERIVE, &obj_derive, sizeof obj_derive); if (!err) err = attr_one (attr, &attr_count, CKA_LOCAL, &obj_local, sizeof obj_local); if (!err) err = attr_one (attr, &attr_count, CKA_KEY_GEN_MECHANISM, &obj_key_gen, sizeof obj_key_gen); if (!err) err = attr_one (attr, &attr_count, CKA_ALLOWED_MECHANISMS, &obj_mechanisms, sizeof obj_mechanisms); if (!err) err = attr_one (attr, &attr_count, CKA_SUBJECT, subject_start, subject_len); if (!err) err = attr_one (attr, &attr_count, CKA_SENSITIVE, &obj_sensitive, sizeof obj_sensitive); if (!err) err = attr_one (attr, &attr_count, CKA_DECRYPT, &obj_decrypt, sizeof obj_decrypt); if (!err) err = attr_one (attr, &attr_count, CKA_SIGN, &obj_sign, sizeof obj_sign); if (!err) err = attr_one (attr, &attr_count, CKA_SIGN_RECOVER, &obj_sign_recover, sizeof obj_sign_recover); if (!err) err = attr_one (attr, &attr_count, CKA_UNWRAP, &obj_unwrap, sizeof obj_unwrap); if (!err) err = attr_one (attr, &attr_count, CKA_EXTRACTABLE, &obj_extractable, sizeof obj_extractable); if (!err) err = attr_one (attr, &attr_count, CKA_ALWAYS_SENSITIVE, &obj_always_sensitive, sizeof obj_always_sensitive); if (!err) err = attr_one (attr, &attr_count, CKA_NEVER_EXTRACTABLE, &obj_never_extractable, sizeof obj_never_extractable); if (!err) err = attr_one (attr, &attr_count, CKA_WRAP_WITH_TRUSTED, &obj_wrap_with_trusted, sizeof obj_wrap_with_trusted); if (!err) err = attr_empty (attr, &attr_count, CKA_UNWRAP_TEMPLATE); if (!err) err = attr_one (attr, &attr_count, CKA_ALWAYS_AUTHENTICATE, &obj_always_authenticate, sizeof obj_always_authenticate); if (!err) err = attr_one (attr, &attr_count, CKA_MODULUS, modulus_start, modulus_len); if (!err) err = attr_one (attr, &attr_count, CKA_PUBLIC_EXPONENT, public_exp_start, public_exp_len); if (err) { DEBUG (DBG_INFO, "could not build private certificate object: %s", gpg_strerror (err)); scute_attr_free (attr, attr_count); return err; } /* FIXME: Not completely safe. */ assert (NR_ATTR_PRV >= attr_count); *attrp = attr; *attr_countp = attr_count; return 0; }