-
Notifications
You must be signed in to change notification settings - Fork 56
/
ReferenceManifestPageController.java
543 lines (495 loc) · 23.5 KB
/
ReferenceManifestPageController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
package hirs.attestationca.portal.page.controllers;
import hirs.attestationca.persist.DBManagerException;
import hirs.attestationca.persist.FilteredRecordsList;
import hirs.attestationca.persist.entity.manager.ReferenceDigestValueRepository;
import hirs.attestationca.persist.entity.manager.ReferenceManifestRepository;
import hirs.attestationca.persist.entity.userdefined.ReferenceManifest;
import hirs.attestationca.persist.entity.userdefined.rim.BaseReferenceManifest;
import hirs.attestationca.persist.entity.userdefined.rim.ReferenceDigestValue;
import hirs.attestationca.persist.entity.userdefined.rim.SupportReferenceManifest;
import hirs.attestationca.portal.datatables.DataTableInput;
import hirs.attestationca.portal.datatables.DataTableResponse;
import hirs.attestationca.portal.page.Page;
import hirs.attestationca.portal.page.PageController;
import hirs.attestationca.portal.page.PageMessages;
import hirs.attestationca.portal.page.params.NoPageParams;
import hirs.utils.tpm.eventlog.TCGEventLog;
import hirs.utils.tpm.eventlog.TpmPcrEvent;
import jakarta.persistence.EntityManager;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import jakarta.xml.bind.UnmarshalException;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springframework.web.servlet.view.RedirectView;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* Controller for the Reference Manifest page.
*/
@Log4j2
@Controller
@RequestMapping("/HIRS_AttestationCAPortal/portal/reference-manifests")
public class ReferenceManifestPageController extends PageController<NoPageParams> {
private static final String BASE_RIM_FILE_PATTERN = "([^\\s]+(\\.(?i)swidtag)$)";
private static final String SUPPORT_RIM_FILE_PATTERN = "([^\\s]+(\\.(?i)(rimpcr|rimel|bin|log))$)";
@Autowired(required = false)
private EntityManager entityManager;
private final ReferenceManifestRepository referenceManifestRepository;
private final ReferenceDigestValueRepository referenceDigestValueRepository;
/**
* Constructor providing the Page's display and routing specification.
*
* @param referenceManifestRepository the reference manifest manager
* @param referenceDigestValueRepository this is the reference event manager
*/
@Autowired
public ReferenceManifestPageController(final ReferenceManifestRepository referenceManifestRepository,
final ReferenceDigestValueRepository referenceDigestValueRepository) {
super(Page.REFERENCE_MANIFESTS);
this.referenceManifestRepository = referenceManifestRepository;
this.referenceDigestValueRepository = referenceDigestValueRepository;
}
/**
* Returns the filePath for the view and the data model for the page.
*
* @param params The object to map url parameters into.
* @param model The data model for the request. Can contain data from
* redirect.
* @return the filePath for the view and data model for the page.
*/
@Override
public ModelAndView initPage(final NoPageParams params,
final Model model) {
return getBaseModelAndView();
}
/**
* Returns the list of RIMs using the data table input for paging, ordering,
* and filtering.
*
* @param input the data tables input
* @return the data tables response, including the result set and paging
* information
*/
@ResponseBody
@RequestMapping(value = "/list",
produces = MediaType.APPLICATION_JSON_VALUE,
method = RequestMethod.GET)
public DataTableResponse<ReferenceManifest> getTableData(
@Valid final DataTableInput input) {
log.debug("Handling request for summary list: " + input);
String orderColumnName = input.getOrderColumnName();
log.info("Ordering on column: " + orderColumnName);
log.info("Querying with the following dataTableInput: " + input.toString());
FilteredRecordsList<ReferenceManifest> records = new FilteredRecordsList<>();
int currentPage = input.getStart() / input.getLength();
Pageable paging = PageRequest.of(currentPage, input.getLength(), Sort.by(orderColumnName));
org.springframework.data.domain.Page<ReferenceManifest> pagedResult = referenceManifestRepository.findByArchiveFlag(false, paging);
int rimCount = 0;
if (pagedResult.hasContent()) {
for (ReferenceManifest manifest : pagedResult.getContent()) {
records.add(manifest);
rimCount++;
}
records.setRecordsTotal(rimCount);
} else {
records.setRecordsTotal(input.getLength());
}
records.setRecordsFiltered(referenceManifestRepository.findByArchiveFlag(false).size());
log.debug("Returning list of size: " + records.size());
return new DataTableResponse<>(records, input);
}
/**
* Upload and processes a reference manifest(s).
*
* @param files the files to process
* @param attr the redirection attributes
* @return the redirection view
* @throws URISyntaxException if malformed URI
* @throws Exception if malformed URI
*/
@RequestMapping(value = "/upload", method = RequestMethod.POST)
protected RedirectView upload(
@RequestParam("file") final MultipartFile[] files,
final RedirectAttributes attr) throws URISyntaxException, Exception {
Map<String, Object> model = new HashMap<>();
PageMessages messages = new PageMessages();
String fileName;
Pattern baseRimPattern = Pattern.compile(BASE_RIM_FILE_PATTERN);
Pattern supportRimPattern = Pattern.compile(SUPPORT_RIM_FILE_PATTERN);
Matcher matcher;
List<BaseReferenceManifest> baseRims = new ArrayList<>();
List<SupportReferenceManifest> supportRims = new ArrayList<>();
log.info(String.format("Processing %s uploaded files", files.length));
// loop through the files
for (MultipartFile file : files) {
boolean isBaseRim;
boolean isSupportRim = false;
fileName = file.getOriginalFilename();
matcher = baseRimPattern.matcher(fileName);
isBaseRim = matcher.matches();
if (!isBaseRim) {
matcher = supportRimPattern.matcher(fileName);
isSupportRim = matcher.matches();
}
if (isBaseRim || isSupportRim) {
parseRIM(file, isSupportRim, messages, baseRims, supportRims);
} else {
String errorString = "The file extension of " + fileName + " was not recognized." +
" Base RIMs support the extension \".swidtag\", and support RIMs support " +
"\".rimpcr\", \".rimel\", \".bin\", and \".log\". " +
"Please verify your upload and retry.";
log.error("File extension in " + fileName + " not recognized as base or support RIM.");
messages.addError(errorString);
}
}
baseRims.stream().forEach((rim) -> {
log.info(String.format("Storing swidtag %s", rim.getFileName()));
this.referenceManifestRepository.save(rim);
});
supportRims.stream().forEach((rim) -> {
log.info(String.format("Storing event log %s", rim.getFileName()));
this.referenceManifestRepository.save(rim);
});
// Prep a map to associated the swidtag payload hash to the swidtag.
// pass it in to update support rims that either were uploaded
// or already exist
// create a map of the supports rims in case an uploaded swidtag
// isn't one to one with the uploaded support rims.
Map<String, SupportReferenceManifest> updatedSupportRims
= updateSupportRimInfo(referenceManifestRepository.findAllSupportRims());
// pass in the updated support rims
// and either update or add the events
processTpmEvents(new ArrayList<SupportReferenceManifest>(updatedSupportRims.values()));
//Add messages to the model
model.put(MESSAGES_ATTRIBUTE, messages);
return redirectTo(Page.REFERENCE_MANIFESTS,
new NoPageParams(), model, attr);
}
/**
* Archives (soft delete) the Reference Integrity Manifest entry.
*
* @param id the UUID of the rim to delete
* @param attr RedirectAttributes used to forward data back to the original
* page.
* @return redirect to this page
* @throws URISyntaxException if malformed URI
*/
@RequestMapping(value = "/delete", method = RequestMethod.POST)
public RedirectView delete(@RequestParam final String id,
final RedirectAttributes attr) throws URISyntaxException {
log.info("Handling request to delete " + id);
Map<String, Object> model = new HashMap<>();
PageMessages messages = new PageMessages();
try {
ReferenceManifest referenceManifest = getRimFromDb(id);
if (referenceManifest == null) {
String notFoundMessage = "Unable to locate RIM with ID: " + id;
messages.addError(notFoundMessage);
log.warn(notFoundMessage);
} else {
// if support rim, update associated events
referenceManifest.archive();
referenceManifestRepository.save(referenceManifest);
String deleteCompletedMessage = "RIM successfully deleted";
messages.addInfo(deleteCompletedMessage);
log.info(deleteCompletedMessage);
}
} catch (IllegalArgumentException iaEx) {
String uuidError = "Failed to parse ID from: " + id;
messages.addError(uuidError);
log.error(uuidError, iaEx);
} catch (DBManagerException dbmEx) {
String dbError = "Failed to archive cert: " + id;
messages.addError(dbError);
log.error(dbError, dbmEx);
}
model.put(MESSAGES_ATTRIBUTE, messages);
return redirectTo(Page.REFERENCE_MANIFESTS, new NoPageParams(), model, attr);
}
/**
* Handles request to download the rim by writing it to the response stream
* for download.
*
* @param id the UUID of the rim to download
* @param response the response object (needed to update the header with the
* file name)
* @throws java.io.IOException when writing to response output stream
*/
@RequestMapping(value = "/download", method = RequestMethod.GET)
public void download(@RequestParam final String id,
final HttpServletResponse response)
throws IOException {
log.info("Handling RIM request to download " + id);
try {
ReferenceManifest referenceManifest = getRimFromDb(id);
if (referenceManifest == null) {
String notFoundMessage = "Unable to locate RIM with ID: " + id;
log.warn(notFoundMessage);
// send a 404 error when invalid Reference Manifest
response.sendError(HttpServletResponse.SC_NOT_FOUND);
} else {
StringBuilder fileName = new StringBuilder("filename=\"");
fileName.append(referenceManifest.getFileName());
// Set filename for download.
response.setHeader("Content-Disposition", "attachment;" + fileName);
response.setContentType("application/octet-stream");
// write cert to output stream
response.getOutputStream().write(referenceManifest.getRimBytes());
}
} catch (IllegalArgumentException ex) {
String uuidError = "Failed to parse ID from: " + id;
log.error(uuidError, ex);
// send a 404 error when invalid certificate
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
/**
* Handles request to download bulk of RIMs by writing it to the response stream
* for download in bulk.
*
* @param response the response object (needed to update the header with the
* file name)
* @throws java.io.IOException when writing to response output stream
*/
@RequestMapping(value = "/bulk", method = RequestMethod.GET)
public void bulk(final HttpServletResponse response)
throws IOException {
log.info("Handling request to download all Reference Integrity Manifests");
String fileName = "rims.zip";
String zipFileName;
// Set filename for download.
response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
response.setContentType("application/zip");
List<ReferenceManifest> referenceManifestList = new LinkedList<>();
for (ReferenceManifest rim : referenceManifestRepository.findAll()) {
if ((rim instanceof BaseReferenceManifest)
|| (rim instanceof SupportReferenceManifest)) {
referenceManifestList.add(rim);
}
}
try (ZipOutputStream zipOut = new ZipOutputStream(response.getOutputStream())) {
// get all files
for (ReferenceManifest rim : referenceManifestList) {
if (rim.getFileName().isEmpty()) {
zipFileName = "";
} else {
// configure the zip entry, the properties of the 'file'
zipFileName = rim.getFileName();
}
ZipEntry zipEntry = new ZipEntry(zipFileName);
zipEntry.setSize((long) rim.getRimBytes().length * Byte.SIZE);
zipEntry.setTime(System.currentTimeMillis());
zipOut.putNextEntry(zipEntry);
// the content of the resource
StreamUtils.copy(rim.getRimBytes(), zipOut);
zipOut.closeEntry();
}
zipOut.finish();
// write cert to output stream
} catch (IllegalArgumentException ex) {
String uuidError = "Failed to parse ID from: ";
log.error(uuidError, ex);
// send a 404 error when invalid certificate
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
/**
* This method takes the parameter and looks for this information in the
* Database.
*
* @param id of the RIM
* @return the associated RIM from the DB
* @throws IllegalArgumentException
*/
private ReferenceManifest getRimFromDb(final String id) throws IllegalArgumentException {
UUID uuid = UUID.fromString(id);
if (referenceManifestRepository.existsById(uuid)) {
return referenceManifestRepository.getReferenceById(uuid);
} else {
return null;
}
}
/**
* Takes the rim files provided and returns a {@link ReferenceManifest}
* object.
*
* @param file the provide user file via browser.
* @param supportRIM matcher result
* @param messages the object that handles displaying information to the
* user.
* @param baseRims object to store multiple files
* @param supportRims object to store multiple files
* @return a single or collection of reference manifest files.
*/
private void parseRIM(
final MultipartFile file, final boolean supportRIM,
final PageMessages messages, final List<BaseReferenceManifest> baseRims,
final List<SupportReferenceManifest> supportRims) {
byte[] fileBytes = new byte[0];
String fileName = file.getOriginalFilename();
BaseReferenceManifest baseRim;
SupportReferenceManifest supportRim;
// build the manifest from the uploaded bytes
try {
fileBytes = file.getBytes();
} catch (IOException e) {
final String failMessage
= String.format("Failed to read uploaded file (%s): ", fileName);
log.error(failMessage, e);
messages.addError(failMessage + e.getMessage());
}
try {
if (supportRIM) {
supportRim = new SupportReferenceManifest(fileName, fileBytes);
if (referenceManifestRepository.findByHexDecHashAndRimType(
supportRim.getHexDecHash(), supportRim.getRimType()) == null) {
supportRims.add(supportRim);
messages.addInfo("Saved support RIM " + fileName);
}
} else {
baseRim = new BaseReferenceManifest(fileName, fileBytes);
if (referenceManifestRepository.findByHexDecHashAndRimType(
baseRim.getHexDecHash(), baseRim.getRimType()) == null) {
baseRims.add(baseRim);
messages.addInfo("Saved base RIM " + fileName);
}
}
} catch (IOException | NullPointerException ioEx) {
final String failMessage
= String.format("Failed to parse support RIM file (%s): ", fileName);
log.error(failMessage, ioEx);
messages.addError(failMessage + ioEx.getMessage());
} catch (UnmarshalException e) {
final String failMessage
= String.format("Failed to parse base RIM file (%s): ", fileName);
log.error(failMessage, e);
messages.addError(failMessage + e.getMessage());
} catch (Exception e) {
final String failMessage
= String.format("Failed to parse (%s): ", fileName);
log.error(failMessage, e);
}
}
private Map<String, SupportReferenceManifest> updateSupportRimInfo(
final List<SupportReferenceManifest> dbSupportRims) {
SupportReferenceManifest supportRim;
String fileString;
Map<String, SupportReferenceManifest> updatedSupportRims = new HashMap<>();
Map<String, SupportReferenceManifest> hashValues = new HashMap<>();
for (SupportReferenceManifest support : dbSupportRims) {
hashValues.put(support.getHexDecHash(), support);
}
for (BaseReferenceManifest dbBaseRim : referenceManifestRepository.findAllBaseRims()) {
for (String supportHash : hashValues.keySet()) {
fileString = new String(dbBaseRim.getRimBytes(), StandardCharsets.UTF_8);
if (fileString.contains(supportHash)) {
supportRim = hashValues.get(supportHash);
// I have to assume the baseRim is from the database
// Updating the id values, manufacturer, model
if (supportRim != null && !supportRim.isUpdated()) {
supportRim.setSwidTagVersion(dbBaseRim.getSwidTagVersion());
supportRim.setPlatformManufacturer(dbBaseRim.getPlatformManufacturer());
supportRim.setPlatformModel(dbBaseRim.getPlatformModel());
supportRim.setTagId(dbBaseRim.getTagId());
supportRim.setAssociatedRim(dbBaseRim.getId());
supportRim.setUpdated(true);
referenceManifestRepository.save(supportRim);
updatedSupportRims.put(supportHash, supportRim);
}
}
}
}
return updatedSupportRims;
}
/**
* If the support rim is a supplemental or base, this method looks for the
* original oem base rim to associate with each event.
* @param supportRim assumed db object
* @return reference to the base rim
*/
private ReferenceManifest findBaseRim(final SupportReferenceManifest supportRim) {
if (supportRim != null && (supportRim.getId() != null
&& !supportRim.getId().toString().equals(""))) {
List<BaseReferenceManifest> baseRims = new LinkedList<>();
baseRims.addAll(this.referenceManifestRepository
.getBaseByManufacturerModel(supportRim.getPlatformManufacturer(),
supportRim.getPlatformModel()));
for (BaseReferenceManifest base : baseRims) {
if (base.isBase()) {
// there should be only one
return base;
}
}
}
return null;
}
private void processTpmEvents(final List<SupportReferenceManifest> dbSupportRims) {
List<ReferenceDigestValue> tpmEvents;
TCGEventLog logProcessor = null;
ReferenceManifest baseRim;
ReferenceDigestValue newRdv;
for (SupportReferenceManifest dbSupport : dbSupportRims) {
// So first we'll have to pull values based on support rim
// get by support rim id NEXT
if (dbSupport.getPlatformManufacturer() != null) {
tpmEvents = referenceDigestValueRepository.findBySupportRimId(dbSupport.getId());
baseRim = findBaseRim(dbSupport);
if (tpmEvents.isEmpty()) {
try {
logProcessor = new TCGEventLog(dbSupport.getRimBytes());
for (TpmPcrEvent tpe : logProcessor.getEventList()) {
newRdv = new ReferenceDigestValue(baseRim.getId(),
dbSupport.getId(), dbSupport.getPlatformManufacturer(),
dbSupport.getPlatformModel(), tpe.getPcrIndex(),
tpe.getEventDigestStr(), dbSupport.getHexDecHash(),
tpe.getEventTypeStr(),false, false,
true, tpe.getEventContent());
this.referenceDigestValueRepository.save(newRdv);
}
} catch (CertificateException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
} else {
for (ReferenceDigestValue rdv : tpmEvents) {
if (!rdv.isUpdated()) {
rdv.updateInfo(dbSupport, baseRim.getId());
this.referenceDigestValueRepository.save(rdv);
}
}
}
}
}
}
}