Document _isds_extract_cms_data() does not verify CMS' signature
[libisds.git] / src / crypto_gpg.c
blob9c320620d44fb0eab7ad2202f5dd6e37f70253b0
1 #include "isds_priv.h"
2 #include "utils.h"
3 #include <gcrypt.h>
5 #ifdef ISDS_USE_KSBA
6 #include <ksba.h>
7 #endif
9 #include <gpg-error.h> /* Because of ksba or gpgme */
10 #include <gpgme.h>
11 #include <locale.h>
13 #include "crypto.h"
16 /* Initialize libgrcypt if not yet done by application or other library.
17 * @current_version is static string describing current gcrypt version
18 * @return IE_SUCCESS if everything is O.k. */
19 static isds_error _isds_init_gcrypt(const char **current_version) {
20 const char *gcrypt_version;
22 /* Check version and initialize gcrypt */
23 gcrypt_version = gcry_check_version(NULL);
24 if (current_version) *current_version = gcrypt_version;
25 if (!gcrypt_version) {
26 isds_log(ILF_SEC, ILL_CRIT, _("Could not check gcrypt version\n"));
27 return IE_ERROR;
30 /* Finalize initialization if not yet done */
31 if (!gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) {
32 /* Disable secure memory */
33 /* TODO: Allow it when implementing key authentication */
34 gcry_control (GCRYCTL_DISABLE_SECMEM, 0);
35 /* Finish initialization */
36 gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
39 isds_log(ILF_SEC, ILL_INFO, _("gcrypt version in use: %s\n"),
40 gcrypt_version);
42 return IE_SUCCESS;
46 /* Computes hash from @input with @length and store it into @hash.
47 * The hash algorithm is defined inside @hash.
48 * @input is input block to hash
49 * @length is @input block length in bytes
50 * @hash input algorithm, output hash value and hash length; hash value will be
51 * reallocated, it's always valid pointer or NULL (before and after call) */
52 _hidden isds_error _isds_compute_hash(const void *input, const size_t length,
53 struct isds_hash *hash) {
54 int g_algorithm;
55 void *buffer;
57 if ((length != 0 && !input) || !hash) return IE_INVAL;
59 isds_log(ILF_SEC, ILL_DEBUG,
60 _("Data hash requested, length=%zu, content:\n%*s\n"
61 "End of data to hash\n"), length, length, input);
63 /* Select algorithm */
64 switch (hash->algorithm) {
65 case HASH_ALGORITHM_MD5: g_algorithm = GCRY_MD_MD5; break;
66 case HASH_ALGORITHM_SHA_1: g_algorithm = GCRY_MD_SHA1; break;
67 case HASH_ALGORITHM_SHA_224: g_algorithm = GCRY_MD_SHA224; break;
68 case HASH_ALGORITHM_SHA_256: g_algorithm = GCRY_MD_SHA256; break;
69 case HASH_ALGORITHM_SHA_384: g_algorithm = GCRY_MD_SHA384; break;
70 case HASH_ALGORITHM_SHA_512: g_algorithm = GCRY_MD_SHA512; break;
71 default: return IE_NOTSUP;
74 /* Test it's available */
75 if (gcry_md_test_algo(g_algorithm)) return IE_NOTSUP;
77 /* Get known the hash length and allocate buffer for hash value */
78 hash->length = gcry_md_get_algo_dlen(g_algorithm);
79 buffer = realloc(hash->value, hash->length);
80 if (!buffer) return IE_NOMEM;
81 hash->value = buffer;
83 /* Compute the hash */
84 gcry_md_hash_buffer(g_algorithm, hash->value, (length)?input:"", length);
86 return IE_SUCCESS;
90 /* Initialize GPGME.
91 * @current_version is pointer to static string describing current gpgme
92 * @return IE_SUCCESS if everything is O.k. */
93 static isds_error _isds_init_gpgme(const char **current_version) {
94 const char *gpgme_version;
95 gpgme_error_t err;
97 /* Check version and initialize GPGME */
98 gpgme_version = gpgme_check_version(NULL);
99 if (current_version) *current_version = gpgme_version;
100 if (!gpgme_version) {
101 isds_log(ILF_SEC, ILL_CRIT, _("GPGME initialization failed\n"));
102 return IE_ERROR;
105 isds_log(ILF_SEC, ILL_INFO, _("GPGME version in use: %s\n"),
106 gpgme_version);
107 /* Needed to propagate locale to remote processes like pinentry */
108 gpgme_set_locale (NULL, LC_CTYPE, setlocale(LC_CTYPE, NULL));
109 #ifdef LC_MESSAGES
110 gpgme_set_locale (NULL, LC_MESSAGES, setlocale(LC_MESSAGES, NULL));
111 #endif
113 /* Check for engines */
114 err = gpgme_engine_check_version(GPGME_PROTOCOL_CMS);
115 if (err) {
116 isds_log(ILF_SEC, ILL_CRIT, _("GPGME does not support CMS\n"));
118 if (gpgme_err_code(err) == GPG_ERR_INV_ENGINE) {
119 gpgme_engine_info_t info; /* Do not free it */
121 err = gpgme_get_engine_info (&info);
122 if (!err) {
123 while (info && info->protocol != GPGME_PROTOCOL_CMS)
124 info = info->next;
125 if (!info)
126 isds_log(ILF_SEC, ILL_CRIT,
127 _("GPGME compiled without support for "
128 "protocol %s\n"),
129 gpgme_get_protocol_name(GPGME_PROTOCOL_CMS));
130 else if (info->file_name && !info->version)
131 isds_log(ILF_SEC, ILL_CRIT,
132 _("Engine %s not installed properly\n"),
133 info->file_name);
134 else if (info->file_name && info->version && info->req_version)
135 isds_log(ILF_SEC, ILL_CRIT,
136 _("Engine %s version %s installed, "
137 "but at least version %s required\n"),
138 info->file_name, info->version, info->req_version);
139 else
140 isds_log(ILF_SEC, ILL_CRIT,
141 _("Unknown problem with engine for protocol %s\n"),
142 gpgme_get_protocol_name(GPGME_PROTOCOL_CMS));
146 return IE_ERROR;
149 return IE_SUCCESS;
153 /* Initialise all cryptographic libraries which libisds depends on.
154 * @return IE_SUCCESS if everything went all-right. */
155 _hidden isds_error _isds_init_crypto(void) {
156 /* Initialize gpg-error because of gpgme and ksba */
157 if (gpg_err_init()) {
158 isds_log(ILF_ISDS, ILL_CRIT,
159 _("gpg-error library initialization failed\n"));
160 return IE_ERROR;
163 /* Initialize GPGME */
164 if (_isds_init_gpgme(&version_gpgme)) {
165 isds_log(ILF_ISDS, ILL_CRIT,
166 _("GPGME library initialization failed\n"));
167 return IE_ERROR;
170 /* Initialize gcrypt */
171 if (_isds_init_gcrypt(&version_gcrypt)) {
172 isds_log(ILF_ISDS, ILL_CRIT,
173 _("gcrypt library initialization failed\n"));
174 return IE_ERROR;
177 return IE_SUCCESS;
181 /* Free CMS data buffer allocated inside _isds_extract_cms_data().
182 * This is necessary because GPGME.
183 * @buffer is pointer to memory to free */
184 _hidden void _isds_cms_data_free(void *buffer) {
185 #ifdef ISDS_USE_KSBA
186 free(buffer);
187 #else
188 if (buffer) gpgme_free(buffer);
189 #endif
193 /* Extract data from CMS (successor of PKCS#7)
194 * The CMS' signature is is not verified.
195 * @context is session context
196 * @cms is input block with CMS structure
197 * @cms_length is @cms block length in bytes
198 * @data are automatically reallocated bit stream with data found in @cms
199 * You must free them with _isds_cms_data_free().
200 * @data_length is length of @data in bytes */
201 _hidden isds_error _isds_extract_cms_data(struct isds_ctx *context,
202 const void *cms, const size_t cms_length,
203 void **data, size_t *data_length) {
204 isds_error err = IE_SUCCESS;
206 if (!cms || !data || !data_length) return IE_INVAL;
208 zfree(*data);
209 *data_length = 0;
211 #ifdef ISDS_USE_KSBA
212 ksba_cms_t cms_handler = NULL;
213 ksba_reader_t cms_reader = NULL;
214 ksba_writer_t cms_writer = NULL;
215 gpg_error_t gerr;
216 char gpg_error_string[128];
217 ksba_stop_reason_t stop_reason;
219 if (ksba_cms_new(&cms_handler)) {
220 isds_log_message(context, _("Could not allocate CMS parser handler"));
221 err = IE_NOMEM;
222 goto leave;
224 if (ksba_reader_new(&cms_reader)) {
225 isds_log_message(context, _("Could not allocate CMS reader"));
226 err = IE_ERROR;
227 goto leave;
229 if (ksba_reader_set_mem(cms_reader, cms, cms_length)) {
230 isds_log_message(context,
231 _("Could not bind CMS reader to PKCS#7 structure"));
232 err = IE_ERROR;
233 goto leave;
235 if (ksba_writer_new(&cms_writer)) {
236 isds_log_message(context, _("Could not allocate CMS writer"));
237 err = IE_ERROR;
238 goto leave;
240 if (ksba_writer_set_mem(cms_writer, 0)) {
241 isds_log_message(context,
242 _("Could not bind CMS reader to PKCS#7 structure"));
243 err = IE_ERROR;
244 goto leave;
246 if (ksba_cms_set_reader_writer(cms_handler, cms_reader, cms_writer)) {
247 isds_log_message(context,
248 _("Could not register CMS reader to CMS handler"));
249 err = IE_ERROR;
250 goto leave;
254 /* FIXME: This cycle stops with: Missing action on KSBA_CT_SIGNED_DATA
255 * I don't know how to program the KSBA cycle.
256 * TODO: Use gpgme's verify call to extract data. The only problem is it
257 * gpgme verifies signature always. We don't need it now and it's slow.
258 * We should find out how to use KSBA. */
259 do {
260 gerr = ksba_cms_parse(cms_handler, &stop_reason);
261 if (gerr) {
262 gpg_strerror_r(gerr, gpg_error_string, sizeof(gpg_error_string));
263 gpg_error_string[sizeof(gpg_error_string)/sizeof(char) - 1] = '\0';
264 isds_printf_message(context,
265 _("Error while parsing PKCS#7 structure: %s"),
266 gpg_error_string);
267 return IE_ERROR;
269 if (stop_reason == KSBA_SR_BEGIN_DATA) {
270 isds_log(ILF_SEC, ILL_DEBUG, _("CMS: Data beginning found\n"));
272 if (stop_reason == KSBA_SR_GOT_CONTENT) {
273 char *type;
274 switch (ksba_cms_get_content_type(cms_handler, 0)) {
275 case KSBA_CT_NONE: type = _("unknown data"); break;
276 case KSBA_CT_DATA: type = _("plain data"); break;
277 case KSBA_CT_SIGNED_DATA: type = _("signed data"); break;
278 case KSBA_CT_ENVELOPED_DATA:
279 type = _("encrypted data by session key"); break;
280 case KSBA_CT_DIGESTED_DATA: type = _("digest data"); break;
281 case KSBA_CT_ENCRYPTED_DATA: type = _("encrypted data"); break;
282 case KSBA_CT_AUTH_DATA: type = _("auth data"); break;
283 default: type = _("other data");
285 isds_log(ILF_SEC, ILL_DEBUG, _("CMS: Data type: %s\n"), type);
287 if (stop_reason == KSBA_SR_END_DATA) {
288 isds_log(ILF_SEC, ILL_DEBUG, _("CMS: Data end found\n"));
290 } while (stop_reason != KSBA_SR_READY);
292 *data = ksba_writer_snatch_mem(cms_writer, data_length);
293 if (!*data) {
294 isds_log_message(context, _("Getting CMS writer buffer failed"));
295 err = IE_ERROR;
296 goto leave;
299 leave:
300 ksba_cms_release(cms_handler);
301 ksba_writer_release(cms_writer);
302 ksba_reader_release(cms_reader);
303 #else /* ndef ISDS_USE_KSBA */
304 gpgme_ctx_t gctx = NULL;
305 gpgme_error_t gerr;
306 char gpgme_error_string[128];
307 gpgme_data_t cms_handler = NULL, plain_handler = NULL;
309 #define GET_GPGME_ERROR_STRING \
310 gpgme_strerror_r(gerr, gpgme_error_string, sizeof(gpgme_error_string)); \
311 gpgme_error_string[sizeof(gpgme_error_string)/sizeof(char) - 1] = '\0'; \
313 #define FAIL_ON_GPGME_ERROR(code, message) \
314 if (code) { \
315 GET_GPGME_ERROR_STRING; \
316 isds_printf_message(context, message, gpgme_error_string); \
317 if ((code) == GPG_ERR_ENOMEM) err = IE_NOMEM; \
318 else err = IE_ERROR; \
319 goto leave; \
322 /* Create GPGME context */
323 gerr = gpgme_new(&gctx);
324 FAIL_ON_GPGME_ERROR(gerr, _("Could not create GPGME context: %s"));
326 gerr = gpgme_set_protocol(gctx, GPGME_PROTOCOL_CMS);
327 FAIL_ON_GPGME_ERROR(gerr,
328 _("Could not set CMS protocol for GPGME context: %s"));
330 /* Create data handlers */
331 gerr = gpgme_data_new_from_mem(&cms_handler, cms, cms_length, 0);
332 FAIL_ON_GPGME_ERROR(gerr, _("Could not create data handler for "
333 "signed message in CMS structure: %s"));
334 gerr = gpgme_data_set_encoding(cms_handler, GPGME_DATA_ENCODING_BINARY);
335 FAIL_ON_GPGME_ERROR(gerr, _("Could not explain to GPGME "
336 "that CMS structure was packed in DER binary format: %s"));
338 gerr = gpgme_data_new(&plain_handler);
339 FAIL_ON_GPGME_ERROR(gerr, _("Could not create data handler for "
340 "plain message extracted from CMS structure: %s"));
342 /* Verify signature */
343 gerr = gpgme_op_verify(gctx, cms_handler, NULL, plain_handler);
344 if (gerr) {
345 GET_GPGME_ERROR_STRING;
346 isds_printf_message(context,
347 _("CMS verification failed: %s"),
348 gpgme_error_string);
349 err = IE_ERROR;
350 goto leave;
353 /* Get extracted plain message
354 * XXX: One must free *data with gpgme_free() because of clashing
355 * possibly different allocators. */
356 *data = gpgme_data_release_and_get_mem(plain_handler, data_length);
357 plain_handler = NULL;
358 if (!*data) {
359 /* No data or error occurred */
360 isds_printf_message(context,
361 _("Could not get plain data from GPGME "
362 "after verifying CMS structure"));
363 err = IE_ERROR;
364 goto leave;
367 leave:
368 if (gctx) gpgme_release(gctx);
369 if (plain_handler) gpgme_data_release(plain_handler);
370 if (cms_handler) gpgme_data_release(cms_handler);
371 #undef FAIL_ON_GPGME_ERROR
372 #undef GET_GPGME_ERROR_STRING
373 #endif /* ndef ISDS_USE_KSBA */
374 return err;