Started implementing handling of DH Nonce attributes
[strongswan.git] / src / libpts / pts / pts.c
1 /*
2 * Copyright (C) 2011 Sansar Choinyambuu
3 * HSR Hochschule fuer Technik Rapperswil
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * for more details.
14 */
15
16 #include "pts.h"
17
18 #include <debug.h>
19 #include <crypto/hashers/hasher.h>
20
21 #include <trousers/tss.h>
22 #include <trousers/trousers.h>
23
24 #include <sys/stat.h>
25 #include <sys/utsname.h>
26 #include <errno.h>
27
28 #define PTS_BUF_SIZE 4096
29
30 typedef struct private_pts_t private_pts_t;
31
32 /**
33 * Private data of a pts_t object.
34 *
35 */
36 struct private_pts_t {
37
38 /**
39 * Public pts_t interface.
40 */
41 pts_t public;
42
43 /**
44 * PTS Protocol Capabilities
45 */
46 pts_proto_caps_flag_t proto_caps;
47
48 /**
49 * PTS Measurement Algorithm
50 */
51 pts_meas_algorithms_t algorithm;
52
53 /**
54 * PTS Diffie Hellman Group
55 */
56 pts_dh_group_t dh_group;
57
58 /**
59 * Contains a Diffie Hellman Nonce
60 */
61 chunk_t dh_nonce;
62
63 /**
64 * Contains a Diffie Hellman Public Value
65 */
66 chunk_t dh_public_value;
67
68 /**
69 * Platform and OS Info
70 */
71 char *platform_info;
72
73 /**
74 * Do we have an activated TPM
75 */
76 bool has_tpm;
77
78 /**
79 * Contains a TPM_CAP_VERSION_INFO struct
80 */
81 chunk_t tpm_version_info;
82
83 /**
84 * Contains a Attestation Identity Key or Certificate
85 */
86 certificate_t *aik;
87
88 };
89
90 METHOD(pts_t, get_proto_caps, pts_proto_caps_flag_t,
91 private_pts_t *this)
92 {
93 return this->proto_caps;
94 }
95
96 METHOD(pts_t, set_proto_caps, void,
97 private_pts_t *this, pts_proto_caps_flag_t flags)
98 {
99 this->proto_caps = flags;
100 DBG2(DBG_PTS, "supported PTS protocol capabilities: %s%s%s%s%s",
101 flags & PTS_PROTO_CAPS_C ? "C" : ".",
102 flags & PTS_PROTO_CAPS_V ? "V" : ".",
103 flags & PTS_PROTO_CAPS_D ? "D" : ".",
104 flags & PTS_PROTO_CAPS_T ? "T" : ".",
105 flags & PTS_PROTO_CAPS_X ? "X" : ".");
106 }
107
108 METHOD(pts_t, get_meas_algorithm, pts_meas_algorithms_t,
109 private_pts_t *this)
110 {
111 return this->algorithm;
112 }
113
114 METHOD(pts_t, set_meas_algorithm, void,
115 private_pts_t *this, pts_meas_algorithms_t algorithm)
116 {
117 hash_algorithm_t hash_alg;
118
119 hash_alg = pts_meas_to_hash_algorithm(algorithm);
120 DBG2(DBG_PTS, "selected PTS measurement algorithm is %N",
121 hash_algorithm_names, hash_alg);
122 if (hash_alg != HASH_UNKNOWN)
123 {
124 this->algorithm = algorithm;
125 }
126 }
127
128 METHOD(pts_t, get_dh_group, pts_dh_group_t,
129 private_pts_t *this)
130 {
131 return this->dh_group;
132 }
133
134 METHOD(pts_t, set_dh_group, void,
135 private_pts_t *this, pts_dh_group_t group)
136 {
137 diffie_hellman_group_t dh_group;
138
139 dh_group = pts_dh_group_to_strongswan_dh_group(group);
140 DBG2(DBG_PTS, "selected PTS Diffie Hellman Group is %N",
141 diffie_hellman_group_names, dh_group);
142 if (dh_group != MODP_NONE)
143 {
144 this->dh_group = dh_group;
145 }
146 }
147
148 /**
149 * Print TPM 1.2 Version Info
150 */
151 static void print_tpm_version_info(private_pts_t *this)
152 {
153 TPM_CAP_VERSION_INFO versionInfo;
154 UINT64 offset = 0;
155 TSS_RESULT result;
156
157 result = Trspi_UnloadBlob_CAP_VERSION_INFO(&offset,
158 this->tpm_version_info.ptr, &versionInfo);
159 if (result != TSS_SUCCESS)
160 {
161 DBG1(DBG_PTS, "could not parse tpm version info: tss error 0x%x",
162 result);
163 }
164 else
165 {
166 DBG2(DBG_PTS, "TPM 1.2 Version Info: Chip Version: %hhu.%hhu.%hhu.%hhu,"
167 " Spec Level: %hu, Errata Rev: %hhu, Vendor ID: %.4s",
168 versionInfo.version.major, versionInfo.version.minor,
169 versionInfo.version.revMajor, versionInfo.version.revMinor,
170 versionInfo.specLevel, versionInfo.errataRev,
171 versionInfo.tpmVendorID);
172 }
173 }
174
175 METHOD(pts_t, get_platform_info, char*,
176 private_pts_t *this)
177 {
178 return this->platform_info;
179 }
180
181 METHOD(pts_t, set_platform_info, void,
182 private_pts_t *this, char *info)
183 {
184 free(this->platform_info);
185 this->platform_info = strdup(info);
186 }
187
188 METHOD(pts_t, get_tpm_version_info, bool,
189 private_pts_t *this, chunk_t *info)
190 {
191 if (!this->has_tpm)
192 {
193 return FALSE;
194 }
195 *info = this->tpm_version_info;
196 print_tpm_version_info(this);
197 return TRUE;
198 }
199
200 METHOD(pts_t, set_tpm_version_info, void,
201 private_pts_t *this, chunk_t info)
202 {
203 this->tpm_version_info = chunk_clone(info);
204 print_tpm_version_info(this);
205 }
206
207 /**
208 * Load an AIK certificate or public key,
209 * the certificate having precedence over the public key if both are present
210 */
211 static void load_aik(private_pts_t *this)
212 {
213 char *cert_path, *key_path;
214
215 cert_path = lib->settings->get_str(lib->settings,
216 "libimcv.plugins.imc-attestation.aik_cert", NULL);
217 key_path = lib->settings->get_str(lib->settings,
218 "libimcv.plugins.imc-attestation.aik_key", NULL);
219
220 if (cert_path)
221 {
222 this->aik = lib->creds->create(lib->creds, CRED_CERTIFICATE,
223 CERT_X509, BUILD_FROM_FILE,
224 cert_path, BUILD_END);
225 if (this->aik)
226 {
227 DBG2(DBG_PTS, "loaded AIK certificate from '%s'", cert_path);
228 return;
229 }
230 }
231 if (key_path)
232 {
233 this->aik = lib->creds->create(lib->creds, CRED_CERTIFICATE,
234 CERT_TRUSTED_PUBKEY, BUILD_FROM_FILE,
235 key_path, BUILD_END);
236 if (this->aik)
237 {
238 DBG2(DBG_PTS, "loaded AIK public key from '%s'", key_path);
239 return;
240 }
241 }
242 DBG1(DBG_PTS, "neither AIK certificate nor public key is available");
243 }
244
245 METHOD(pts_t, get_aik, certificate_t*,
246 private_pts_t *this)
247 {
248 return this->aik;
249 }
250
251 METHOD(pts_t, set_aik, void,
252 private_pts_t *this, certificate_t *aik)
253 {
254 DESTROY_IF(this->aik);
255 this->aik = aik->get_ref(aik);
256 }
257
258 /**
259 * Compute a hash over a file
260 */
261 static bool hash_file(hasher_t *hasher, char *pathname, u_char *hash)
262 {
263 u_char buffer[PTS_BUF_SIZE];
264 FILE *file;
265 int bytes_read;
266
267 file = fopen(pathname, "rb");
268 if (!file)
269 {
270 DBG1(DBG_PTS," file '%s' can not be opened, %s", pathname,
271 strerror(errno));
272 return FALSE;
273 }
274 while (TRUE)
275 {
276 bytes_read = fread(buffer, 1, sizeof(buffer), file);
277 if (bytes_read > 0)
278 {
279 hasher->get_hash(hasher, chunk_create(buffer, bytes_read), NULL);
280 }
281 else
282 {
283 hasher->get_hash(hasher, chunk_empty, hash);
284 break;
285 }
286 }
287 fclose(file);
288
289 return TRUE;
290 }
291
292 /**
293 * Get the relative filename of a fully qualified file pathname
294 */
295 static char* get_filename(char *pathname)
296 {
297 char *pos, *filename;
298
299 pos = filename = pathname;
300 while (pos && *(++pos) != '\0')
301 {
302 filename = pos;
303 pos = strchr(filename, '/');
304 }
305 return filename;
306 }
307
308 METHOD(pts_t, is_path_valid, bool, private_pts_t *this, char *path,
309 pts_error_code_t *error_code)
310 {
311 struct stat st;
312
313 *error_code = 0;
314
315 if (!stat(path, &st))
316 {
317 return TRUE;
318 }
319 else if (errno == ENOENT || errno == ENOTDIR)
320 {
321 DBG1(DBG_PTS, "file/directory does not exist %s", path);
322 *error_code = TCG_PTS_FILE_NOT_FOUND;
323 }
324 else if (errno == EFAULT)
325 {
326 DBG1(DBG_PTS, "bad address %s", path);
327 *error_code = TCG_PTS_INVALID_PATH;
328 }
329 else
330 {
331 DBG1(DBG_PTS, "error: %s occured while validating path: %s",
332 strerror(errno), path);
333 return FALSE;
334 }
335
336 return TRUE;
337 }
338
339 METHOD(pts_t, do_measurements, pts_file_meas_t*,
340 private_pts_t *this, u_int16_t request_id, char *pathname, bool is_directory)
341 {
342 hasher_t *hasher;
343 hash_algorithm_t hash_alg;
344 u_char hash[HASH_SIZE_SHA384];
345 chunk_t measurement;
346 pts_file_meas_t *measurements;
347
348 /* Create a hasher */
349 hash_alg = pts_meas_to_hash_algorithm(this->algorithm);
350 hasher = lib->crypto->create_hasher(lib->crypto, hash_alg);
351 if (!hasher)
352 {
353 DBG1(DBG_PTS, " hasher %N not available", hash_algorithm_names, hash_alg);
354 return NULL;
355 }
356
357 /* Create a measurement object */
358 measurements = pts_file_meas_create(request_id);
359
360 /* Link the hash to the measurement and set the measurement length */
361 measurement = chunk_create(hash, hasher->get_hash_size(hasher));
362
363 if (is_directory)
364 {
365 enumerator_t *enumerator;
366 char *rel_name, *abs_name;
367 struct stat st;
368
369 enumerator = enumerator_create_directory(pathname);
370 if (!enumerator)
371 {
372 DBG1(DBG_PTS," directory '%s' can not be opened, %s", pathname,
373 strerror(errno));
374 hasher->destroy(hasher);
375 measurements->destroy(measurements);
376 return NULL;
377 }
378 while (enumerator->enumerate(enumerator, &rel_name, &abs_name, &st))
379 {
380 /* measure regular files only */
381 if (S_ISREG(st.st_mode) && *rel_name != '.')
382 {
383 if (!hash_file(hasher, abs_name, hash))
384 {
385 enumerator->destroy(enumerator);
386 hasher->destroy(hasher);
387 measurements->destroy(measurements);
388 return NULL;
389 }
390 DBG2(DBG_PTS, " %#B for '%s'", &measurement, rel_name);
391 measurements->add(measurements, rel_name, measurement);
392 }
393 }
394 enumerator->destroy(enumerator);
395 }
396 else
397 {
398 char *filename;
399
400 if (!hash_file(hasher, pathname, hash))
401 {
402 hasher->destroy(hasher);
403 measurements->destroy(measurements);
404 return NULL;
405 }
406 filename = get_filename(pathname);
407 DBG2(DBG_PTS, " %#B for '%s'", &measurement, filename);
408 measurements->add(measurements, filename, measurement);
409 }
410 hasher->destroy(hasher);
411
412 return measurements;
413 }
414
415 /**
416 * Obtain statistical information describing a file
417 */
418 static bool file_metadata(char *pathname, pts_file_metadata_t **entry)
419 {
420 struct stat st;
421 pts_file_metadata_t *tmp;
422
423 tmp = malloc_thing(pts_file_metadata_t);
424
425 if (stat(pathname, &st))
426 {
427 DBG1(DBG_PTS, "Unable to obtain statistical information about %s", pathname);
428 return FALSE;
429 }
430
431 tmp->filename = strdup(pathname);
432 tmp->meta_length = PTS_FILE_METADATA_SIZE + strlen(tmp->filename);
433
434 if (S_ISREG(st.st_mode))
435 {
436 tmp->type = PTS_FILE_REGULAR;
437 }
438 else if (S_ISDIR(st.st_mode))
439 {
440 tmp->type = PTS_FILE_DIRECTORY;
441 }
442 else if (S_ISCHR(st.st_mode))
443 {
444 tmp->type = PTS_FILE_CHAR_SPEC;
445 }
446 else if (S_ISBLK(st.st_mode))
447 {
448 tmp->type = PTS_FILE_BLOCK_SPEC;
449 }
450 else if (S_ISFIFO(st.st_mode))
451 {
452 tmp->type = PTS_FILE_FIFO;
453 }
454 else if (S_ISLNK(st.st_mode))
455 {
456 tmp->type = PTS_FILE_SYM_LINK;
457 }
458 else if (S_ISSOCK(st.st_mode))
459 {
460 tmp->type = PTS_FILE_SOCKET;
461 }
462 else
463 {
464 tmp->type = PTS_FILE_OTHER;
465 }
466
467 tmp->filesize = (u_int64_t)st.st_size;
468 tmp->create_time = st.st_ctime;
469 tmp->last_modify_time = st.st_mtime;
470 tmp->last_access_time = st.st_atime;
471 tmp->owner_id = (u_int64_t)st.st_uid;
472 tmp->group_id = (u_int64_t)st.st_gid;
473
474 *entry = tmp;
475
476 return TRUE;
477 }
478
479 METHOD(pts_t, get_metadata, pts_file_meta_t*,
480 private_pts_t *this, char *pathname, bool is_directory)
481 {
482 pts_file_meta_t *metadata;
483 pts_file_metadata_t *entry;
484
485 /* Create a metadata object */
486 metadata = pts_file_meta_create();
487
488 if (is_directory)
489 {
490 enumerator_t *enumerator;
491 char *rel_name, *abs_name;
492 struct stat st;
493
494 enumerator = enumerator_create_directory(pathname);
495 if (!enumerator)
496 {
497 DBG1(DBG_PTS," directory '%s' can not be opened, %s", pathname,
498 strerror(errno));
499 metadata->destroy(metadata);
500 return NULL;
501 }
502 while (enumerator->enumerate(enumerator, &rel_name, &abs_name, &st))
503 {
504 /* measure regular files only */
505 if (S_ISREG(st.st_mode) && *rel_name != '.')
506 {
507 if (!file_metadata(abs_name, &entry))
508 {
509 enumerator->destroy(enumerator);
510 metadata->destroy(metadata);
511 return NULL;
512 }
513 DBG3(DBG_PTS, "File name: %s", entry->filename);
514 DBG3(DBG_PTS, " type: %d", entry->type);
515 DBG3(DBG_PTS, " size: %d", entry->filesize);
516 DBG3(DBG_PTS, " create time: %s", ctime(&entry->create_time));
517 DBG3(DBG_PTS, " last modified: %s", ctime(&entry->last_modify_time));
518 DBG3(DBG_PTS, " last accessed: %s", ctime(&entry->last_access_time));
519 DBG3(DBG_PTS, " owner id: %d", entry->owner_id);
520 DBG3(DBG_PTS, " group id: %d", entry->group_id);
521
522 metadata->add(metadata, entry);
523 }
524 }
525 enumerator->destroy(enumerator);
526 }
527 else
528 {
529 char *filename;
530
531 if (!file_metadata(pathname, &entry))
532 {
533 metadata->destroy(metadata);
534 return NULL;
535 }
536 filename = get_filename(pathname);
537 DBG3(DBG_PTS, "File name: %s", entry->filename);
538 DBG3(DBG_PTS, " type: %d", entry->type);
539 DBG3(DBG_PTS, " size: %d", entry->filesize);
540 DBG3(DBG_PTS, " create time: %s", ctime(&entry->create_time));
541 DBG3(DBG_PTS, " last modified: %s", ctime(&entry->last_modify_time));
542 DBG3(DBG_PTS, " last accessed: %s", ctime(&entry->last_access_time));
543 DBG3(DBG_PTS, " owner id: %d", entry->owner_id);
544 DBG3(DBG_PTS, " group id: %d", entry->group_id);
545 metadata->add(metadata, entry);
546 }
547
548 return metadata;
549 }
550
551
552 METHOD(pts_t, destroy, void,
553 private_pts_t *this)
554 {
555 DESTROY_IF(this->aik);
556 free(this->dh_nonce.ptr);
557 free(this->dh_public_value.ptr);
558 free(this->platform_info);
559 free(this->tpm_version_info.ptr);
560 free(this);
561 }
562
563 /**
564 * Determine Linux distribution and hardware platform
565 */
566 static char* extract_platform_info(void)
567 {
568 FILE *file;
569 char buf[BUF_LEN], *pos, *value = NULL;
570 int i, len;
571 struct utsname uninfo;
572
573 /* Linux/Unix distribution release info (from http://linuxmafia.com) */
574 const char* releases[] = {
575 "/etc/lsb-release", "/etc/debian_version",
576 "/etc/SuSE-release", "/etc/novell-release",
577 "/etc/sles-release", "/etc/redhat-release",
578 "/etc/fedora-release", "/etc/gentoo-release",
579 "/etc/slackware-version", "/etc/annvix-release",
580 "/etc/arch-release", "/etc/arklinux-release",
581 "/etc/aurox-release", "/etc/blackcat-release",
582 "/etc/cobalt-release", "/etc/conectiva-release",
583 "/etc/debian_release", "/etc/immunix-release",
584 "/etc/lfs-release", "/etc/linuxppc-release",
585 "/etc/mandrake-release", "/etc/mandriva-release",
586 "/etc/mandrakelinux-release", "/etc/mklinux-release",
587 "/etc/pld-release", "/etc/redhat_version",
588 "/etc/slackware-release", "/etc/e-smith-release",
589 "/etc/release", "/etc/sun-release",
590 "/etc/tinysofa-release", "/etc/turbolinux-release",
591 "/etc/ultrapenguin-release", "/etc/UnitedLinux-release",
592 "/etc/va-release", "/etc/yellowdog-release"
593 };
594
595 const char description[] = "DISTRIB_DESCRIPTION=\"";
596
597 for (i = 0; i < countof(releases); i++)
598 {
599 file = fopen(releases[i], "r");
600 if (!file)
601 {
602 continue;
603 }
604 fseek(file, 0, SEEK_END);
605 len = min(ftell(file), sizeof(buf)-1);
606 rewind(file);
607 buf[len] = '\0';
608 if (fread(buf, 1, len, file) != len)
609 {
610 DBG1(DBG_PTS, "failed to read file '%s'", releases[i]);
611 fclose(file);
612 return NULL;
613 }
614 fclose(file);
615
616 if (i == 0) /* LSB release */
617 {
618 pos = strstr(buf, description);
619 if (!pos)
620 {
621 DBG1(DBG_PTS, "failed to find begin of lsb-release "
622 "DESCRIPTION field");
623 return NULL;
624 }
625 value = pos + strlen(description);
626 pos = strchr(value, '"');
627 if (!pos)
628 {
629 DBG1(DBG_PTS, "failed to find end of lsb-release "
630 "DESCRIPTION field");
631 return NULL;
632 }
633 }
634 else
635 {
636 value = buf;
637 pos = strchr(value, '\n');
638 if (!pos)
639 {
640 DBG1(DBG_PTS, "failed to find end of release string");
641 return NULL;
642 }
643 }
644 break;
645 }
646
647 if (!value)
648 {
649 DBG1(DBG_PTS, "no distribution release file found");
650 return NULL;
651 }
652
653 if (uname(&uninfo) < 0)
654 {
655 DBG1(DBG_PTS, "could not retrieve machine architecture");
656 return NULL;
657 }
658
659 *pos++ = ' ';
660 len = sizeof(buf)-1 + (pos - buf);
661 strncpy(pos, uninfo.machine, len);
662
663 DBG1(DBG_PTS, "platform is '%s'", value);
664 return strdup(value);
665 }
666
667 /**
668 * Check for a TPM by querying for TPM Version Info
669 */
670 static bool has_tpm(private_pts_t *this)
671 {
672 TSS_HCONTEXT hContext;
673 TSS_HTPM hTPM;
674 TSS_RESULT result;
675 u_int32_t version_info_len;
676
677 result = Tspi_Context_Create(&hContext);
678 if (result != TSS_SUCCESS)
679 {
680 DBG1(DBG_PTS, "TPM context could not be created: tss error 0x%x", result);
681 return FALSE;
682 }
683 result = Tspi_Context_Connect(hContext, NULL);
684 if (result != TSS_SUCCESS)
685 {
686 goto err;
687 }
688 result = Tspi_Context_GetTpmObject (hContext, &hTPM);
689 if (result != TSS_SUCCESS)
690 {
691 goto err;
692 }
693 result = Tspi_TPM_GetCapability(hTPM, TSS_TPMCAP_VERSION_VAL, 0, NULL,
694 &version_info_len,
695 &this->tpm_version_info.ptr);
696 this->tpm_version_info.len = version_info_len;
697 if (result != TSS_SUCCESS)
698 {
699 goto err;
700 }
701 this->tpm_version_info = chunk_clone(this->tpm_version_info);
702 return TRUE;
703
704 err:
705 DBG1(DBG_PTS, "TPM not available: tss error 0x%x", result);
706 Tspi_Context_Close(hContext);
707 return FALSE;
708 }
709
710 /**
711 * See header
712 */
713 pts_t *pts_create(bool is_imc)
714 {
715 private_pts_t *this;
716
717 INIT(this,
718 .public = {
719 .get_proto_caps = _get_proto_caps,
720 .set_proto_caps = _set_proto_caps,
721 .get_meas_algorithm = _get_meas_algorithm,
722 .set_meas_algorithm = _set_meas_algorithm,
723 .get_dh_group = _get_dh_group,
724 .set_dh_group = _set_dh_group,
725 .get_platform_info = _get_platform_info,
726 .set_platform_info = _set_platform_info,
727 .get_tpm_version_info = _get_tpm_version_info,
728 .set_tpm_version_info = _set_tpm_version_info,
729 .get_aik = _get_aik,
730 .set_aik = _set_aik,
731 .is_path_valid = _is_path_valid,
732 .do_measurements = _do_measurements,
733 .get_metadata = _get_metadata,
734 .destroy = _destroy,
735 },
736 .proto_caps = PTS_PROTO_CAPS_V,
737 .algorithm = PTS_MEAS_ALGO_SHA256,
738 .dh_group = PTS_DH_GROUP_IKE19,
739 );
740
741 if (is_imc)
742 {
743 this->platform_info = extract_platform_info();
744
745 if (has_tpm(this))
746 {
747 this->has_tpm = TRUE;
748 this->proto_caps |= PTS_PROTO_CAPS_T;
749 load_aik(this);
750 }
751 }
752 else
753 {
754 this->proto_caps |= PTS_PROTO_CAPS_T | PTS_PROTO_CAPS_C;
755 }
756
757 return &this->public;
758 }
759