test: server can require client certificate in TLS handshake
[libisds.git] / src / crypto.c
blob43a3daa93878cc6a517547563c7f2ba80426f606
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 <gpgme.h>
10 #include <locale.h>
12 /* Initialize libgrcypt if not yet done by application or other library.
13 * @current_version is static string describing current gcrypt version
14 * @return IE_SUCCESS if everything is O.k. */
15 _hidden isds_error _isds_init_gcrypt(const char **current_version) {
16 const char *gcrypt_version;
18 /* Check version and initialize gcrypt */
19 gcrypt_version = gcry_check_version(NULL);
20 if (current_version) *current_version = gcrypt_version;
21 if (!gcrypt_version) {
22 isds_log(ILF_SEC, ILL_CRIT, _("Could not check gcrypt version\n"));
23 return IE_ERROR;
26 /* Finalize initialization if not yet done */
27 if (!gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) {
28 /* Disable secure memory */
29 /* TODO: Allow it when implementing key authentication */
30 gcry_control (GCRYCTL_DISABLE_SECMEM, 0);
31 /* Finish initialization */
32 gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
35 isds_log(ILF_SEC, ILL_INFO, _("gcrypt version in use: %s\n"),
36 gcrypt_version);
38 return IE_SUCCESS;
42 /* Computes hash from @input with @length and store it into @hash.
43 * The hash algorithm is defined inside @hash.
44 * @input is input block to hash
45 * @length is @input block length in bytes
46 * @hash input algorithm, output hash value and hash length; hash value will be
47 * reallocated, it's always valid pointer or NULL (before and after call) */
48 _hidden isds_error _isds_compute_hash(const void *input, const size_t length,
49 struct isds_hash *hash) {
50 int g_algorithm;
51 void *buffer;
53 if ((length != 0 && !input) || !hash) return IE_INVAL;
55 isds_log(ILF_SEC, ILL_DEBUG,
56 _("Data hash requested, length=%zu, content:\n%*s\n"
57 "End of data to hash\n"), length, length, input);
59 /* Select algorithm */
60 switch (hash->algorithm) {
61 case HASH_ALGORITHM_MD5: g_algorithm = GCRY_MD_MD5; break;
62 case HASH_ALGORITHM_SHA_1: g_algorithm = GCRY_MD_SHA1; break;
63 case HASH_ALGORITHM_SHA_224: g_algorithm = GCRY_MD_SHA224; break;
64 case HASH_ALGORITHM_SHA_256: g_algorithm = GCRY_MD_SHA256; break;
65 case HASH_ALGORITHM_SHA_384: g_algorithm = GCRY_MD_SHA384; break;
66 case HASH_ALGORITHM_SHA_512: g_algorithm = GCRY_MD_SHA512; break;
67 default: return IE_NOTSUP;
70 /* Test it's available */
71 if (gcry_md_test_algo(g_algorithm)) return IE_NOTSUP;
73 /* Get known the hash length and allocate buffer for hash value */
74 hash->length = gcry_md_get_algo_dlen(g_algorithm);
75 buffer = realloc(hash->value, hash->length);
76 if (!buffer) return IE_NOMEM;
77 hash->value = buffer;
79 /* Compute the hash */
80 gcry_md_hash_buffer(g_algorithm, hash->value, (length)?input:"", length);
82 return IE_SUCCESS;
86 /* Initialize GPGME.
87 * @current_version is pointer to static string describing current gpgme
88 * @return IE_SUCCESS if everything is O.k. */
89 _hidden isds_error _isds_init_gpgme(const char **current_version) {
90 const char *gpgme_version;
91 gpgme_error_t err;
93 /* Check version and initialize GPGME */
94 gpgme_version = gpgme_check_version(NULL);
95 if (current_version) *current_version = gpgme_version;
96 if (!gpgme_version) {
97 isds_log(ILF_SEC, ILL_CRIT, _("GPGME initialization failed\n"));
98 return IE_ERROR;
101 isds_log(ILF_SEC, ILL_INFO, _("GPGME version in use: %s\n"),
102 gpgme_version);
103 /* Needed to propagate locale to remote processes like pinentry */
104 gpgme_set_locale (NULL, LC_CTYPE, setlocale(LC_CTYPE, NULL));
105 #ifdef LC_MESSAGES
106 gpgme_set_locale (NULL, LC_MESSAGES, setlocale(LC_MESSAGES, NULL));
107 #endif
109 /* Check for engines */
110 err = gpgme_engine_check_version(GPGME_PROTOCOL_CMS);
111 if (err) {
112 isds_log(ILF_SEC, ILL_CRIT, _("GPGME does not support CMS\n"));
114 if (gpgme_err_code(err) == GPG_ERR_INV_ENGINE) {
115 gpgme_engine_info_t info; /* Do not free it */
117 err = gpgme_get_engine_info (&info);
118 if (!err) {
119 while (info && info->protocol != GPGME_PROTOCOL_CMS)
120 info = info->next;
121 if (!info)
122 isds_log(ILF_SEC, ILL_CRIT,
123 _("GPGME compiled without support for "
124 "protocol %s\n"),
125 gpgme_get_protocol_name(GPGME_PROTOCOL_CMS));
126 else if (info->file_name && !info->version)
127 isds_log(ILF_SEC, ILL_CRIT,
128 _("Engine %s not installed properly\n"),
129 info->file_name);
130 else if (info->file_name && info->version && info->req_version)
131 isds_log(ILF_SEC, ILL_CRIT,
132 _("Engine %s version %s installed, "
133 "but at least version %s required\n"),
134 info->file_name, info->version, info->req_version);
135 else
136 isds_log(ILF_SEC, ILL_CRIT,
137 _("Unknown problem with engine for protocol %s\n"),
138 gpgme_get_protocol_name(GPGME_PROTOCOL_CMS));
142 return IE_ERROR;
145 return IE_SUCCESS;
149 /* Free CMS data buffer allocated inside _isds_extract_cms_data().
150 * This is necessary because GPGME.
151 * @buffer is pointer to memory to free */
152 _hidden void _isds_cms_data_free(void *buffer) {
153 #ifdef ISDS_USE_KSBA
154 free(buffer);
155 #else
156 if (buffer) gpgme_free(buffer);
157 #endif
161 /* Extract data from CMS (successor of PKCS#7)
162 * @context is session context
163 * @cms is input block with CMS structure
164 * @cms_length is @cms block length in bytes
165 * @data are automatically reallocated bit stream with data found in @cms
166 * You must free them with _isds_cms_data_free().
167 * @data_length is length of @data in bytes */
168 _hidden isds_error _isds_extract_cms_data(struct isds_ctx *context,
169 const void *cms, const size_t cms_length,
170 void **data, size_t *data_length) {
171 isds_error err = IE_SUCCESS;
173 if (!cms || !data || !data_length) return IE_INVAL;
175 zfree(*data);
176 *data_length = 0;
178 #ifdef ISDS_USE_KSBA
179 ksba_cms_t cms_handler = NULL;
180 ksba_reader_t cms_reader = NULL;
181 ksba_writer_t cms_writer = NULL;
182 gpg_error_t gerr;
183 char gpg_error_string[128];
184 ksba_stop_reason_t stop_reason;
186 if (ksba_cms_new(&cms_handler)) {
187 isds_log_message(context, _("Could not allocate CMS parser handler"));
188 err = IE_NOMEM;
189 goto leave;
191 if (ksba_reader_new(&cms_reader)) {
192 isds_log_message(context, _("Could not allocate CMS reader"));
193 err = IE_ERROR;
194 goto leave;
196 if (ksba_reader_set_mem(cms_reader, cms, cms_length)) {
197 isds_log_message(context,
198 _("Could not bind CMS reader to PKCS#7 structure"));
199 err = IE_ERROR;
200 goto leave;
202 if (ksba_writer_new(&cms_writer)) {
203 isds_log_message(context, _("Could not allocate CMS writer"));
204 err = IE_ERROR;
205 goto leave;
207 if (ksba_writer_set_mem(cms_writer, 0)) {
208 isds_log_message(context,
209 _("Could not bind CMS reader to PKCS#7 structure"));
210 err = IE_ERROR;
211 goto leave;
213 if (ksba_cms_set_reader_writer(cms_handler, cms_reader, cms_writer)) {
214 isds_log_message(context,
215 _("Could not register CMS reader to CMS handler"));
216 err = IE_ERROR;
217 goto leave;
221 /* FIXME: This cycle stops with: Missing action on KSBA_CT_SIGNED_DATA
222 * I don't know how to program the KSBA cycle.
223 * TODO: Use gpgme's verify call to extract data. The only problem is it
224 * gpgme verifies signature always. We don't need it now and it's slow.
225 * We should find out how to use KSBA. */
226 do {
227 gerr = ksba_cms_parse(cms_handler, &stop_reason);
228 if (gerr) {
229 gpg_strerror_r(gerr, gpg_error_string, sizeof(gpg_error_string));
230 gpg_error_string[sizeof(gpg_error_string)/sizeof(char) - 1] = '\0';
231 isds_printf_message(context,
232 _("Error while parsing PKCS#7 structure: %s"),
233 gpg_error_string);
234 return IE_ERROR;
236 if (stop_reason == KSBA_SR_BEGIN_DATA) {
237 isds_log(ILF_SEC, ILL_DEBUG, _("CMS: Data beginning found\n"));
239 if (stop_reason == KSBA_SR_GOT_CONTENT) {
240 char *type;
241 switch (ksba_cms_get_content_type(cms_handler, 0)) {
242 case KSBA_CT_NONE: type = _("unknown data"); break;
243 case KSBA_CT_DATA: type = _("plain data"); break;
244 case KSBA_CT_SIGNED_DATA: type = _("signed data"); break;
245 case KSBA_CT_ENVELOPED_DATA:
246 type = _("encrypted data by session key"); break;
247 case KSBA_CT_DIGESTED_DATA: type = _("digest data"); break;
248 case KSBA_CT_ENCRYPTED_DATA: type = _("encrypted data"); break;
249 case KSBA_CT_AUTH_DATA: type = _("auth data"); break;
250 default: type = _("other data");
252 isds_log(ILF_SEC, ILL_DEBUG, _("CMS: Data type: %s\n"), type);
254 if (stop_reason == KSBA_SR_END_DATA) {
255 isds_log(ILF_SEC, ILL_DEBUG, _("CMS: Data end found\n"));
257 } while (stop_reason != KSBA_SR_READY);
259 *data = ksba_writer_snatch_mem(cms_writer, data_length);
260 if (!*data) {
261 isds_log_message(context, _("Getting CMS writer buffer failed"));
262 err = IE_ERROR;
263 goto leave;
266 leave:
267 ksba_cms_release(cms_handler);
268 ksba_writer_release(cms_writer);
269 ksba_reader_release(cms_reader);
270 #else /* ndef ISDS_USE_KSBA */
271 gpgme_ctx_t gctx = NULL;
272 gpgme_error_t gerr;
273 char gpgme_error_string[128];
274 gpgme_data_t cms_handler = NULL, plain_handler = NULL;
276 #define GET_GPGME_ERROR_STRING \
277 gpgme_strerror_r(gerr, gpgme_error_string, sizeof(gpgme_error_string)); \
278 gpgme_error_string[sizeof(gpgme_error_string)/sizeof(char) - 1] = '\0'; \
280 #define FAIL_ON_GPGME_ERROR(code, message) \
281 if (code) { \
282 GET_GPGME_ERROR_STRING; \
283 isds_printf_message(context, message, gpgme_error_string); \
284 if ((code) == GPG_ERR_ENOMEM) err = IE_NOMEM; \
285 else err = IE_ERROR; \
286 goto leave; \
289 /* Create GPGME context */
290 gerr = gpgme_new(&gctx);
291 FAIL_ON_GPGME_ERROR(gerr, _("Could not create GPGME context: %s"));
293 gerr = gpgme_set_protocol(gctx, GPGME_PROTOCOL_CMS);
294 FAIL_ON_GPGME_ERROR(gerr,
295 _("Could not set CMS protocol for GPGME context: %s"));
297 /* Create data handlers */
298 gerr = gpgme_data_new_from_mem(&cms_handler, cms, cms_length, 0);
299 FAIL_ON_GPGME_ERROR(gerr, _("Could not create data handler for "
300 "signed message in CMS structure: %s"));
301 gerr = gpgme_data_set_encoding(cms_handler, GPGME_DATA_ENCODING_BINARY);
302 FAIL_ON_GPGME_ERROR(gerr, _("Could not explain to GPGME "
303 "that CMS structure was packed in DER binary format: %s"));
305 gerr = gpgme_data_new(&plain_handler);
306 FAIL_ON_GPGME_ERROR(gerr, _("Could not create data handler for "
307 "plain message extracted from CMS structure: %s"));
309 /* Verify signature */
310 gerr = gpgme_op_verify(gctx, cms_handler, NULL, plain_handler);
311 if (gerr) {
312 GET_GPGME_ERROR_STRING;
313 isds_printf_message(context,
314 _("CMS verification failed: %s"),
315 gpgme_error_string);
316 err = IE_ERROR;
317 goto leave;
320 /* Get extracted plain message
321 * XXX: One must free *data with gpgme_free() because of clashing
322 * possibly different allocators. */
323 *data = gpgme_data_release_and_get_mem(plain_handler, data_length);
324 plain_handler = NULL;
325 if (!*data) {
326 /* No data or error occurred */
327 isds_printf_message(context,
328 _("Could not get plain data from GPGME "
329 "after verifying CMS structure"));
330 err = IE_ERROR;
331 goto leave;
334 leave:
335 if (gctx) gpgme_release(gctx);
336 if (plain_handler) gpgme_data_release(plain_handler);
337 if (cms_handler) gpgme_data_release(cms_handler);
338 #undef FAIL_ON_GPGME_ERROR
339 #undef GET_GPGME_ERROR_STRING
340 #endif /* ndef ISDS_USE_KSBA */
341 return err;