source: trunk/packages/xen-3.1/xen-3.1/tools/vtpm_manager/manager/securestorage.c @ 34

Last change on this file since 34 was 34, checked in by hartmans, 18 years ago

Add xen and xen-common

  • Property svn:mime-type set to text/cpp
File size: 17.7 KB
Line 
1// ===================================================================
2//
3// Copyright (c) 2005, Intel Corp.
4// All rights reserved.
5//
6// Redistribution and use in source and binary forms, with or without
7// modification, are permitted provided that the following conditions
8// are met:
9//
10//   * Redistributions of source code must retain the above copyright
11//     notice, this list of conditions and the following disclaimer.
12//   * Redistributions in binary form must reproduce the above
13//     copyright notice, this list of conditions and the following
14//     disclaimer in the documentation and/or other materials provided
15//     with the distribution.
16//   * Neither the name of Intel Corporation nor the names of its
17//     contributors may be used to endorse or promote products derived
18//     from this software without specific prior written permission.
19//
20// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
29// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
31// OF THE POSSIBILITY OF SUCH DAMAGE.
32// ===================================================================
33//
34// securestorage.c
35//
36//  Functions regarding securely storing DMI secrets.
37//
38// ==================================================================
39
40#include <sys/types.h>
41#include <sys/stat.h>
42#include <fcntl.h>
43#include <unistd.h>
44#include <string.h>
45
46#include "tcg.h"
47#include "vtpm_manager.h"
48#include "vtpmpriv.h"
49#include "vtsp.h"
50#include "bsg.h"
51#include "crypto.h"
52#include "hashtable.h"
53#include "hashtable_itr.h"
54#include "buffer.h"
55#include "log.h"
56
57TPM_RESULT envelope_encrypt(const buffer_t     *inbuf,
58                            CRYPTO_INFO        *asymkey,
59                            buffer_t           *sealed_data) {
60  TPM_RESULT status = TPM_SUCCESS;
61  symkey_t    symkey;
62  buffer_t    data_cipher = NULL_BUF,
63              symkey_cipher = NULL_BUF;
64 
65  UINT32 i;
66  struct pack_constbuf_t symkey_cipher32, data_cipher32;
67 
68  vtpmloginfo(VTPM_LOG_VTPM_DEEP, "Enveloping Input[%d]: 0x", buffer_len(inbuf));
69  for (i=0; i< buffer_len(inbuf); i++)
70    vtpmloginfomore(VTPM_LOG_VTPM_DEEP, "%x ", inbuf->bytes[i]);
71  vtpmloginfomore(VTPM_LOG_VTPM_DEEP, "\n");
72 
73  // Generate a sym key and encrypt state with it
74  TPMTRY(TPM_ENCRYPT_ERROR, Crypto_symcrypto_genkey (&symkey) );
75  TPMTRY(TPM_ENCRYPT_ERROR, Crypto_symcrypto_encrypt (&symkey, inbuf, &data_cipher) );
76 
77  // Encrypt symmetric key
78  TPMTRYRETURN( VTSP_Bind(    asymkey,
79                              &symkey.key,
80                              &symkey_cipher) );
81 
82  // Create output blob: symkey_size + symkey_cipher + state_cipher_size + state_cipher
83 
84  symkey_cipher32.size = buffer_len(&symkey_cipher);
85  symkey_cipher32.data = symkey_cipher.bytes;
86 
87  data_cipher32.size = buffer_len(&data_cipher);
88  data_cipher32.data = data_cipher.bytes;
89 
90  TPMTRYRETURN( buffer_init(sealed_data, 2 * sizeof(UINT32) + symkey_cipher32.size + data_cipher32.size, NULL));
91 
92  BSG_PackList(sealed_data->bytes, 2,
93               BSG_TPM_SIZE32_DATA, &symkey_cipher32,
94               BSG_TPM_SIZE32_DATA, &data_cipher32);
95
96  vtpmloginfo(VTPM_LOG_VTPM, "Saved %d bytes of E(symkey) + %d bytes of E(data)\n", buffer_len(&symkey_cipher), buffer_len(&data_cipher));
97
98  vtpmloginfo(VTPM_LOG_VTPM_DEEP, "Enveloping Output[%d]: 0x", buffer_len(sealed_data));
99  for (i=0; i< buffer_len(sealed_data); i++)
100    vtpmloginfomore(VTPM_LOG_VTPM_DEEP, "%x ", sealed_data->bytes[i]);
101  vtpmloginfomore(VTPM_LOG_VTPM_DEEP, "\n");
102
103  goto egress;
104
105 abort_egress:
106  vtpmlogerror(VTPM_LOG_VTPM, "Failed to envelope encrypt\n.");
107 
108 egress:
109 
110  buffer_free ( &data_cipher);
111  buffer_free ( &symkey_cipher);
112  Crypto_symcrypto_freekey (&symkey);
113 
114  return status;
115}
116
117TPM_RESULT envelope_decrypt(const buffer_t     *cipher,
118                            TCS_CONTEXT_HANDLE TCSContext,
119                            TPM_HANDLE         keyHandle,
120                            const TPM_AUTHDATA *key_usage_auth,
121                            buffer_t           *unsealed_data) {
122
123  TPM_RESULT status = TPM_SUCCESS;
124  symkey_t    symkey;
125  buffer_t    data_cipher = NULL_BUF,
126              symkey_clear = NULL_BUF,
127              symkey_cipher = NULL_BUF;
128  struct pack_buf_t symkey_cipher32, data_cipher32;
129  int i;
130
131  memset(&symkey, 0, sizeof(symkey_t));
132
133  vtpmloginfo(VTPM_LOG_VTPM_DEEP, "Envelope Decrypt Input[%d]: 0x", buffer_len(cipher) );
134  for (i=0; i< buffer_len(cipher); i++)
135    vtpmloginfomore(VTPM_LOG_VTPM_DEEP, "%x ", cipher->bytes[i]);
136  vtpmloginfomore(VTPM_LOG_VTPM_DEEP, "\n");
137 
138  BSG_UnpackList(cipher->bytes, 2,
139                 BSG_TPM_SIZE32_DATA, &symkey_cipher32,
140                 BSG_TPM_SIZE32_DATA, &data_cipher32);
141 
142  TPMTRYRETURN( buffer_init_alias_convert (&symkey_cipher,
143                                           symkey_cipher32.size,
144                                           symkey_cipher32.data) );
145 
146  TPMTRYRETURN( buffer_init_alias_convert (&data_cipher,
147                                           data_cipher32.size,
148                                           data_cipher32.data) );
149
150  // Decrypt Symmetric Key
151  TPMTRYRETURN( VTSP_Unbind(  TCSContext,
152                              keyHandle,
153                              &symkey_cipher,
154                              key_usage_auth,
155                              &symkey_clear,
156                              &(vtpm_globals->keyAuth) ) );
157 
158  // create symmetric key using saved bits
159  Crypto_symcrypto_initkey (&symkey, &symkey_clear);
160 
161  // Decrypt State
162  TPMTRY(TPM_DECRYPT_ERROR, Crypto_symcrypto_decrypt (&symkey, &data_cipher, unsealed_data) );
163
164  vtpmloginfo(VTPM_LOG_VTPM_DEEP, "Envelope Decrypte Output[%d]: 0x", buffer_len(unsealed_data));
165  for (i=0; i< buffer_len(unsealed_data); i++)
166    vtpmloginfomore(VTPM_LOG_VTPM_DEEP, "%x ", unsealed_data->bytes[i]);
167  vtpmloginfomore(VTPM_LOG_VTPM_DEEP, "\n");
168 
169  goto egress;
170 
171 abort_egress:
172  vtpmlogerror(VTPM_LOG_VTPM, "Failed to envelope decrypt data\n.");
173 
174 egress:
175  buffer_free ( &data_cipher);
176  buffer_free ( &symkey_clear);
177  buffer_free ( &symkey_cipher);
178  Crypto_symcrypto_freekey (&symkey);
179 
180  return status;
181}
182
183TPM_RESULT VTPM_Handle_Save_NVM(VTPM_DMI_RESOURCE *myDMI,
184                                const buffer_t *inbuf,
185                                buffer_t *outbuf) {
186 
187  TPM_RESULT status = TPM_SUCCESS;
188  int fh;
189  long bytes_written;
190  buffer_t sealed_NVM = NULL_BUF;
191 
192  vtpmloginfo(VTPM_LOG_VTPM_DEEP, "Saving %d bytes of NVM.\n", buffer_len(inbuf));
193
194  TPMTRYRETURN( envelope_encrypt(inbuf,
195                                 &vtpm_globals->storageKey,
196                                 &sealed_NVM) );
197                                 
198  // Write sealed blob off disk from NVMLocation
199  // TODO: How to properly return from these. Do we care if we return failure
200  //       after writing the file? We can't get the old one back.
201  // TODO: Backup old file and try and recover that way.
202  fh = open(myDMI->NVMLocation, O_WRONLY | O_CREAT, S_IREAD | S_IWRITE);
203  if ( (bytes_written = write(fh, sealed_NVM.bytes, buffer_len(&sealed_NVM) ) != (long) buffer_len(&sealed_NVM))) {
204    vtpmlogerror(VTPM_LOG_VTPM, "We just overwrote a DMI_NVM and failed to finish. %ld/%ld bytes.\n", bytes_written, (long)buffer_len(&sealed_NVM));
205    status = TPM_IOERROR;
206    goto abort_egress;
207  }
208  close(fh);
209 
210  Crypto_SHA1Full (sealed_NVM.bytes, buffer_len(&sealed_NVM), (BYTE *) &myDMI->NVM_measurement);   
211 
212  goto egress;
213 
214 abort_egress:
215  vtpmlogerror(VTPM_LOG_VTPM, "Failed to save NVM\n.");
216 
217 egress:
218  buffer_free(&sealed_NVM);
219  return status;
220}
221
222
223/* Expected Params: inbuf = null, outbuf = sealed blob size, sealed blob.*/
224TPM_RESULT VTPM_Handle_Load_NVM(VTPM_DMI_RESOURCE *myDMI,
225                                const buffer_t    *inbuf,
226                                buffer_t          *outbuf) {
227 
228  TPM_RESULT status = TPM_SUCCESS;
229
230  buffer_t sealed_NVM = NULL_BUF;
231  long fh_size;
232  int fh, stat_ret, i;
233  struct stat file_stat;
234  TPM_DIGEST sealedNVMHash;
235   
236  if (myDMI->NVMLocation == NULL) {
237    vtpmlogerror(VTPM_LOG_VTPM, "Unable to load NVM because the file name NULL.\n");
238    status = TPM_AUTHFAIL;
239    goto abort_egress;
240  }
241 
242  //Read sealed blob off disk from NVMLocation
243  fh = open(myDMI->NVMLocation, O_RDONLY);
244  stat_ret = fstat(fh, &file_stat);
245  if (stat_ret == 0)
246    fh_size = file_stat.st_size;
247  else {
248    status = TPM_IOERROR;
249    goto abort_egress;
250  }
251 
252  TPMTRYRETURN( buffer_init( &sealed_NVM, fh_size, NULL) );
253  if (read(fh, sealed_NVM.bytes, buffer_len(&sealed_NVM)) != fh_size) {
254    status = TPM_IOERROR;
255    goto abort_egress;
256  }
257  close(fh);
258 
259  vtpmloginfo(VTPM_LOG_VTPM_DEEP, "Load_NVMing[%d],\n", buffer_len(&sealed_NVM));
260 
261  Crypto_SHA1Full(sealed_NVM.bytes, buffer_len(&sealed_NVM), (BYTE *) &sealedNVMHash);   
262 
263  // Verify measurement of sealed blob.
264  if (memcmp(&sealedNVMHash, &myDMI->NVM_measurement, sizeof(TPM_DIGEST)) ) {
265    vtpmlogerror(VTPM_LOG_VTPM, "VTPM LoadNVM NVM measurement check failed.\n");
266    vtpmloginfo(VTPM_LOG_VTPM_DEEP, "Correct hash: ");
267    for (i=0; i< sizeof(TPM_DIGEST); i++)
268      vtpmloginfomore(VTPM_LOG_VTPM_DEEP, "%x ", ((BYTE*)&myDMI->NVM_measurement)[i]);
269    vtpmloginfomore(VTPM_LOG_VTPM_DEEP, "\n");
270
271    vtpmloginfo(VTPM_LOG_VTPM_DEEP, "Measured hash: ");
272    for (i=0; i< sizeof(TPM_DIGEST); i++)
273      vtpmloginfomore(VTPM_LOG_VTPM_DEEP, "%x ", ((BYTE*)&sealedNVMHash)[i]);
274    vtpmloginfomore(VTPM_LOG_VTPM_DEEP, "\n");
275   
276    status = TPM_AUTHFAIL;
277    goto abort_egress;
278  }
279 
280  TPMTRYRETURN( envelope_decrypt(&sealed_NVM,
281                                 myDMI->TCSContext,
282                                 vtpm_globals->storageKeyHandle,
283                                 (const TPM_AUTHDATA*)&vtpm_globals->storage_key_usage_auth,
284                                 outbuf) ); 
285  goto egress;
286 
287 abort_egress:
288  vtpmlogerror(VTPM_LOG_VTPM, "Failed to load NVM\n.");
289 
290 egress:
291  buffer_free( &sealed_NVM );
292 
293  return status;
294}
295
296
297TPM_RESULT VTPM_SaveManagerData(void) {
298  TPM_RESULT status=TPM_SUCCESS;
299  int fh, dmis=-1;
300
301  BYTE *flat_boot_key=NULL, *flat_dmis=NULL, *flat_enc=NULL;
302  buffer_t clear_flat_global=NULL_BUF, enc_flat_global=NULL_BUF;
303  UINT32 storageKeySize = buffer_len(&vtpm_globals->storageKeyWrap);
304  UINT32 bootKeySize = buffer_len(&vtpm_globals->bootKeyWrap);
305  struct pack_buf_t storage_key_pack = {storageKeySize, vtpm_globals->storageKeyWrap.bytes};
306  struct pack_buf_t boot_key_pack = {bootKeySize, vtpm_globals->bootKeyWrap.bytes};
307  BYTE vtpm_manager_gen = VTPM_MANAGER_GEN;
308
309  struct hashtable_itr *dmi_itr;
310  VTPM_DMI_RESOURCE *dmi_res;
311
312  UINT32 boot_key_size = 0, flat_dmis_size = 0;
313
314  // Initially fill these with buffer sizes for each data type. Later fill
315  // in actual size, once flattened.
316  boot_key_size =  sizeof(UINT32) +       // bootkeysize
317                   bootKeySize;           // boot key
318
319  TPMTRYRETURN(buffer_init(&clear_flat_global,sizeof(BYTE) + // manager version
320                                              3*sizeof(TPM_DIGEST) + // Auths
321                                              sizeof(UINT32) +// storagekeysize
322                                              storageKeySize, NULL) ); // storage key
323
324
325  flat_boot_key = (BYTE *) malloc( boot_key_size );
326  flat_enc = (BYTE *) malloc( sizeof(UINT32) );
327
328  boot_key_size = BSG_PackList(flat_boot_key, 1,
329                               BSG_TPM_SIZE32_DATA, &boot_key_pack);
330
331  BSG_PackList(clear_flat_global.bytes, 4,
332                BSG_TYPE_BYTE,    &vtpm_manager_gen,
333                BSG_TPM_AUTHDATA, &vtpm_globals->owner_usage_auth,
334                BSG_TPM_SECRET,   &vtpm_globals->storage_key_usage_auth,
335                BSG_TPM_SIZE32_DATA, &storage_key_pack);
336
337  TPMTRYRETURN(envelope_encrypt(&clear_flat_global,
338                                &vtpm_globals->bootKey,
339                                &enc_flat_global) );
340
341  BSG_PackConst(buffer_len(&enc_flat_global), 4, flat_enc);
342
343  // Per DMI values to be saved (if any exit)
344  if (hashtable_count(vtpm_globals->dmi_map) > 1) {
345
346    flat_dmis = (BYTE *) malloc(
347                     (hashtable_count(vtpm_globals->dmi_map) - 1) * // num DMIS (-1 for Dom0)
348                     (sizeof(UINT32) +sizeof(BYTE) + 2*sizeof(TPM_DIGEST)) ); // Per DMI info
349
350    dmi_itr = hashtable_iterator(vtpm_globals->dmi_map);
351    do {
352      dmi_res = (VTPM_DMI_RESOURCE *) hashtable_iterator_value(dmi_itr);
353      dmis++;
354
355      // No need to save dmi0.
356      if (dmi_res->dmi_id == 0)
357        continue;
358
359
360      flat_dmis_size += BSG_PackList( flat_dmis + flat_dmis_size, 4,
361                                        BSG_TYPE_UINT32, &dmi_res->dmi_id,
362                                        BSG_TYPE_BYTE, &dmi_res->dmi_type,
363                                        BSG_TPM_DIGEST, &dmi_res->NVM_measurement,
364                                        BSG_TPM_DIGEST, &dmi_res->DMI_measurement);
365
366    } while (hashtable_iterator_advance(dmi_itr));
367  }
368
369  fh = open(STATE_FILE, O_WRONLY | O_CREAT, S_IREAD | S_IWRITE);
370  if (fh == -1) {
371    vtpmlogerror(VTPM_LOG_VTPM, "Unable to open %s file for write.\n", STATE_FILE);
372    status = TPM_IOERROR;
373    goto abort_egress;
374  }
375
376  if ( ( write(fh, flat_boot_key, boot_key_size) != boot_key_size ) ||
377       ( write(fh, flat_enc, sizeof(UINT32)) != sizeof(UINT32) ) ||
378       ( write(fh, enc_flat_global.bytes, buffer_len(&enc_flat_global)) != buffer_len(&enc_flat_global) ) ||
379       ( write(fh, flat_dmis, flat_dmis_size) != flat_dmis_size ) ) {
380    vtpmlogerror(VTPM_LOG_VTPM, "Failed to completely write service data.\n");
381    status = TPM_IOERROR;
382    goto abort_egress;
383 }
384
385  goto egress;
386
387 abort_egress:
388 egress:
389
390  free(flat_boot_key);
391  free(flat_enc);
392  buffer_free(&enc_flat_global);
393  free(flat_dmis);
394  close(fh);
395
396  vtpmloginfo(VTPM_LOG_VTPM, "Saved VTPM Manager state (status = %d, dmis = %d)\n", (int) status, dmis);
397  return status;
398}
399
400TPM_RESULT VTPM_LoadManagerData(void) {
401
402  TPM_RESULT status=TPM_SUCCESS;
403  int fh, stat_ret, dmis=0;
404  long fh_size = 0, step_size;
405  BYTE *flat_table=NULL;
406  buffer_t  unsealed_data, enc_table_abuf;
407  struct pack_buf_t storage_key_pack, boot_key_pack;
408  UINT32 *dmi_id_key, enc_size;
409  BYTE vtpm_manager_gen;
410
411  VTPM_DMI_RESOURCE *dmi_res;
412  UINT32 dmi_id;
413  BYTE dmi_type;
414  struct stat file_stat;
415
416  TPM_HANDLE boot_key_handle;
417  TPM_AUTHDATA boot_usage_auth;
418  memset(&boot_usage_auth, 0, sizeof(TPM_AUTHDATA));
419
420  fh = open(STATE_FILE, O_RDONLY );
421  stat_ret = fstat(fh, &file_stat);
422  if (stat_ret == 0)
423    fh_size = file_stat.st_size;
424  else {
425    status = TPM_IOERROR;
426    goto abort_egress;
427  }
428
429  flat_table = (BYTE *) malloc(fh_size);
430
431  if ((long) read(fh, flat_table, fh_size) != fh_size ) {
432    status = TPM_IOERROR;
433    goto abort_egress;
434  }
435
436  // Read Boot Key
437  step_size = BSG_UnpackList( flat_table, 2,
438                              BSG_TPM_SIZE32_DATA, &boot_key_pack,
439                              BSG_TYPE_UINT32, &enc_size);
440
441  TPMTRYRETURN(buffer_init(&vtpm_globals->bootKeyWrap, 0, 0) );
442  TPMTRYRETURN(buffer_init_alias_convert(&enc_table_abuf, enc_size, flat_table + step_size) );
443  TPMTRYRETURN(buffer_append_raw(&vtpm_globals->bootKeyWrap, boot_key_pack.size, boot_key_pack.data) );
444
445  //Load Boot Key
446  TPMTRYRETURN( VTSP_LoadKey( vtpm_globals->manager_tcs_handle,
447                              TPM_SRK_KEYHANDLE,
448                              &vtpm_globals->bootKeyWrap,
449                              &SRK_AUTH,
450                              &boot_key_handle,
451                              &vtpm_globals->keyAuth,
452                              &vtpm_globals->bootKey,
453                              FALSE) );
454
455  TPMTRYRETURN( envelope_decrypt(&enc_table_abuf,
456                                 vtpm_globals->manager_tcs_handle,
457                                 boot_key_handle,
458                                 (const TPM_AUTHDATA*) &boot_usage_auth,
459                                 &unsealed_data) );
460  step_size += enc_size;
461
462  if (*unsealed_data.bytes != VTPM_MANAGER_GEN) {
463      // Once there is more than one gen, this will include some compatability stuff
464      vtpmlogerror(VTPM_LOG_VTPM, "Warning: Manager Data file is gen %d, which this manager is gen %d.\n", vtpm_manager_gen, VTPM_MANAGER_GEN);
465  }
466
467  // Global Values needing to be saved
468  BSG_UnpackList( unsealed_data.bytes, 4,
469                  BSG_TYPE_BYTE,    &vtpm_manager_gen,
470                  BSG_TPM_AUTHDATA, &vtpm_globals->owner_usage_auth,
471                  BSG_TPM_SECRET,   &vtpm_globals->storage_key_usage_auth,
472                  BSG_TPM_SIZE32_DATA, &storage_key_pack);
473
474  TPMTRYRETURN(buffer_init(&vtpm_globals->storageKeyWrap, 0, 0) );
475  TPMTRYRETURN(buffer_append_raw(&vtpm_globals->storageKeyWrap, storage_key_pack.size, storage_key_pack.data) );
476
477  // Per DMI values to be saved
478  while ( step_size < fh_size ){
479    if (fh_size - step_size < (long) (sizeof(UINT32) + sizeof(BYTE) + 2*sizeof(TPM_DIGEST))) {
480      vtpmlogerror(VTPM_LOG_VTPM, "Encountered %ld extra bytes at end of manager state.\n", fh_size-step_size);
481      step_size = fh_size;
482    } else {
483      step_size += BSG_UnpackList(flat_table + step_size, 2,
484                                 BSG_TYPE_UINT32, &dmi_id,
485                                 BSG_TYPE_BYTE, &dmi_type);
486
487      //TODO: Try and gracefully recover from problems.
488      TPMTRYRETURN(init_dmi(dmi_id, dmi_type, &dmi_res) );
489      dmis++;
490
491      step_size += BSG_UnpackList(flat_table + step_size, 2,
492                                 BSG_TPM_DIGEST, &dmi_res->NVM_measurement,
493                                 BSG_TPM_DIGEST, &dmi_res->DMI_measurement);
494    }
495
496  }
497
498  vtpmloginfo(VTPM_LOG_VTPM, "Loaded saved state (dmis = %d).\n", dmis);
499  goto egress;
500
501 abort_egress:
502  vtpmlogerror(VTPM_LOG_VTPM, "Failed to load service data with error = %s\n", tpm_get_error_name(status));
503 egress:
504
505  free(flat_table);
506  close(fh);
507
508  // TODO: Could be nice and evict BootKey. (Need to add EvictKey to VTSP.
509
510  return status;
511}
512
Note: See TracBrowser for help on using the repository browser.