test: guess_raw_type: plain signed incoming message
[libisds.git] / src / crypto.c
bloba19cf86e2aa4eacd84ce45010f98141573ae55d4
1 #define _XOPEN_SOURCE 500 /* strdup from string.h */
2 #include "isds_priv.h"
3 #include "utils.h"
4 #include "gcrypt.h"
6 #ifdef ISDS_USE_KSBA
7 #include <ksba.h>
8 #endif
10 #include <gpgme.h>
11 #include <locale.h>
13 /* Inicialize libgrcypt if not yet done by application or other library.
14 * @current_version is static string describing cerrent gcrypt version
15 * @return IE_SUCCESS if everything is O.k. */
16 _hidden isds_error init_gcrypt(const char **current_version) {
17 const char *gcrypt_version;
19 /* Check version and initialize gcrypt */
20 gcrypt_version = gcry_check_version(NULL);
21 if (current_version) *current_version = gcrypt_version;
22 if (!gcrypt_version) {
23 isds_log(ILF_SEC, ILL_CRIT, _("Could not check gcrypt version\n"));
24 return IE_ERROR;
27 /* Finalize initialization if not yet done */
28 if (!gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) {
29 /* Disable secure memory */
30 /* TODO: Allow it when implementing key authentication */
31 gcry_control (GCRYCTL_DISABLE_SECMEM, 0);
32 /* Finish initialization */
33 gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
36 isds_log(ILF_SEC, ILL_INFO, _("gcrypt version in use: %s\n"),
37 gcrypt_version);
39 return IE_SUCCESS;
43 /* Computes hash from @input with @length and store it into @hash.
44 * The hash algoritm is defined inside @hash.
45 * @input is input block to hash
46 * @length is @input block length in bytes
47 * @hash input algoritm, output hash value and hash length; hash value will be
48 * reallocated, it's always valid pointer or NULL (before and after call) */
49 _hidden isds_error compute_hash(const void *input, const size_t length,
50 struct isds_hash *hash) {
51 int g_algorithm;
52 void *buffer;
54 if ((length != 0 && !input) || !hash) return IE_INVAL;
56 isds_log(ILF_SEC, ILL_DEBUG,
57 _("Data hash requested, length=%zu, content:\n%*s\n"
58 "End of data to hash\n"), length, length, input);
60 /* Select algorithm */
61 switch (hash->algorithm) {
62 case HASH_ALGORITHM_MD5: g_algorithm = GCRY_MD_MD5; break;
63 case HASH_ALGORITHM_SHA_1: g_algorithm = GCRY_MD_SHA1; break;
64 case HASH_ALGORITHM_SHA_224: g_algorithm = GCRY_MD_SHA224; break;
65 case HASH_ALGORITHM_SHA_256: g_algorithm = GCRY_MD_SHA256; break;
66 case HASH_ALGORITHM_SHA_384: g_algorithm = GCRY_MD_SHA384; break;
67 case HASH_ALGORITHM_SHA_512: g_algorithm = GCRY_MD_SHA512; break;
68 default: return IE_NOTSUP;
71 /* Test it's available */
72 if (gcry_md_test_algo(g_algorithm)) return IE_NOTSUP;
74 /* Get known the hash length and allocate buffer for hash value */
75 hash->length = gcry_md_get_algo_dlen(g_algorithm);
76 buffer = realloc(hash->value, hash->length);
77 if (!buffer) return IE_NOMEM;
78 hash->value = buffer;
80 /* Compute the hash */
81 gcry_md_hash_buffer(g_algorithm, hash->value, (length)?input:"", length);
83 return IE_SUCCESS;
87 /* Inicialize GPGME.
88 * @current_version is pointer to static string decribing currnet gpgme
89 * @return IE_SUCCESS if everything is O.k. */
90 _hidden isds_error init_gpgme(const char **current_version) {
91 const char *gpgme_version;
92 gpgme_error_t err;
94 /* Check version and initialize GPGME */
95 gpgme_version = gpgme_check_version(NULL);
96 if (current_version) *current_version = gpgme_version;
97 if (!gpgme_version) {
98 isds_log(ILF_SEC, ILL_CRIT, _("GPGME initialization failed\n"));
99 return IE_ERROR;
102 isds_log(ILF_SEC, ILL_INFO, _("GPGME version in use: %s\n"),
103 gpgme_version);
104 /* Needed to propagate locale to remote processes like pinentry */
105 gpgme_set_locale (NULL, LC_CTYPE, setlocale(LC_CTYPE, NULL));
106 #ifdef LC_MESSAGES
107 gpgme_set_locale (NULL, LC_MESSAGES, setlocale(LC_MESSAGES, NULL));
108 #endif
110 /* Check for engines */
111 err = gpgme_engine_check_version(GPGME_PROTOCOL_CMS);
112 if (err) {
113 isds_log(ILF_SEC, ILL_CRIT, _("GPGME does not support CMS\n"));
115 if (gpgme_err_code(err) == GPG_ERR_INV_ENGINE) {
116 gpgme_engine_info_t info; /* Do not free it */
118 err = gpgme_get_engine_info (&info);
119 if (!err) {
120 while (info && info->protocol != GPGME_PROTOCOL_CMS)
121 info = info->next;
122 if (!info)
123 isds_log(ILF_SEC, ILL_CRIT,
124 _("GPGME compiled without support for "
125 "protocol %s\n"),
126 gpgme_get_protocol_name(GPGME_PROTOCOL_CMS));
127 else if (info->file_name && !info->version)
128 isds_log(ILF_SEC, ILL_CRIT,
129 _("Engine %s not installed properly\n"),
130 info->file_name);
131 else if (info->file_name && info->version && info->req_version)
132 isds_log(ILF_SEC, ILL_CRIT,
133 _("Engine %s version %s installed, "
134 "but at least version %s required\n"),
135 info->file_name, info->version, info->req_version);
136 else
137 isds_log(ILF_SEC, ILL_CRIT,
138 _("Unknown problem with engine for protocol %s\n"),
139 gpgme_get_protocol_name(GPGME_PROTOCOL_CMS));
143 return IE_ERROR;
146 return IE_SUCCESS;
150 /* Free CMS data buffer allocated inside extract_cms_data().
151 * This is necesary because GPGME.
152 * @buffer is pointer to memory to free */
153 _hidden void cms_data_free(void *buffer) {
154 #ifdef ISDS_USE_KSBA
155 free(buffer);
156 #else
157 if (buffer) gpgme_free(buffer);
158 #endif
162 /* Extract data from CMS (successor of PKCS#7)
163 * @context is session context
164 * @cms is input block with CMS structure
165 * @cms_length is @cms block length in bytes
166 * @data are automatically reallocated bit stream with data found in @cms
167 * You must free them with cms_data_free().
168 * @data_length is length of @data in bytes */
169 _hidden isds_error extract_cms_data(struct isds_ctx *context,
170 const void *cms, const size_t cms_length,
171 void **data, size_t *data_length) {
172 isds_error err = IE_SUCCESS;
174 if (!cms || !data || !data_length) return IE_INVAL;
176 zfree(*data);
177 *data_length = 0;
179 #ifdef ISDS_USE_KSBA
180 ksba_cms_t cms_handler = NULL;
181 ksba_reader_t cms_reader = NULL;
182 ksba_writer_t cms_writer = NULL;
183 gpg_error_t gerr;
184 char gpg_error_string[128];
185 ksba_stop_reason_t stop_reason;
187 if (ksba_cms_new(&cms_handler)) {
188 isds_log_message(context, _("Could not allocate CMS parser handler"));
189 err = IE_NOMEM;
190 goto leave;
192 if (ksba_reader_new(&cms_reader)) {
193 isds_log_message(context, _("Could not allocate CMS reader"));
194 err = IE_ERROR;
195 goto leave;
197 if (ksba_reader_set_mem(cms_reader, cms, cms_length)) {
198 isds_log_message(context,
199 _("Could not bind CMS reader to PKCS#7 structure"));
200 err = IE_ERROR;
201 goto leave;
203 if (ksba_writer_new(&cms_writer)) {
204 isds_log_message(context, _("Could not allocate CMS writer"));
205 err = IE_ERROR;
206 goto leave;
208 if (ksba_writer_set_mem(cms_writer, 0)) {
209 isds_log_message(context,
210 _("Could not bind CMS reader to PKCS#7 structure"));
211 err = IE_ERROR;
212 goto leave;
214 if (ksba_cms_set_reader_writer(cms_handler, cms_reader, cms_writer)) {
215 isds_log_message(context,
216 _("Could not register CMS reader to CMS handler"));
217 err = IE_ERROR;
218 goto leave;
222 /* FIXME: This cycle stops with: Missing action on KSBA_CT_SIGNED_DATA
223 * I don't know how to program the KSBA cycle.
224 * TODO: Use gpgme's verify call to extract data. The only problem is it
225 * gpgme verifies signature always. We don't need it now and it's slow.
226 * We should find out how to use KSBA. */
227 do {
228 gerr = ksba_cms_parse(cms_handler, &stop_reason);
229 if (gerr) {
230 gpg_strerror_r(gerr, gpg_error_string, sizeof(gpg_error_string));
231 gpg_error_string[sizeof(gpg_error_string)/sizeof(char) - 1] = '\0';
232 isds_printf_message(context,
233 _("Error while parsing PKCS#7 structure: %s"),
234 gpg_error_string);
235 return IE_ERROR;
237 if (stop_reason == KSBA_SR_BEGIN_DATA) {
238 isds_log(ILF_SEC, ILL_DEBUG, _("CMS: Data begining found\n"));
240 if (stop_reason == KSBA_SR_GOT_CONTENT) {
241 char *type;
242 switch (ksba_cms_get_content_type(cms_handler, 0)) {
243 case KSBA_CT_NONE: type = _("uknown data"); break;
244 case KSBA_CT_DATA: type = _("plain data"); break;
245 case KSBA_CT_SIGNED_DATA: type = _("signed data"); break;
246 case KSBA_CT_ENVELOPED_DATA:
247 type = _("encypted data by session key"); break;
248 case KSBA_CT_DIGESTED_DATA: type = _("digest data"); break;
249 case KSBA_CT_ENCRYPTED_DATA: type = _("encrypted data"); break;
250 case KSBA_CT_AUTH_DATA: type = _("auth data"); break;
251 default: type = _("other data");
253 isds_log(ILF_SEC, ILL_DEBUG, _("CMS: Data type: %s\n"), type);
255 if (stop_reason == KSBA_SR_END_DATA) {
256 isds_log(ILF_SEC, ILL_DEBUG, _("CMS: Data end found\n"));
258 } while (stop_reason != KSBA_SR_READY);
260 *data = ksba_writer_snatch_mem(cms_writer, data_length);
261 if (!*data) {
262 isds_log_message(context, _("Getting CMS writer buffer failed"));
263 err = IE_ERROR;
264 goto leave;
267 leave:
268 ksba_cms_release(cms_handler);
269 ksba_writer_release(cms_writer);
270 ksba_reader_release(cms_reader);
271 #else /* ndef ISDS_USE_KSBA */
272 gpgme_ctx_t gctx = NULL;
273 gpgme_error_t gerr;
274 char gpgme_error_string[128];
275 gpgme_data_t cms_handler = NULL, plain_handler = NULL;
277 #define GET_GPGME_ERROR_STRING \
278 gpgme_strerror_r(gerr, gpgme_error_string, sizeof(gpgme_error_string)); \
279 gpgme_error_string[sizeof(gpgme_error_string)/sizeof(char) - 1] = '\0'; \
281 #define FAIL_ON_GPGME_ERROR(code, message) \
282 if (code) { \
283 GET_GPGME_ERROR_STRING; \
284 isds_printf_message(context, message, gpgme_error_string); \
285 if ((code) == GPG_ERR_ENOMEM) err = IE_NOMEM; \
286 else err = IE_ERROR; \
287 goto leave; \
290 /* Create GPGME context */
291 gerr = gpgme_new(&gctx);
292 FAIL_ON_GPGME_ERROR(gerr, _("Could not create GPGME context: %s"));
294 gerr = gpgme_set_protocol(gctx, GPGME_PROTOCOL_CMS);
295 FAIL_ON_GPGME_ERROR(gerr,
296 _("Could not set CMS protocol for GPGME context: %s"));
298 /* Create data handlers */
299 gerr = gpgme_data_new_from_mem(&cms_handler, cms, cms_length, 0);
300 FAIL_ON_GPGME_ERROR(gerr, _("Could not create data handler for "
301 "signed message in CMS structure: %s"));
302 gerr = gpgme_data_set_encoding(cms_handler, GPGME_DATA_ENCODING_BINARY);
303 FAIL_ON_GPGME_ERROR(gerr, _("Could not explain to GPGME "
304 "that CMS structure was packed in DER binary format: %s"));
306 gerr = gpgme_data_new(&plain_handler);
307 FAIL_ON_GPGME_ERROR(gerr, _("Could not create data handler for "
308 "plain message extracted from CMS structure: %s"));
310 /* Verify signature */
311 gerr = gpgme_op_verify(gctx, cms_handler, NULL, plain_handler);
312 if (gerr) {
313 GET_GPGME_ERROR_STRING;
314 isds_printf_message(context,
315 _("CMS verification failed: %s"),
316 gpgme_error_string);
317 err = IE_ERROR;
318 goto leave;
321 /* Get extracted plain message
322 * XXX: One must free *data with gpgme_free() because of clashing
323 * possibly different allocators. */
324 *data = gpgme_data_release_and_get_mem(plain_handler, data_length);
325 plain_handler = NULL;
326 if (!*data) {
327 /* No data or error occured */
328 isds_printf_message(context,
329 _("Could not get plain data from GPGME "
330 "after verifying CMS structure"));
331 err = IE_ERROR;
332 goto leave;
335 leave:
336 if (gctx) gpgme_release(gctx);
337 if (plain_handler) gpgme_data_release(plain_handler);
338 if (cms_handler) gpgme_data_release(cms_handler);
339 #undef FAIL_ON_GPGME_ERROR
340 #undef GET_GPGME_ERROR_STRING
341 #endif /* ndef ISDS_USE_KSBA */
342 return err;