Separated IMV session management from IMV policy database
[strongswan.git] / src / libpts / pts / pts_database.c
1 /*
2 * Copyright (C) 2011-2012 Sansar Choinyambuu
3 * Copyright (C) 2012-2014 Andreas Steffen
4 * HSR Hochschule fuer Technik Rapperswil
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation; either version 2 of the License, or (at your
9 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * for more details.
15 */
16
17 #define _GNU_SOURCE
18 #include <stdio.h>
19 #include <libgen.h>
20
21 #include "pts_database.h"
22
23 #include <utils/debug.h>
24 #include <crypto/hashers/hasher.h>
25
26
27 typedef struct private_pts_database_t private_pts_database_t;
28
29 /**
30 * Private data of a pts_database_t object.
31 *
32 */
33 struct private_pts_database_t {
34
35 /**
36 * Public pts_database_t interface.
37 */
38 pts_database_t public;
39
40 /**
41 * database instance
42 */
43 database_t *db;
44
45 };
46
47 METHOD(pts_database_t, get_pathname, char*,
48 private_pts_database_t *this, bool is_dir, int id)
49 {
50 enumerator_t *e;
51 char *path, *name, *pathname;
52
53 if (is_dir)
54 {
55 e = this->db->query(this->db,
56 "SELECT path FROM directories WHERE id = ?",
57 DB_INT, id, DB_TEXT);
58 if (!e || !e->enumerate(e, &path))
59 {
60 pathname = NULL;
61 }
62 else
63 {
64 pathname = strdup(path);
65 }
66 }
67 else
68 {
69 e = this->db->query(this->db,
70 "SELECT d.path, f.name FROM files AS f "
71 "JOIN directories AS d ON d.id = f.dir WHERE f.id = ?",
72 DB_INT, id, DB_TEXT, DB_TEXT);
73 if (!e || !e->enumerate(e, &path, &name) ||
74 asprintf(&pathname, "%s%s%s",
75 path, streq(path, "/") ? "" : "/", name) == -1)
76 {
77 pathname = NULL;
78 }
79 }
80 DESTROY_IF(e);
81
82 return pathname;
83 }
84
85 METHOD(pts_database_t, create_file_hash_enumerator, enumerator_t*,
86 private_pts_database_t *this, char *product, pts_meas_algorithms_t algo,
87 bool is_dir, int id)
88 {
89 enumerator_t *e;
90
91 if (is_dir)
92 {
93 e = this->db->query(this->db,
94 "SELECT f.name, fh.hash FROM file_hashes AS fh "
95 "JOIN files AS f ON f.id = fh.file "
96 "JOIN products AS p ON p.id = fh.product "
97 "JOIN directories as d ON d.id = f.dir "
98 "WHERE p.name = ? AND fh.algo = ? AND d.id = ? "
99 "ORDER BY f.name",
100 DB_TEXT, product, DB_INT, algo, DB_INT, id, DB_TEXT, DB_BLOB);
101 }
102 else
103 {
104 e = this->db->query(this->db,
105 "SELECT f.name, fh.hash FROM file_hashes AS fh "
106 "JOIN files AS f ON f.id = fh.file "
107 "JOIN products AS p ON p.id = fh.product "
108 "WHERE p.name = ? AND fh.algo = ? AND fh.file = ?",
109 DB_TEXT, product, DB_INT, algo, DB_INT, id, DB_TEXT, DB_BLOB);
110 }
111 return e;
112 }
113
114 METHOD(pts_database_t, check_aik_keyid, status_t,
115 private_pts_database_t *this, chunk_t keyid, int *kid)
116 {
117 enumerator_t *e;
118 chunk_t keyid_hex;
119
120 /* Convert keyid into a hex-encoded string */
121 keyid_hex = chunk_to_hex(keyid, NULL, FALSE);
122
123 /* If the AIK is registered get the primary key */
124 e = this->db->query(this->db,
125 "SELECT id FROM devices WHERE name = ?",
126 DB_TEXT, keyid_hex.ptr, DB_INT);
127 if (!e)
128 {
129 DBG1(DBG_PTS, "no database query enumerator returned");
130 return FAILED;
131 }
132 if (!e->enumerate(e, kid))
133 {
134 DBG1(DBG_PTS, "AIK %#B is not registered in database", &keyid);
135 e->destroy(e);
136 return FAILED;
137 }
138 e->destroy(e);
139
140 return SUCCESS;
141 }
142
143 METHOD(pts_database_t, add_file_measurement, status_t,
144 private_pts_database_t *this, char *product, pts_meas_algorithms_t algo,
145 chunk_t measurement, char *filename, bool is_dir, int id)
146 {
147 enumerator_t *e;
148 char *name;
149 chunk_t hash_value;
150 int hash_id, fid, pid = 0;
151 status_t status = SUCCESS;
152
153 /* get primary key of product string */
154 e = this->db->query(this->db,
155 "SELECT id FROM products WHERE name = ?", DB_TEXT, product, DB_INT);
156 if (e)
157 {
158 e->enumerate(e, &pid);
159 e->destroy(e);
160 }
161 if (pid == 0)
162 {
163 return FAILED;
164 }
165
166 if (is_dir)
167 {
168 /* does filename entry already exist? */
169 e = this->db->query(this->db,
170 "SELECT id FROM files WHERE name = ? AND dir = ?",
171 DB_TEXT, filename, DB_INT, id, DB_INT);
172 if (!e)
173 {
174 return FAILED;
175 }
176 if (!e->enumerate(e, &fid))
177 {
178 /* create filename entry */
179 if (this->db->execute(this->db, &fid,
180 "INSERT INTO files (name, dir) VALUES (?, ?)",
181 DB_TEXT, filename, DB_INT, id) != 1)
182 {
183 DBG1(DBG_PTS, "could not insert filename into database");
184 status = FAILED;
185 }
186 }
187 e->destroy(e);
188 }
189 else
190 {
191 fid = id;
192
193 /* verify filename */
194 e = this->db->query(this->db,
195 "SELECT name FROM files WHERE id = ?", DB_INT, fid, DB_TEXT);
196 if (!e)
197 {
198 return FAILED;
199 }
200 if (!e->enumerate(e, &name) || !streq(name, filename))
201 {
202 DBG1(DBG_PTS, "filename of reference measurement does not match");
203 status = FAILED;
204 }
205 e->destroy(e);
206 }
207
208 if (status != SUCCESS)
209 {
210 return status;
211 }
212
213 /* does hash measurement value already exist? */
214 e = this->db->query(this->db,
215 "SELECT fh.id, fh.hash FROM file_hashes AS fh "
216 "WHERE fh.product = ? AND fh.algo = ? AND fh.file = ?",
217 DB_INT, pid, DB_INT, algo, DB_INT, fid, DB_INT, DB_BLOB);
218 if (!e)
219 {
220 return FAILED;
221 }
222 if (e->enumerate(e, &hash_id, &hash_value))
223 {
224 if (!chunk_equals(measurement, hash_value))
225 {
226 /* update hash measurement value */
227 if (this->db->execute(this->db, &hash_id,
228 "UPDATE file_hashes SET hash = ? WHERE id = ?",
229 DB_BLOB, measurement, DB_INT, hash_id) != 1)
230 {
231 status = FAILED;
232 }
233 }
234 }
235 else
236 {
237 /* insert hash measurement value */
238 if (this->db->execute(this->db, &hash_id,
239 "INSERT INTO file_hashes (file, product, algo, hash) "
240 "VALUES (?, ?, ?, ?)", DB_INT, fid, DB_INT, pid,
241 DB_INT, algo, DB_BLOB, measurement) != 1)
242 {
243 status = FAILED;
244 }
245 }
246 e->destroy(e);
247
248 return status;
249 }
250
251 METHOD(pts_database_t, check_file_measurement, status_t,
252 private_pts_database_t *this, char *product, pts_meas_algorithms_t algo,
253 chunk_t measurement, char *filename)
254 {
255 enumerator_t *e;
256 chunk_t hash;
257 status_t status = NOT_FOUND;
258 char *dir, *file;
259
260 if (strlen(filename) < 1)
261 {
262 return INVALID_ARG;
263 }
264
265 /* separate filename into directory and basename components */
266 dir = path_dirname(filename);
267 file = path_basename(filename);
268
269 if (*dir == '.')
270 { /* relative pathname */
271 e = this->db->query(this->db,
272 "SELECT fh.hash FROM file_hashes AS fh "
273 "JOIN files AS f ON f.id = fh.file "
274 "JOIN products AS p ON p.id = fh.product "
275 "WHERE p.name = ? AND f.name = ? AND fh.algo = ?",
276 DB_TEXT, product, DB_TEXT, file, DB_INT, algo, DB_BLOB);
277 }
278 else
279 { /* absolute pathname */
280 bool dir_found;
281 int did;
282
283 /* find directory entry first */
284 e = this->db->query(this->db,
285 "SELECT id FROM directories WHERE path = ?",
286 DB_TEXT, dir, DB_INT);
287 if (!e)
288 {
289 status = FAILED;
290 goto err;
291 }
292 dir_found = e->enumerate(e, &did);
293 e->destroy(e);
294
295 if (!dir_found)
296 {
297 status = NOT_FOUND;
298 goto err;
299 }
300 e = this->db->query(this->db,
301 "SELECT fh.hash FROM file_hashes AS fh "
302 "JOIN files AS f ON f.id = fh.file "
303 "JOIN products AS p ON p.id = fh.product "
304 "WHERE p.name = ? AND f.dir = ? AND f.name = ? AND fh.algo = ?",
305 DB_TEXT, product, DB_INT, did, DB_TEXT, file, DB_INT, algo,
306 DB_BLOB);
307 }
308 if (!e)
309 {
310 status = FAILED;
311 goto err;
312 }
313 while (e->enumerate(e, &hash))
314 {
315 /* with relative filenames there might be multiple entries */
316 if (chunk_equals(measurement, hash))
317 {
318 status = SUCCESS;
319 break;
320 }
321 else
322 {
323 status = VERIFY_ERROR;
324 }
325 }
326 e->destroy(e);
327
328 err:
329 free(file);
330 free(dir);
331
332 return status;
333 }
334
335 METHOD(pts_database_t, create_comp_evid_enumerator, enumerator_t*,
336 private_pts_database_t *this, int kid)
337 {
338 enumerator_t *e;
339
340 /* look for all entries belonging to an AIK in the components table */
341 e = this->db->query(this->db,
342 "SELECT c.vendor_id, c.name, c.qualifier, kc.depth "
343 "FROM components AS c "
344 "JOIN key_component AS kc ON c.id = kc.component "
345 "WHERE kc.key = ? ORDER BY kc.seq_no",
346 DB_INT, kid, DB_INT, DB_INT, DB_INT, DB_INT);
347 return e;
348 }
349
350 METHOD(pts_database_t, check_comp_measurement, status_t,
351 private_pts_database_t *this, chunk_t measurement, int cid, int kid,
352 int seq_no, int pcr, pts_meas_algorithms_t algo)
353 {
354 enumerator_t *e;
355 chunk_t hash;
356 status_t status = NOT_FOUND;
357
358 e = this->db->query(this->db,
359 "SELECT hash FROM component_hashes "
360 "WHERE component = ? AND key = ? "
361 "AND seq_no = ? AND pcr = ? AND algo = ? ",
362 DB_INT, cid, DB_INT, kid, DB_INT, seq_no,
363 DB_INT, pcr, DB_INT, algo, DB_BLOB);
364 if (!e)
365 {
366 DBG1(DBG_PTS, "no database query enumerator returned");
367 return FAILED;
368 }
369
370 while (e->enumerate(e, &hash))
371 {
372 if (chunk_equals(hash, measurement))
373 {
374 status = SUCCESS;
375 break;
376 }
377 else
378 {
379 DBG1(DBG_PTS, "PCR %2d no matching component measurement #%d "
380 "found in database", pcr, seq_no);
381 DBG1(DBG_PTS, " expected: %#B", &hash);
382 DBG1(DBG_PTS, " received: %#B", &measurement);
383 status = VERIFY_ERROR;
384 break;
385 }
386 }
387 e->destroy(e);
388
389 if (status == NOT_FOUND)
390 {
391 DBG1(DBG_PTS, "PCR %2d no measurement #%d "
392 "found in database", pcr, seq_no);
393 }
394
395 return status;
396 }
397
398 METHOD(pts_database_t, insert_comp_measurement, status_t,
399 private_pts_database_t *this, chunk_t measurement, int cid, int kid,
400 int seq_no, int pcr, pts_meas_algorithms_t algo)
401 {
402 int id;
403
404 if (this->db->execute(this->db, &id,
405 "INSERT INTO component_hashes "
406 "(component, key, seq_no, pcr, algo, hash) "
407 "VALUES (?, ?, ?, ?, ?, ?)",
408 DB_INT, cid, DB_INT, kid, DB_INT, seq_no, DB_INT, pcr,
409 DB_INT, algo, DB_BLOB, measurement) == 1)
410 {
411 return SUCCESS;
412 }
413
414 DBG1(DBG_PTS, "could not insert component measurement into database");
415 return FAILED;
416 }
417
418 METHOD(pts_database_t, delete_comp_measurements, int,
419 private_pts_database_t *this, int cid, int kid)
420 {
421 return this->db->execute(this->db, NULL,
422 "DELETE FROM component_hashes "
423 "WHERE component = ? AND key = ?",
424 DB_INT, cid, DB_INT, kid);
425 }
426
427 METHOD(pts_database_t, get_comp_measurement_count, status_t,
428 private_pts_database_t *this, pts_comp_func_name_t *comp_name,
429 chunk_t keyid, pts_meas_algorithms_t algo, int *cid, int *kid, int *count)
430 {
431 enumerator_t *e;
432 status_t status = SUCCESS;
433
434 /* Initialize count */
435 *count = 0;
436
437 if (_check_aik_keyid(this, keyid, kid) != SUCCESS)
438 {
439 return FAILED;
440 }
441
442 /* Get the primary key of the Component Functional Name */
443 e = this->db->query(this->db,
444 "SELECT id FROM components "
445 " WHERE vendor_id = ? AND name = ? AND qualifier = ?",
446 DB_INT, comp_name->get_vendor_id(comp_name),
447 DB_INT, comp_name->get_name(comp_name),
448 DB_INT, comp_name->get_qualifier(comp_name),
449 DB_INT);
450 if (!e)
451 {
452 DBG1(DBG_PTS, "no database query enumerator returned");
453 return FAILED;
454 }
455 if (!e->enumerate(e, cid))
456 {
457 DBG1(DBG_PTS, "component functional name not found in database");
458 e->destroy(e);
459 return FAILED;
460 }
461 e->destroy(e);
462
463 /* Get the number of stored measurements for a given AIK and component */
464 e = this->db->query(this->db,
465 "SELECT COUNT(*) FROM component_hashes AS ch "
466 "WHERE component = ? AND key = ? AND algo = ?",
467 DB_INT, *cid, DB_INT, *kid, DB_INT, algo, DB_INT);
468 if (!e)
469 {
470 DBG1(DBG_PTS, "no database query enumerator returned");
471 return FAILED;
472 }
473 if (!e->enumerate(e, count))
474 {
475 DBG1(DBG_PTS, "no component measurement count returned from database");
476 status = FAILED;
477 }
478 e->destroy(e);
479
480 return status;
481 }
482
483 METHOD(pts_database_t, destroy, void,
484 private_pts_database_t *this)
485 {
486 free(this);
487 }
488
489 /**
490 * See header
491 */
492 pts_database_t *pts_database_create(imv_database_t *imv_db)
493 {
494 private_pts_database_t *this;
495
496 if (!imv_db)
497 {
498 return NULL;
499 }
500
501 INIT(this,
502 .public = {
503 .get_pathname = _get_pathname,
504 .create_comp_evid_enumerator = _create_comp_evid_enumerator,
505 .create_file_hash_enumerator = _create_file_hash_enumerator,
506 .add_file_measurement = _add_file_measurement,
507 .check_file_measurement = _check_file_measurement,
508 .check_comp_measurement = _check_comp_measurement,
509 .insert_comp_measurement = _insert_comp_measurement,
510 .delete_comp_measurements = _delete_comp_measurements,
511 .get_comp_measurement_count = _get_comp_measurement_count,
512 .destroy = _destroy,
513 },
514 .db = imv_db->get_database(imv_db),
515 );
516
517 return &this->public;
518 }
519