optimized handling of UNIX file metadata
[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 #include <bio/bio_writer.h>
21 #include <bio/bio_reader.h>
22
23 #include <trousers/tss.h>
24 #include <trousers/trousers.h>
25
26 #include <sys/stat.h>
27 #include <sys/utsname.h>
28 #include <errno.h>
29
30 #include <openssl/rsa.h>
31 #include <openssl/evp.h>
32 #include <openssl/x509.h>
33
34 #define PTS_BUF_SIZE 4096
35
36 typedef struct private_pts_t private_pts_t;
37
38 /**
39 * Private data of a pts_t object.
40 *
41 */
42 struct private_pts_t {
43
44 /**
45 * Public pts_t interface.
46 */
47 pts_t public;
48
49 /**
50 * PTS Protocol Capabilities
51 */
52 pts_proto_caps_flag_t proto_caps;
53
54 /**
55 * PTS Measurement Algorithm
56 */
57 pts_meas_algorithms_t algorithm;
58
59 /**
60 * DH Hash Algorithm
61 */
62 pts_meas_algorithms_t dh_hash_algorithm;
63
64 /**
65 * PTS Diffie-Hellman Secret
66 */
67 diffie_hellman_t *dh;
68
69 /**
70 * PTS Diffie-Hellman Initiator Nonce
71 */
72 chunk_t initiator_nonce;
73
74 /**
75 * PTS Diffie-Hellman Responder Nonce
76 */
77 chunk_t responder_nonce;
78
79 /**
80 * Secret assessment value to be used for TPM Quote as an external data
81 */
82 chunk_t secret;
83
84 /**
85 * Platform and OS Info
86 */
87 char *platform_info;
88
89 /**
90 * TRUE if IMC-PTS, FALSE if IMV-PTS
91 */
92 bool is_imc;
93
94 /**
95 * Do we have an activated TPM
96 */
97 bool has_tpm;
98
99 /**
100 * Contains a TPM_CAP_VERSION_INFO struct
101 */
102 chunk_t tpm_version_info;
103
104 /**
105 * Contains TSS Blob structure for AIK
106 */
107 chunk_t aik_blob;
108
109 /**
110 * Contains a Attestation Identity Key or Certificate
111 */
112 certificate_t *aik;
113
114 /**
115 * List of extended PCR's with corresponding values
116 */
117 linked_list_t *pcrs;
118 };
119
120 METHOD(pts_t, get_proto_caps, pts_proto_caps_flag_t,
121 private_pts_t *this)
122 {
123 return this->proto_caps;
124 }
125
126 METHOD(pts_t, set_proto_caps, void,
127 private_pts_t *this, pts_proto_caps_flag_t flags)
128 {
129 this->proto_caps = flags;
130 DBG2(DBG_PTS, "supported PTS protocol capabilities: %s%s%s%s%s",
131 flags & PTS_PROTO_CAPS_C ? "C" : ".",
132 flags & PTS_PROTO_CAPS_V ? "V" : ".",
133 flags & PTS_PROTO_CAPS_D ? "D" : ".",
134 flags & PTS_PROTO_CAPS_T ? "T" : ".",
135 flags & PTS_PROTO_CAPS_X ? "X" : ".");
136 }
137
138 METHOD(pts_t, get_meas_algorithm, pts_meas_algorithms_t,
139 private_pts_t *this)
140 {
141 return this->algorithm;
142 }
143
144 METHOD(pts_t, set_meas_algorithm, void,
145 private_pts_t *this, pts_meas_algorithms_t algorithm)
146 {
147 hash_algorithm_t hash_alg;
148
149 hash_alg = pts_meas_algo_to_hash(algorithm);
150 DBG2(DBG_PTS, "selected PTS measurement algorithm is %N",
151 hash_algorithm_names, hash_alg);
152 if (hash_alg != HASH_UNKNOWN)
153 {
154 this->algorithm = algorithm;
155 }
156 }
157
158 METHOD(pts_t, get_dh_hash_algorithm, pts_meas_algorithms_t,
159 private_pts_t *this)
160 {
161 return this->dh_hash_algorithm;
162 }
163
164 METHOD(pts_t, set_dh_hash_algorithm, void,
165 private_pts_t *this, pts_meas_algorithms_t algorithm)
166 {
167 hash_algorithm_t hash_alg;
168
169 hash_alg = pts_meas_algo_to_hash(algorithm);
170 DBG2(DBG_PTS, "selected DH hash algorithm is %N",
171 hash_algorithm_names, hash_alg);
172 if (hash_alg != HASH_UNKNOWN)
173 {
174 this->dh_hash_algorithm = algorithm;
175 }
176 }
177
178
179 METHOD(pts_t, create_dh_nonce, bool,
180 private_pts_t *this, pts_dh_group_t group, int nonce_len)
181 {
182 diffie_hellman_group_t dh_group;
183 chunk_t *nonce;
184 rng_t *rng;
185
186 dh_group = pts_dh_group_to_ike(group);
187 DBG2(DBG_PTS, "selected PTS DH group is %N",
188 diffie_hellman_group_names, dh_group);
189 DESTROY_IF(this->dh);
190 this->dh = lib->crypto->create_dh(lib->crypto, dh_group);
191
192 rng = lib->crypto->create_rng(lib->crypto, RNG_STRONG);
193 if (!rng)
194 {
195 DBG1(DBG_PTS, "no rng available");
196 return FALSE;
197 }
198 DBG2(DBG_PTS, "nonce length is %d", nonce_len);
199 nonce = this->is_imc ? &this->responder_nonce : &this->initiator_nonce;
200 chunk_free(nonce);
201 rng->allocate_bytes(rng, nonce_len, nonce);
202 rng->destroy(rng);
203
204 return TRUE;
205 }
206
207 METHOD(pts_t, get_my_public_value, void,
208 private_pts_t *this, chunk_t *value, chunk_t *nonce)
209 {
210 this->dh->get_my_public_value(this->dh, value);
211 *nonce = this->is_imc ? this->responder_nonce : this->initiator_nonce;
212 }
213
214 METHOD(pts_t, set_peer_public_value, void,
215 private_pts_t *this, chunk_t value, chunk_t nonce)
216 {
217 this->dh->set_other_public_value(this->dh, value);
218
219 nonce = chunk_clone(nonce);
220 if (this->is_imc)
221 {
222 this->initiator_nonce = nonce;
223 }
224 else
225 {
226 this->responder_nonce = nonce;
227 }
228 }
229
230 METHOD(pts_t, calculate_secret, bool,
231 private_pts_t *this)
232 {
233 hasher_t *hasher;
234 hash_algorithm_t hash_alg;
235 chunk_t shared_secret;
236
237 /* Check presence of nonces */
238 if (!this->initiator_nonce.len || !this->responder_nonce.len)
239 {
240 DBG1(DBG_PTS, "initiator and/or responder nonce is not available");
241 return FALSE;
242 }
243 DBG3(DBG_PTS, "initiator nonce: %B", &this->initiator_nonce);
244 DBG3(DBG_PTS, "responder nonce: %B", &this->responder_nonce);
245
246 /* Calculate the DH secret */
247 if (this->dh->get_shared_secret(this->dh, &shared_secret) != SUCCESS)
248 {
249 DBG1(DBG_PTS, "shared DH secret computation failed");
250 return FALSE;
251 }
252 DBG4(DBG_PTS, "shared DH secret: %B", &shared_secret);
253
254 /* Calculate the secret assessment value */
255 hash_alg = pts_meas_algo_to_hash(this->dh_hash_algorithm);
256 hasher = lib->crypto->create_hasher(lib->crypto, hash_alg);
257
258 hasher->allocate_hash(hasher, chunk_from_chars('1'), NULL);
259 hasher->allocate_hash(hasher, this->initiator_nonce, NULL);
260 hasher->allocate_hash(hasher, this->responder_nonce, NULL);
261 hasher->allocate_hash(hasher, shared_secret, &this->secret);
262 hasher->destroy(hasher);
263
264 /* The DH secret must be destroyed */
265 chunk_clear(&shared_secret);
266
267 /*
268 * Truncate the hash to 20 bytes to fit the ExternalData
269 * argument of the TPM Quote command
270 */
271 this->secret.len = min(this->secret.len, 20);
272 DBG4(DBG_PTS, "secret assessment value: %B", &this->secret);
273 return TRUE;
274 }
275
276 /**
277 * Print TPM 1.2 Version Info
278 */
279 static void print_tpm_version_info(private_pts_t *this)
280 {
281 TPM_CAP_VERSION_INFO versionInfo;
282 UINT64 offset = 0;
283 TSS_RESULT result;
284
285 result = Trspi_UnloadBlob_CAP_VERSION_INFO(&offset,
286 this->tpm_version_info.ptr, &versionInfo);
287 if (result != TSS_SUCCESS)
288 {
289 DBG1(DBG_PTS, "could not parse tpm version info: tss error 0x%x",
290 result);
291 }
292 else
293 {
294 DBG2(DBG_PTS, "TPM 1.2 Version Info: Chip Version: %hhu.%hhu.%hhu.%hhu,"
295 " Spec Level: %hu, Errata Rev: %hhu, Vendor ID: %.4s",
296 versionInfo.version.major, versionInfo.version.minor,
297 versionInfo.version.revMajor, versionInfo.version.revMinor,
298 versionInfo.specLevel, versionInfo.errataRev,
299 versionInfo.tpmVendorID);
300 }
301 }
302
303 METHOD(pts_t, get_platform_info, char*,
304 private_pts_t *this)
305 {
306 return this->platform_info;
307 }
308
309 METHOD(pts_t, set_platform_info, void,
310 private_pts_t *this, char *info)
311 {
312 free(this->platform_info);
313 this->platform_info = strdup(info);
314 }
315
316 METHOD(pts_t, get_tpm_version_info, bool,
317 private_pts_t *this, chunk_t *info)
318 {
319 if (!this->has_tpm)
320 {
321 return FALSE;
322 }
323 *info = this->tpm_version_info;
324 print_tpm_version_info(this);
325 return TRUE;
326 }
327
328 METHOD(pts_t, set_tpm_version_info, void,
329 private_pts_t *this, chunk_t info)
330 {
331 this->tpm_version_info = chunk_clone(info);
332 print_tpm_version_info(this);
333 }
334
335 /**
336 * Load an AIK Blob (TSS_TSPATTRIB_KEYBLOB_BLOB attribute)
337 */
338 static void load_aik_blob(private_pts_t *this)
339 {
340 char *blob_path;
341 FILE *fp;
342 u_int32_t aikBlobLen;
343
344 blob_path = lib->settings->get_str(lib->settings,
345 "libimcv.plugins.imc-attestation.aik_blob", NULL);
346
347 if (blob_path)
348 {
349 /* Read aik key blob from a file */
350 if ((fp = fopen(blob_path, "r")) == NULL)
351 {
352 DBG1(DBG_PTS, "unable to open AIK Blob file: %s", blob_path);
353 return;
354 }
355
356 fseek(fp, 0, SEEK_END);
357 aikBlobLen = ftell(fp);
358 fseek(fp, 0L, SEEK_SET);
359
360 this->aik_blob = chunk_alloc(aikBlobLen);
361 if (fread(this->aik_blob.ptr, 1, aikBlobLen, fp))
362 {
363 DBG2(DBG_PTS, "loaded AIK Blob from '%s'", blob_path);
364 DBG3(DBG_PTS, "AIK Blob: %B", &this->aik_blob);
365 }
366 else
367 {
368 DBG1(DBG_PTS, "unable to read AIK Blob file '%s'", blob_path);
369 }
370 fclose(fp);
371 return;
372 }
373
374 DBG1(DBG_PTS, "AIK Blob is not available");
375 }
376
377 /**
378 * Load an AIK certificate or public key
379 * the certificate having precedence over the public key if both are present
380 */
381 static void load_aik(private_pts_t *this)
382 {
383 char *cert_path, *key_path;
384
385 cert_path = lib->settings->get_str(lib->settings,
386 "libimcv.plugins.imc-attestation.aik_cert", NULL);
387 key_path = lib->settings->get_str(lib->settings,
388 "libimcv.plugins.imc-attestation.aik_key", NULL);
389
390 if (cert_path)
391 {
392 this->aik = lib->creds->create(lib->creds, CRED_CERTIFICATE,
393 CERT_X509, BUILD_FROM_FILE,
394 cert_path, BUILD_END);
395 if (this->aik)
396 {
397 DBG2(DBG_PTS, "loaded AIK certificate from '%s'", cert_path);
398 return;
399 }
400 }
401 if (key_path)
402 {
403 this->aik = lib->creds->create(lib->creds, CRED_CERTIFICATE,
404 CERT_TRUSTED_PUBKEY, BUILD_FROM_FILE,
405 key_path, BUILD_END);
406 if (this->aik)
407 {
408 DBG2(DBG_PTS, "loaded AIK public key from '%s'", key_path);
409 return;
410 }
411 }
412
413 DBG1(DBG_PTS, "neither AIK certificate nor public key is available");
414 }
415
416 METHOD(pts_t, get_aik, certificate_t*,
417 private_pts_t *this)
418 {
419 return this->aik;
420 }
421
422 METHOD(pts_t, set_aik, void,
423 private_pts_t *this, certificate_t *aik)
424 {
425 DESTROY_IF(this->aik);
426 this->aik = aik->get_ref(aik);
427 }
428
429 METHOD(pts_t, hash_file, bool,
430 private_pts_t *this, hasher_t *hasher, char *pathname, u_char *hash)
431 {
432 u_char buffer[PTS_BUF_SIZE];
433 FILE *file;
434 int bytes_read;
435
436 file = fopen(pathname, "rb");
437 if (!file)
438 {
439 DBG1(DBG_PTS," file '%s' can not be opened, %s", pathname,
440 strerror(errno));
441 return FALSE;
442 }
443 while (TRUE)
444 {
445 bytes_read = fread(buffer, 1, sizeof(buffer), file);
446 if (bytes_read > 0)
447 {
448 hasher->get_hash(hasher, chunk_create(buffer, bytes_read), NULL);
449 }
450 else
451 {
452 hasher->get_hash(hasher, chunk_empty, hash);
453 break;
454 }
455 }
456 fclose(file);
457
458 return TRUE;
459 }
460
461 /**
462 * Get the relative filename of a fully qualified file pathname
463 */
464 static char* get_filename(char *pathname)
465 {
466 char *pos, *filename;
467
468 pos = filename = pathname;
469 while (pos && *(++pos) != '\0')
470 {
471 filename = pos;
472 pos = strchr(filename, '/');
473 }
474 return filename;
475 }
476
477 METHOD(pts_t, is_path_valid, bool,
478 private_pts_t *this, char *path, pts_error_code_t *error_code)
479 {
480 struct stat st;
481
482 *error_code = 0;
483
484 if (!stat(path, &st))
485 {
486 return TRUE;
487 }
488 else if (errno == ENOENT || errno == ENOTDIR)
489 {
490 DBG1(DBG_PTS, "file/directory does not exist %s", path);
491 *error_code = TCG_PTS_FILE_NOT_FOUND;
492 }
493 else if (errno == EFAULT)
494 {
495 DBG1(DBG_PTS, "bad address %s", path);
496 *error_code = TCG_PTS_INVALID_PATH;
497 }
498 else
499 {
500 DBG1(DBG_PTS, "error: %s occured while validating path: %s",
501 strerror(errno), path);
502 return FALSE;
503 }
504
505 return TRUE;
506 }
507
508 METHOD(pts_t, do_measurements, pts_file_meas_t*,
509 private_pts_t *this, u_int16_t request_id, char *pathname, bool is_directory)
510 {
511 hasher_t *hasher;
512 hash_algorithm_t hash_alg;
513 u_char hash[HASH_SIZE_SHA384];
514 chunk_t measurement;
515 pts_file_meas_t *measurements;
516
517 /* Create a hasher */
518 hash_alg = pts_meas_algo_to_hash(this->algorithm);
519 hasher = lib->crypto->create_hasher(lib->crypto, hash_alg);
520 if (!hasher)
521 {
522 DBG1(DBG_PTS, "hasher %N not available", hash_algorithm_names, hash_alg);
523 return NULL;
524 }
525
526 /* Create a measurement object */
527 measurements = pts_file_meas_create(request_id);
528
529 /* Link the hash to the measurement and set the measurement length */
530 measurement = chunk_create(hash, hasher->get_hash_size(hasher));
531
532 if (is_directory)
533 {
534 enumerator_t *enumerator;
535 char *rel_name, *abs_name;
536 struct stat st;
537
538 enumerator = enumerator_create_directory(pathname);
539 if (!enumerator)
540 {
541 DBG1(DBG_PTS," directory '%s' can not be opened, %s", pathname,
542 strerror(errno));
543 hasher->destroy(hasher);
544 measurements->destroy(measurements);
545 return NULL;
546 }
547 while (enumerator->enumerate(enumerator, &rel_name, &abs_name, &st))
548 {
549 /* measure regular files only */
550 if (S_ISREG(st.st_mode) && *rel_name != '.')
551 {
552 if (!hash_file(this, hasher, abs_name, hash))
553 {
554 enumerator->destroy(enumerator);
555 hasher->destroy(hasher);
556 measurements->destroy(measurements);
557 return NULL;
558 }
559 DBG2(DBG_PTS, " %#B for '%s'", &measurement, rel_name);
560 measurements->add(measurements, rel_name, measurement);
561 }
562 }
563 enumerator->destroy(enumerator);
564 }
565 else
566 {
567 char *filename;
568
569 if (!hash_file(this, hasher, pathname, hash))
570 {
571 hasher->destroy(hasher);
572 measurements->destroy(measurements);
573 return NULL;
574 }
575 filename = get_filename(pathname);
576 DBG2(DBG_PTS, " %#B for '%s'", &measurement, filename);
577 measurements->add(measurements, filename, measurement);
578 }
579 hasher->destroy(hasher);
580
581 return measurements;
582 }
583
584 /**
585 * Obtain statistical information describing a file
586 */
587 static bool file_metadata(char *pathname, pts_file_metadata_t **entry)
588 {
589 struct stat st;
590 pts_file_metadata_t *this;
591
592 this = malloc_thing(pts_file_metadata_t);
593
594 if (stat(pathname, &st))
595 {
596 DBG1(DBG_PTS, "Unable to obtain statistics about '%s'", pathname);
597 return FALSE;
598 }
599
600 this->filename = strdup(pathname);
601
602 if (S_ISREG(st.st_mode))
603 {
604 this->type = PTS_FILE_REGULAR;
605 }
606 else if (S_ISDIR(st.st_mode))
607 {
608 this->type = PTS_FILE_DIRECTORY;
609 }
610 else if (S_ISCHR(st.st_mode))
611 {
612 this->type = PTS_FILE_CHAR_SPEC;
613 }
614 else if (S_ISBLK(st.st_mode))
615 {
616 this->type = PTS_FILE_BLOCK_SPEC;
617 }
618 else if (S_ISFIFO(st.st_mode))
619 {
620 this->type = PTS_FILE_FIFO;
621 }
622 else if (S_ISLNK(st.st_mode))
623 {
624 this->type = PTS_FILE_SYM_LINK;
625 }
626 else if (S_ISSOCK(st.st_mode))
627 {
628 this->type = PTS_FILE_SOCKET;
629 }
630 else
631 {
632 this->type = PTS_FILE_OTHER;
633 }
634
635 this->filesize = st.st_size;
636 this->created = st.st_ctime;
637 this->modified = st.st_mtime;
638 this->accessed = st.st_atime;
639 this->owner = st.st_uid;
640 this->group = st.st_gid;
641
642 *entry = this;
643 return TRUE;
644 }
645
646 METHOD(pts_t, get_metadata, pts_file_meta_t*,
647 private_pts_t *this, char *pathname, bool is_directory)
648 {
649 pts_file_meta_t *metadata;
650 pts_file_metadata_t *entry;
651
652 /* Create a metadata object */
653 metadata = pts_file_meta_create();
654
655 if (is_directory)
656 {
657 enumerator_t *enumerator;
658 char *rel_name, *abs_name;
659 struct stat st;
660
661 enumerator = enumerator_create_directory(pathname);
662 if (!enumerator)
663 {
664 DBG1(DBG_PTS," directory '%s' can not be opened, %s", pathname,
665 strerror(errno));
666 metadata->destroy(metadata);
667 return NULL;
668 }
669 while (enumerator->enumerate(enumerator, &rel_name, &abs_name, &st))
670 {
671 /* measure regular files only */
672 if (S_ISREG(st.st_mode) && *rel_name != '.')
673 {
674 if (!file_metadata(abs_name, &entry))
675 {
676 enumerator->destroy(enumerator);
677 metadata->destroy(metadata);
678 return NULL;
679 }
680 metadata->add(metadata, entry);
681 }
682 }
683 enumerator->destroy(enumerator);
684 }
685 else
686 {
687 char *filename;
688
689 if (!file_metadata(pathname, &entry))
690 {
691 metadata->destroy(metadata);
692 return NULL;
693 }
694 filename = get_filename(pathname);
695 metadata->add(metadata, entry);
696 }
697
698 return metadata;
699 }
700
701 METHOD(pts_t, read_pcr, bool,
702 private_pts_t *this, u_int32_t pcr_num, chunk_t *output)
703 {
704 TSS_HCONTEXT hContext;
705 TSS_HTPM hTPM;
706 TSS_RESULT result;
707 u_int32_t pcr_length;
708 chunk_t pcr_value;
709
710 result = Tspi_Context_Create(&hContext);
711 if (result != TSS_SUCCESS)
712 {
713 DBG1(DBG_PTS, "TPM context could not be created: tss error 0x%x",
714 result);
715 return FALSE;
716 }
717
718 result = Tspi_Context_Connect(hContext, NULL);
719 if (result != TSS_SUCCESS)
720 {
721 goto err;
722 }
723 result = Tspi_Context_GetTpmObject (hContext, &hTPM);
724 if (result != TSS_SUCCESS)
725 {
726 goto err;
727 }
728 pcr_value = chunk_alloc(PCR_LEN);
729 result = Tspi_TPM_PcrRead(hTPM, pcr_num, &pcr_length, &pcr_value.ptr);
730 if (result != TSS_SUCCESS)
731 {
732 goto err;
733 }
734
735 *output = pcr_value;
736 *output = chunk_clone(*output);
737
738 chunk_clear(&pcr_value);
739 Tspi_Context_Close(hContext);
740 DBG3(DBG_PTS, "PCR %d value:%B", pcr_num, output);
741 return TRUE;
742
743 err:
744 chunk_clear(&pcr_value);
745 DBG1(DBG_PTS, "TPM not available: tss error 0x%x", result);
746 Tspi_Context_Close(hContext);
747 return FALSE;
748 }
749
750 METHOD(pts_t, extend_pcr, bool,
751 private_pts_t *this, u_int32_t pcr_num, chunk_t input, chunk_t *output)
752 {
753 TSS_HCONTEXT hContext;
754 TSS_HTPM hTPM;
755 TSS_RESULT result;
756 u_int32_t pcr_length;
757 chunk_t pcr_value;
758
759 result = Tspi_Context_Create(&hContext);
760 if (result != TSS_SUCCESS)
761 {
762 DBG1(DBG_PTS, "TPM context could not be created: tss error 0x%x",
763 result);
764 return FALSE;
765 }
766 result = Tspi_Context_Connect(hContext, NULL);
767 if (result != TSS_SUCCESS)
768 {
769 goto err;
770 }
771 result = Tspi_Context_GetTpmObject (hContext, &hTPM);
772 if (result != TSS_SUCCESS)
773 {
774 goto err;
775 }
776
777 pcr_value = chunk_alloc(PCR_LEN);
778 result = Tspi_TPM_PcrExtend(hTPM, pcr_num, PCR_LEN, input.ptr,
779 NULL, &pcr_length, &pcr_value.ptr);
780 if (result != TSS_SUCCESS)
781 {
782 goto err;
783 }
784
785 *output = pcr_value;
786 *output = chunk_clone(*output);
787
788 chunk_clear(&pcr_value);
789 Tspi_Context_Close(hContext);
790 DBG3(DBG_PTS, "PCR %d extended with: %B", pcr_num, &input);
791 DBG3(DBG_PTS, "PCR %d value after extend: %B", pcr_num, output);
792 return TRUE;
793
794 err:
795 chunk_clear(&pcr_value);
796 DBG1(DBG_PTS, "TPM not available: tss error 0x%x", result);
797 Tspi_Context_Close(hContext);
798 return FALSE;
799 }
800
801 METHOD(pts_t, quote_tpm, bool,
802 private_pts_t *this, u_int32_t *pcrs, u_int32_t num_of_pcrs,
803 chunk_t *pcr_composite, chunk_t *quote_signature)
804 {
805 TSS_HCONTEXT hContext;
806 TSS_HTPM hTPM;
807 TSS_HKEY hAIK;
808 TSS_HKEY hSRK;
809 TSS_HPOLICY srkUsagePolicy;
810 TSS_UUID SRK_UUID = TSS_UUID_SRK;
811 BYTE secret[] = TSS_WELL_KNOWN_SECRET;
812 TSS_HPCRS hPcrComposite;
813 TSS_VALIDATION valData;
814 u_int32_t i;
815 TSS_RESULT result;
816 chunk_t pcr_comp, quote_sign;
817
818 result = Tspi_Context_Create(&hContext);
819 if (result != TSS_SUCCESS)
820 {
821 DBG1(DBG_PTS, "TPM context could not be created: tss error 0x%x",
822 result);
823 return FALSE;
824 }
825 result = Tspi_Context_Connect(hContext, NULL);
826 if (result != TSS_SUCCESS)
827 {
828 goto err1;
829 }
830 result = Tspi_Context_GetTpmObject (hContext, &hTPM);
831 if (result != TSS_SUCCESS)
832 {
833 goto err1;
834 }
835
836 /* Retrieve SRK from TPM and set the authentication to well known secret*/
837 result = Tspi_Context_LoadKeyByUUID(hContext, TSS_PS_TYPE_SYSTEM,
838 SRK_UUID, &hSRK);
839 if (result != TSS_SUCCESS)
840 {
841 goto err1;
842 }
843
844 result = Tspi_GetPolicyObject(hSRK, TSS_POLICY_USAGE, &srkUsagePolicy);
845 if (result != TSS_SUCCESS)
846 {
847 goto err1;
848 }
849 result = Tspi_Policy_SetSecret(srkUsagePolicy, TSS_SECRET_MODE_SHA1,
850 20, secret);
851 if (result != TSS_SUCCESS)
852 {
853 goto err1;
854 }
855
856 result = Tspi_Context_LoadKeyByBlob (hContext, hSRK, this->aik_blob.len,
857 this->aik_blob.ptr, &hAIK);
858 if (result != TSS_SUCCESS)
859 {
860 goto err1;
861 }
862
863 /* Create PCR composite object */
864 result = Tspi_Context_CreateObject(hContext,
865 TSS_OBJECT_TYPE_PCRS, 0, &hPcrComposite);
866 if (result != TSS_SUCCESS)
867 {
868 goto err2;
869 }
870
871 /* Select PCR's */
872 for (i = 0; i < num_of_pcrs ; i++)
873 {
874 if (pcrs[i] < 0 || pcrs[i] >= MAX_NUM_PCR )
875 {
876 DBG1(DBG_PTS, "Invalid PCR number: %d", pcrs[i]);
877 goto err3;
878 }
879 result = Tspi_PcrComposite_SelectPcrIndex(hPcrComposite, pcrs[i]);
880 if (result != TSS_SUCCESS)
881 {
882 goto err3;
883 }
884 }
885
886 /* Set the Validation Data */
887 valData.ulExternalDataLength = this->secret.len;
888 valData.rgbExternalData = (BYTE *)this->secret.ptr;
889
890
891 /* TPM Quote */
892 result = Tspi_TPM_Quote(hTPM, hAIK, hPcrComposite, &valData);
893 if (result != TSS_SUCCESS)
894 {
895 goto err4;
896 }
897
898 /* Set output chunks */
899 pcr_comp = chunk_alloc(HASH_SIZE_SHA1);
900 memcpy(pcr_comp.ptr, valData.rgbData + 8, HASH_SIZE_SHA1);
901 *pcr_composite = pcr_comp;
902 *pcr_composite = chunk_clone(*pcr_composite);
903 DBG3(DBG_PTS, "Hash of PCR Composite: %B",pcr_composite);
904
905 quote_sign = chunk_alloc(valData.ulValidationDataLength);
906 memcpy(quote_sign.ptr, valData.rgbValidationData,
907 valData.ulValidationDataLength);
908 *quote_signature = quote_sign;
909 *quote_signature = chunk_clone(*quote_signature);
910 DBG3(DBG_PTS, "TOM Quote Signature: %B",quote_signature);
911
912 chunk_clear(&quote_sign);
913 Tspi_Context_FreeMemory(hContext, NULL);
914 Tspi_Context_CloseObject(hContext, hPcrComposite);
915 Tspi_Context_CloseObject(hContext, hAIK);
916 Tspi_Context_Close(hContext);
917 free(pcrs);
918 return TRUE;
919
920 /* Cleanup */
921 err4:
922 Tspi_Context_FreeMemory(hContext, NULL);
923
924 err3:
925 Tspi_Context_CloseObject(hContext, hPcrComposite);
926
927 err2:
928 Tspi_Context_CloseObject(hContext, hAIK);
929
930 err1:
931 Tspi_Context_Close(hContext);
932 free(pcrs);
933 DBG1(DBG_PTS, "TPM not available: tss error 0x%x", result);
934 return FALSE;
935 }
936
937 /**
938 * Comparison function for pcr_entry_t struct
939 */
940 static int pcr_entry_compare(const pcr_entry_t *a, const pcr_entry_t *b)
941 {
942 return (a->pcr_number - b->pcr_number);
943 }
944
945 static int pcr_entry_compare_qsort(const void *a, const void *b)
946 {
947 return pcr_entry_compare(*(const pcr_entry_t *const *)a
948 , *(const pcr_entry_t *const *)b);
949 }
950
951 METHOD(pts_t, add_pcr_entry, void,
952 private_pts_t *this, pcr_entry_t *new)
953 {
954 enumerator_t *e;
955 pcr_entry_t *entry;
956
957 if (!this->pcrs)
958 {
959 this->pcrs = linked_list_create();
960 }
961
962 e = this->pcrs->create_enumerator(this->pcrs);
963 while (e->enumerate(e, &entry))
964 {
965 if (entry->pcr_number == new->pcr_number)
966 {
967 DBG4(DBG_PTS, "updating already added PCR%d value",
968 entry->pcr_number);
969 this->pcrs->remove_at(this->pcrs, e);
970 free(entry);
971 break;
972 }
973 }
974 DESTROY_IF(e);
975
976 this->pcrs->insert_last(this->pcrs, new);
977
978 qsort(this->pcrs, this->pcrs->get_count(this->pcrs),
979 sizeof(pcr_entry_t *), pcr_entry_compare_qsort);
980 }
981
982 /**
983 * 1. build a TCPA_PCR_COMPOSITE structure which contains (pcrCompositeBuf)
984 * TCPA_PCR_SELECTION structure (bitmask length + bitmask)
985 * UINT32 (network order) gives the number of bytes following (pcr entries * 20)
986 * TCPA_PCRVALUE[] with the pcr values
987 *
988 * The first two bytes of the message represent the length
989 * of the bitmask that follows. The bitmask represents the
990 * requested PCRs to be quoted.
991 *
992 * TPM Main-Part 2 TPM Structures_v1.2 8.1
993 * The bitmask is in big endian order"
994 *
995 * BYTE 1 BYTE 2 ...
996 * Bit: 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0 ...
997 * Pcr: 7 6 5 4 3 2 1 0 15 14 13 12 11 10 9 8 ...
998 *
999 * 2. SHA1(pcrCompositeBuf)
1000 *
1001 * 3. build a TCPA_QUOTE_INFO structure which contains
1002 * 4 bytes of version
1003 * 4 bytes 'Q' 'U' 'O' 'T'
1004 * 20 byte SHA1 of TCPA_PCR_COMPOSITE
1005 * 20 byte nonce
1006 */
1007
1008 METHOD(pts_t, get_quote_info, bool,
1009 private_pts_t *this, chunk_t *out_pcr_composite, chunk_t *out_quote_info)
1010 {
1011 enumerator_t *e;
1012 pcr_entry_t *pcr_entry;
1013 chunk_t pcr_composite;
1014 u_int32_t pcr_composite_len;
1015 bio_writer_t *writer;
1016 u_int8_t mask_bytes[PCR_MASK_LEN] = {0,0,0}, i;
1017 hasher_t *hasher;
1018
1019 if (this->pcrs->get_count(this->pcrs) == 0)
1020 {
1021 DBG1(DBG_PTS, "PCR entries unavailable, unable to construct "
1022 "TPM Quote Info");
1023 return FALSE;
1024 }
1025
1026 pcr_composite_len = 2 + PCR_MASK_LEN + 4 +
1027 this->pcrs->get_count(this->pcrs) * PCR_LEN;
1028
1029 writer = bio_writer_create(pcr_composite_len);
1030 /* Lenght of the bist mask field */
1031 writer->write_uint16(writer, PCR_MASK_LEN);
1032 /* Bit mask indicating selected PCRs */
1033 e = this->pcrs->create_enumerator(this->pcrs);
1034 while (e->enumerate(e, &pcr_entry))
1035 {
1036 u_int32_t index = pcr_entry->pcr_number;
1037 mask_bytes[index / 8] |= (1 << (index % 8));
1038 }
1039 e->destroy(e);
1040
1041 for (i = 0; i< PCR_MASK_LEN ; i++)
1042 {
1043 writer->write_uint8(writer, mask_bytes[i]);
1044 }
1045
1046 /* Lenght of the pcr entries */
1047 writer->write_uint32(writer, this->pcrs->get_count(this->pcrs) * PCR_LEN);
1048 /* Actual PCR values */
1049 e = this->pcrs->create_enumerator(this->pcrs);
1050 while (e->enumerate(e, &pcr_entry))
1051 {
1052 writer->write_data(writer, chunk_create(pcr_entry->pcr_value, PCR_LEN));
1053 }
1054 free(pcr_entry);
1055 e->destroy(e);
1056
1057 /* PCR Composite structure */
1058 pcr_composite = chunk_clone(writer->get_buf(writer));
1059 writer->destroy(writer);
1060
1061 writer = bio_writer_create(TPM_QUOTE_INFO_LEN);
1062 /* Version number */
1063 writer->write_uint8(writer, 1);
1064 writer->write_uint8(writer, 1);
1065 writer->write_uint8(writer, 0);
1066 writer->write_uint8(writer, 0);
1067
1068 /* Magic QUOT value, depends on TPM Ordinal */
1069 writer->write_uint8(writer, 'Q');
1070 writer->write_uint8(writer, 'U');
1071 writer->write_uint8(writer, 'O');
1072 writer->write_uint8(writer, 'T');
1073
1074 /* SHA1 hash of PCR Composite Structure */
1075 hasher = lib->crypto->create_hasher(lib->crypto, HASH_SHA1);
1076 hasher->allocate_hash(hasher, pcr_composite, out_pcr_composite);
1077 DBG4(DBG_PTS, "Hash of calculated PCR Composite: %B", out_pcr_composite);
1078
1079 chunk_clear(&pcr_composite);
1080 hasher->destroy(hasher);
1081 writer->write_data(writer, *out_pcr_composite);
1082
1083 if (!this->secret.ptr)
1084 {
1085 DBG1(DBG_PTS, "Secret assessment value unavailable",
1086 "unable to construct TPM Quote Info");
1087 chunk_clear(out_pcr_composite);
1088 writer->destroy(writer);
1089 return FALSE;
1090 }
1091 /* Secret assessment value 20 bytes (nonce) */
1092 writer->write_data(writer, this->secret);
1093 /* TPM Quote Info */
1094 *out_quote_info = chunk_clone(writer->get_buf(writer));
1095 DBG4(DBG_PTS, "Calculated TPM Quote Info: %B", out_quote_info);
1096 writer->destroy(writer);
1097
1098 return TRUE;
1099 }
1100
1101 METHOD(pts_t, verify_quote_signature, bool,
1102 private_pts_t *this, chunk_t data, chunk_t signature)
1103 {
1104 public_key_t *aik_pub_key;
1105 chunk_t key_encoding;
1106 EVP_PKEY *pkey = NULL;
1107 RSA *rsa = NULL;
1108 unsigned char *p;
1109
1110 aik_pub_key = this->aik->get_public_key(this->aik);
1111 if (!aik_pub_key)
1112 {
1113 DBG1(DBG_PTS, "failed to get public key from AIK certificate");
1114 return FALSE;
1115 }
1116
1117 /** Implementation using strongswan -> not working */
1118 /**if (!aik_pub_key->verify(aik_pub_key, SIGN_RSA_EMSA_PKCS1_SHA1,
1119 data, signature))
1120 {
1121 DBG1(DBG_PTS, "signature verification failed for TPM Quote Info");
1122 goto cleanup;
1123 }
1124 */
1125
1126 if (!aik_pub_key->get_encoding(aik_pub_key,
1127 PUBKEY_SPKI_ASN1_DER, &key_encoding))
1128 {
1129 DBG1(DBG_PTS, "failed to get encoding of AIK public key");
1130 goto cleanup;
1131 }
1132
1133 p = key_encoding.ptr;
1134 pkey = d2i_PUBKEY(NULL, (const unsigned char**)&p, key_encoding.len);
1135 if (!pkey)
1136 {
1137 DBG1(DBG_PTS, "failed to get EVP_PKEY object from AIK public key");
1138 goto cleanup;
1139 }
1140
1141 rsa = EVP_PKEY_get1_RSA(pkey);
1142 if (!rsa)
1143 {
1144 DBG1(DBG_PTS, "failed to get RSA object from EVP_PKEY");
1145 goto cleanup;
1146 }
1147
1148 if (RSA_verify(NID_sha1, data.ptr, data.len,
1149 signature.ptr, signature.len, rsa) != 1)
1150 {
1151 DBG1(DBG_PTS, "signature verification failed for TPM Quote Info");
1152 goto cleanup;
1153 }
1154
1155 RSA_free(rsa);
1156 EVP_PKEY_free(pkey);
1157 if (key_encoding.ptr)
1158 {
1159 chunk_clear(&key_encoding);
1160 }
1161 aik_pub_key->destroy(aik_pub_key);
1162 return TRUE;
1163
1164 cleanup:
1165 if (rsa)
1166 {
1167 RSA_free(rsa);
1168 }
1169 if (pkey)
1170 {
1171 EVP_PKEY_free(pkey);
1172 }
1173 if (key_encoding.ptr)
1174 {
1175 chunk_clear(&key_encoding);
1176 }
1177 DESTROY_IF(aik_pub_key);
1178 return FALSE;
1179 }
1180
1181 METHOD(pts_t, destroy, void,
1182 private_pts_t *this)
1183 {
1184 DESTROY_IF(this->aik);
1185 DESTROY_IF(this->dh);
1186 DESTROY_IF(this->pcrs);
1187 free(this->initiator_nonce.ptr);
1188 free(this->responder_nonce.ptr);
1189 free(this->secret.ptr);
1190 free(this->platform_info);
1191 free(this->aik_blob.ptr);
1192 free(this->tpm_version_info.ptr);
1193 free(this);
1194 }
1195
1196 /**
1197 * Determine Linux distribution and hardware platform
1198 */
1199 static char* extract_platform_info(void)
1200 {
1201 FILE *file;
1202 char buf[BUF_LEN], *pos, *value = NULL;
1203 int i, len;
1204 struct utsname uninfo;
1205
1206 /* Linux/Unix distribution release info (from http://linuxmafia.com) */
1207 const char* releases[] = {
1208 "/etc/lsb-release", "/etc/debian_version",
1209 "/etc/SuSE-release", "/etc/novell-release",
1210 "/etc/sles-release", "/etc/redhat-release",
1211 "/etc/fedora-release", "/etc/gentoo-release",
1212 "/etc/slackware-version", "/etc/annvix-release",
1213 "/etc/arch-release", "/etc/arklinux-release",
1214 "/etc/aurox-release", "/etc/blackcat-release",
1215 "/etc/cobalt-release", "/etc/conectiva-release",
1216 "/etc/debian_release", "/etc/immunix-release",
1217 "/etc/lfs-release", "/etc/linuxppc-release",
1218 "/etc/mandrake-release", "/etc/mandriva-release",
1219 "/etc/mandrakelinux-release", "/etc/mklinux-release",
1220 "/etc/pld-release", "/etc/redhat_version",
1221 "/etc/slackware-release", "/etc/e-smith-release",
1222 "/etc/release", "/etc/sun-release",
1223 "/etc/tinysofa-release", "/etc/turbolinux-release",
1224 "/etc/ultrapenguin-release", "/etc/UnitedLinux-release",
1225 "/etc/va-release", "/etc/yellowdog-release"
1226 };
1227
1228 const char description[] = "DISTRIB_DESCRIPTION=\"";
1229
1230 for (i = 0; i < countof(releases); i++)
1231 {
1232 file = fopen(releases[i], "r");
1233 if (!file)
1234 {
1235 continue;
1236 }
1237 fseek(file, 0, SEEK_END);
1238 len = min(ftell(file), sizeof(buf)-1);
1239 rewind(file);
1240 buf[len] = '\0';
1241 if (fread(buf, 1, len, file) != len)
1242 {
1243 DBG1(DBG_PTS, "failed to read file '%s'", releases[i]);
1244 fclose(file);
1245 return NULL;
1246 }
1247 fclose(file);
1248
1249 if (i == 0) /* LSB release */
1250 {
1251 pos = strstr(buf, description);
1252 if (!pos)
1253 {
1254 DBG1(DBG_PTS, "failed to find begin of lsb-release "
1255 "DESCRIPTION field");
1256 return NULL;
1257 }
1258 value = pos + strlen(description);
1259 pos = strchr(value, '"');
1260 if (!pos)
1261 {
1262 DBG1(DBG_PTS, "failed to find end of lsb-release "
1263 "DESCRIPTION field");
1264 return NULL;
1265 }
1266 }
1267 else
1268 {
1269 value = buf;
1270 pos = strchr(value, '\n');
1271 if (!pos)
1272 {
1273 DBG1(DBG_PTS, "failed to find end of release string");
1274 return NULL;
1275 }
1276 }
1277 break;
1278 }
1279
1280 if (!value)
1281 {
1282 DBG1(DBG_PTS, "no distribution release file found");
1283 return NULL;
1284 }
1285
1286 if (uname(&uninfo) < 0)
1287 {
1288 DBG1(DBG_PTS, "could not retrieve machine architecture");
1289 return NULL;
1290 }
1291
1292 *pos++ = ' ';
1293 len = sizeof(buf)-1 + (pos - buf);
1294 strncpy(pos, uninfo.machine, len);
1295
1296 DBG1(DBG_PTS, "platform is '%s'", value);
1297 return strdup(value);
1298 }
1299
1300 /**
1301 * Check for a TPM by querying for TPM Version Info
1302 */
1303 static bool has_tpm(private_pts_t *this)
1304 {
1305 TSS_HCONTEXT hContext;
1306 TSS_HTPM hTPM;
1307 TSS_RESULT result;
1308 u_int32_t version_info_len;
1309
1310 result = Tspi_Context_Create(&hContext);
1311 if (result != TSS_SUCCESS)
1312 {
1313 DBG1(DBG_PTS, "TPM context could not be created: tss error 0x%x",
1314 result);
1315 return FALSE;
1316 }
1317 result = Tspi_Context_Connect(hContext, NULL);
1318 if (result != TSS_SUCCESS)
1319 {
1320 goto err;
1321 }
1322 result = Tspi_Context_GetTpmObject (hContext, &hTPM);
1323 if (result != TSS_SUCCESS)
1324 {
1325 goto err;
1326 }
1327 result = Tspi_TPM_GetCapability(hTPM, TSS_TPMCAP_VERSION_VAL, 0, NULL,
1328 &version_info_len,
1329 &this->tpm_version_info.ptr);
1330 this->tpm_version_info.len = version_info_len;
1331 if (result != TSS_SUCCESS)
1332 {
1333 goto err;
1334 }
1335 this->tpm_version_info = chunk_clone(this->tpm_version_info);
1336 Tspi_Context_Close(hContext);
1337 return TRUE;
1338
1339 err:
1340 DBG1(DBG_PTS, "TPM not available: tss error 0x%x", result);
1341 Tspi_Context_Close(hContext);
1342 return FALSE;
1343 }
1344
1345 /**
1346 * See header
1347 */
1348 pts_t *pts_create(bool is_imc)
1349 {
1350 private_pts_t *this;
1351
1352 INIT(this,
1353 .public = {
1354 .get_proto_caps = _get_proto_caps,
1355 .set_proto_caps = _set_proto_caps,
1356 .get_meas_algorithm = _get_meas_algorithm,
1357 .set_meas_algorithm = _set_meas_algorithm,
1358 .get_dh_hash_algorithm = _get_dh_hash_algorithm,
1359 .set_dh_hash_algorithm = _set_dh_hash_algorithm,
1360 .create_dh_nonce = _create_dh_nonce,
1361 .get_my_public_value = _get_my_public_value,
1362 .set_peer_public_value = _set_peer_public_value,
1363 .calculate_secret = _calculate_secret,
1364 .get_platform_info = _get_platform_info,
1365 .set_platform_info = _set_platform_info,
1366 .get_tpm_version_info = _get_tpm_version_info,
1367 .set_tpm_version_info = _set_tpm_version_info,
1368 .get_aik = _get_aik,
1369 .set_aik = _set_aik,
1370 .is_path_valid = _is_path_valid,
1371 .hash_file = _hash_file,
1372 .do_measurements = _do_measurements,
1373 .get_metadata = _get_metadata,
1374 .read_pcr = _read_pcr,
1375 .extend_pcr = _extend_pcr,
1376 .quote_tpm = _quote_tpm,
1377 .add_pcr_entry = _add_pcr_entry,
1378 .get_quote_info = _get_quote_info,
1379 .verify_quote_signature = _verify_quote_signature,
1380 .destroy = _destroy,
1381 },
1382 .is_imc = is_imc,
1383 .proto_caps = PTS_PROTO_CAPS_V,
1384 .algorithm = PTS_MEAS_ALGO_SHA256,
1385 .dh_hash_algorithm = PTS_MEAS_ALGO_SHA256,
1386 );
1387
1388 if (is_imc)
1389 {
1390 this->platform_info = extract_platform_info();
1391
1392 if (has_tpm(this))
1393 {
1394 this->has_tpm = TRUE;
1395 this->proto_caps |= PTS_PROTO_CAPS_T;
1396 load_aik(this);
1397 load_aik_blob(this);
1398 }
1399 }
1400 else
1401 {
1402 this->proto_caps |= PTS_PROTO_CAPS_T | PTS_PROTO_CAPS_C;
1403 }
1404
1405 return &this->public;
1406 }