/src/opensc/src/libopensc/pkcs15-cedulauy.c
Line | Count | Source |
1 | | /* |
2 | | * PKCS#15 emulation for the Uruguayan national eID (cédula de identidad digital), |
3 | | * driven by card-cedulauy.c. |
4 | | * |
5 | | * The card's on-card EF(TokenInfo) encodes one supportedAlgorithms entry with an |
6 | | * empty SEQUENCE (30 00) where PKCS#15 expects NULL/OID, which the generic binder |
7 | | * rejects. Instead of parsing the on-card PKCS#15 structure, this emulator builds |
8 | | * a synthetic view from AGESIC's publicly documented file layout: |
9 | | * - IAS application AID A0 00 00 00 18 40 00 00 01 63 42 00 |
10 | | * - signing certificate EF B001 |
11 | | * - signing key reference 0x01 (RSA 2048) |
12 | | * - Global PIN reference 0x11 (VERIFY 00 20 00 11) |
13 | | * - identity data EFs 7001/7002/7004/700B under DF 7000 |
14 | | * References: AGESIC "Documentación técnica de la cédula de identidad con chip" |
15 | | * and the AGESIC reference code at https://github.com/eIDuy/apdu-services . |
16 | | * |
17 | | * Copyright (C) 2026 Carlos Andrés Planchón Prestes <carlosandresplanchonprestes@gmail.com> |
18 | | * |
19 | | * This library is free software; you can redistribute it and/or |
20 | | * modify it under the terms of the GNU Lesser General Public |
21 | | * License as published by the Free Software Foundation; either |
22 | | * version 2.1 of the License, or (at your option) any later version. |
23 | | * |
24 | | * This library is distributed in the hope that it will be useful, |
25 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
26 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
27 | | * Lesser General Public License for more details. |
28 | | * |
29 | | * You should have received a copy of the GNU Lesser General Public |
30 | | * License along with this library; if not, write to the Free Software |
31 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
32 | | */ |
33 | | |
34 | | #ifdef HAVE_CONFIG_H |
35 | | #include <config.h> |
36 | | #endif |
37 | | |
38 | | #include "common/compat_strlcpy.h" |
39 | | #include "internal.h" |
40 | | #include "log.h" |
41 | | #include "pkcs15.h" |
42 | | #include <stdlib.h> |
43 | | #include <string.h> |
44 | | |
45 | | /* IAS application AID (AGESIC, documented) */ |
46 | | static const unsigned char cedulauy_aid[] = { |
47 | | 0xA0, 0x00, 0x00, 0x00, 0x18, 0x40, 0x00, 0x00, 0x01, 0x63, 0x42, 0x00}; |
48 | | |
49 | 67 | #define CEDULAUY_CERT_FID "B001" /* signing certificate EF */ |
50 | 67 | #define CEDULAUY_KEY_REF 0x01 /* signing private key reference */ |
51 | 67 | #define CEDULAUY_PIN_REF 0x11 /* Global PIN reference */ |
52 | 268 | #define CEDULAUY_OBJ_ID 0x01 /* links cert <-> prkey */ |
53 | | |
54 | | /* Public identity data files under DF 7000 (AGESIC layout), all readable |
55 | | * without PIN. They are exposed as raw PKCS#15 data objects carrying the |
56 | | * BER-TLV content documented by AGESIC; callers parse the tags themselves. |
57 | | * EF 711D (the ICAO SOD) is not exposed: it is absent on earlier batches. */ |
58 | | static const struct { |
59 | | const char *path; /* absolute path; the MF is emulated by the driver */ |
60 | | const char *label; |
61 | | } cedulauy_data_files[] = { |
62 | | {"3F0070007001", "Numero de documento"}, |
63 | | {"3F0070007002", "Datos biograficos" }, |
64 | | {"3F0070007004", "Fotografia" }, |
65 | | {"3F007000700B", "MRZ" }, |
66 | | }; |
67 | | |
68 | | static int |
69 | | sc_pkcs15emu_cedulauy_init(struct sc_pkcs15_card *p15card) |
70 | 67 | { |
71 | 67 | struct sc_context *ctx = p15card->card->ctx; |
72 | 67 | struct sc_aid aid; |
73 | 67 | int r; |
74 | 67 | size_t i; |
75 | | |
76 | 67 | struct sc_pkcs15_auth_info pin_info = {0}; |
77 | 67 | struct sc_pkcs15_object pin_obj = {0}; |
78 | 67 | struct sc_pkcs15_cert_info cert_info = {0}; |
79 | 67 | struct sc_pkcs15_object cert_obj = {0}; |
80 | 67 | struct sc_pkcs15_prkey_info prkey_info = {0}; |
81 | 67 | struct sc_pkcs15_object prkey_obj = {0}; |
82 | 67 | struct sc_pkcs15_cert *cert = NULL; |
83 | 67 | struct sc_app_info *appinfo; |
84 | | |
85 | 67 | LOG_FUNC_CALLED(ctx); |
86 | | |
87 | 67 | memcpy(aid.value, cedulauy_aid, sizeof cedulauy_aid); |
88 | 67 | aid.len = sizeof cedulauy_aid; |
89 | | |
90 | 67 | appinfo = calloc(1, sizeof(struct sc_app_info)); |
91 | 67 | if (appinfo == NULL) { |
92 | 0 | LOG_FUNC_RETURN(ctx, SC_ERROR_OUT_OF_MEMORY); |
93 | 0 | } |
94 | 67 | appinfo->aid = aid; |
95 | 67 | appinfo->ddo.aid = aid; |
96 | 67 | p15card->app = appinfo; |
97 | | |
98 | | /* Global PIN (reference 0x11, ASCII numeric, zero-padded to 12 bytes). */ |
99 | 67 | pin_info.auth_id.value[0] = CEDULAUY_OBJ_ID; |
100 | 67 | pin_info.auth_id.len = 1; |
101 | 67 | pin_info.auth_type = SC_PKCS15_PIN_AUTH_TYPE_PIN; |
102 | 67 | pin_info.attrs.pin.reference = CEDULAUY_PIN_REF; |
103 | 67 | pin_info.attrs.pin.flags = SC_PKCS15_PIN_FLAG_INITIALIZED | SC_PKCS15_PIN_FLAG_NEEDS_PADDING; |
104 | 67 | pin_info.attrs.pin.type = SC_PKCS15_PIN_TYPE_ASCII_NUMERIC; |
105 | 67 | pin_info.attrs.pin.min_length = 4; |
106 | 67 | pin_info.attrs.pin.stored_length = 12; |
107 | 67 | pin_info.attrs.pin.max_length = 12; |
108 | 67 | pin_info.attrs.pin.pad_char = 0x00; |
109 | 67 | pin_info.tries_left = -1; |
110 | 67 | pin_info.max_tries = -1; |
111 | 67 | strlcpy(pin_obj.label, "PIN", sizeof pin_obj.label); |
112 | 67 | r = sc_pkcs15emu_add_pin_obj(p15card, &pin_obj, &pin_info); |
113 | 67 | LOG_TEST_RET(ctx, r, "Cannot add Global PIN object"); |
114 | | |
115 | | /* Signing certificate (EF B001 under the IAS application). */ |
116 | 67 | sc_format_path("i" CEDULAUY_CERT_FID, &cert_info.path); /* 'i' => select by file ID */ |
117 | 67 | cert_info.path.aid = aid; |
118 | 67 | cert_info.id.value[0] = CEDULAUY_OBJ_ID; |
119 | 67 | cert_info.id.len = 1; |
120 | 67 | strlcpy(cert_obj.label, "Certificado de Firma", sizeof cert_obj.label); |
121 | 67 | r = sc_pkcs15emu_add_x509_cert(p15card, &cert_obj, &cert_info); |
122 | 67 | LOG_TEST_RET(ctx, r, "Cannot add signing certificate object"); |
123 | | |
124 | | /* Set the token label and serial number from the certificate, when |
125 | | * readable. The serial number also keys the file cache. */ |
126 | 67 | if (sc_pkcs15_read_certificate(p15card, &cert_info, 0, &cert) == SC_SUCCESS) { |
127 | 0 | static const struct sc_object_id cn_oid = { |
128 | 0 | {2, 5, 4, 3, -1} |
129 | 0 | }; |
130 | 0 | u8 *cn = NULL; |
131 | 0 | size_t cn_len = 0; |
132 | 0 | const u8 *serial = cert->serial; |
133 | 0 | size_t serial_len = cert->serial_len; |
134 | |
|
135 | 0 | sc_pkcs15_get_name_from_dn(ctx, cert->subject, cert->subject_len, |
136 | 0 | &cn_oid, &cn, &cn_len); |
137 | 0 | if (cn_len > 0) { |
138 | 0 | char *label = malloc(cn_len + 1); |
139 | 0 | if (label) { |
140 | 0 | memcpy(label, cn, cn_len); |
141 | 0 | label[cn_len] = '\0'; |
142 | 0 | free(p15card->tokeninfo->label); |
143 | 0 | p15card->tokeninfo->label = label; |
144 | 0 | } |
145 | 0 | } |
146 | 0 | free(cn); |
147 | | |
148 | | /* strip the ASN.1 INTEGER header, if present */ |
149 | 0 | if (serial_len > 2 && serial[0] == 0x02 && serial[1] == serial_len - 2) { |
150 | 0 | serial += 2; |
151 | 0 | serial_len -= 2; |
152 | 0 | } |
153 | 0 | if (serial_len > 0) { |
154 | 0 | char *sn = malloc(serial_len * 2 + 1); |
155 | 0 | if (sn) { |
156 | 0 | sc_bin_to_hex(serial, serial_len, sn, serial_len * 2 + 1, 0); |
157 | 0 | free(p15card->tokeninfo->serial_number); |
158 | 0 | p15card->tokeninfo->serial_number = sn; |
159 | 0 | } |
160 | 0 | } |
161 | |
|
162 | 0 | sc_pkcs15_free_certificate(cert); |
163 | 0 | } |
164 | | |
165 | | /* Signing private key (reference 0x01, RSA 2048, PIN-protected). */ |
166 | 67 | prkey_info.id.value[0] = CEDULAUY_OBJ_ID; |
167 | 67 | prkey_info.id.len = 1; |
168 | 67 | prkey_info.usage = SC_PKCS15_PRKEY_USAGE_SIGN | SC_PKCS15_PRKEY_USAGE_NONREPUDIATION; |
169 | 67 | prkey_info.native = 1; |
170 | 67 | prkey_info.key_reference = CEDULAUY_KEY_REF; |
171 | 67 | prkey_info.modulus_length = 2048; |
172 | 67 | prkey_obj.auth_id.value[0] = CEDULAUY_OBJ_ID; |
173 | 67 | prkey_obj.auth_id.len = 1; |
174 | 67 | prkey_obj.flags = SC_PKCS15_CO_FLAG_PRIVATE; |
175 | 67 | strlcpy(prkey_obj.label, "Clave de Firma", sizeof prkey_obj.label); |
176 | 67 | r = sc_pkcs15emu_add_rsa_prkey(p15card, &prkey_obj, &prkey_info); |
177 | 67 | LOG_TEST_RET(ctx, r, "Cannot add signing private key object"); |
178 | | |
179 | | /* Public identity data objects (DF 7000), readable without PIN. */ |
180 | 335 | for (i = 0; i < sizeof cedulauy_data_files / sizeof cedulauy_data_files[0]; i++) { |
181 | 268 | struct sc_pkcs15_data_info dinfo = {0}; |
182 | 268 | struct sc_pkcs15_object dobj = {0}; |
183 | | |
184 | 268 | sc_format_path(cedulauy_data_files[i].path, &dinfo.path); |
185 | 268 | strlcpy(dinfo.app_label, cedulauy_data_files[i].label, sizeof dinfo.app_label); |
186 | 268 | strlcpy(dobj.label, cedulauy_data_files[i].label, sizeof dobj.label); |
187 | 268 | r = sc_pkcs15emu_add_data_object(p15card, &dobj, &dinfo); |
188 | 268 | LOG_TEST_RET(ctx, r, "Cannot add identity data object"); |
189 | 268 | } |
190 | | |
191 | 67 | LOG_FUNC_RETURN(ctx, SC_SUCCESS); |
192 | 67 | } |
193 | | |
194 | | int |
195 | | sc_pkcs15emu_cedulauy_init_ex(struct sc_pkcs15_card *p15card, struct sc_aid *aid) |
196 | 10.1k | { |
197 | 10.1k | if (p15card->card->type != SC_CARD_TYPE_CEDULAUY) |
198 | 10.0k | return SC_ERROR_WRONG_CARD; |
199 | | |
200 | 67 | return sc_pkcs15emu_cedulauy_init(p15card); |
201 | 10.1k | } |