View Javadoc
1   /**
2    * Portions of this software was developed by employees of the National Institute
3    * of Standards and Technology (NIST), an agency of the Federal Government and is
4    * being made available as a public service. Pursuant to title 17 United States
5    * Code Section 105, works of NIST employees are not subject to copyright
6    * protection in the United States. This software may be subject to foreign
7    * copyright. Permission in the United States and in foreign countries, to the
8    * extent that NIST may hold copyright, to use, copy, modify, create derivative
9    * works, and distribute this software and its documentation without fee is hereby
10   * granted on a non-exclusive basis, provided that this notice and disclaimer
11   * of warranty appears in all copies.
12   *
13   * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
14   * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
15   * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
16   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
17   * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
18   * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
19   * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
20   * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
21   * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
22   * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
23   * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
24   * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
25   */
26  
27  package gov.nist.secauto.swid.builder.output;
28  
29  import com.fasterxml.jackson.core.JsonFactory;
30  import com.fasterxml.jackson.core.JsonGenerator;
31  
32  import gov.nist.secauto.swid.builder.AbstractLanguageSpecificBuilder;
33  import gov.nist.secauto.swid.builder.EntityBuilder;
34  import gov.nist.secauto.swid.builder.LinkBuilder;
35  import gov.nist.secauto.swid.builder.MetaBuilder;
36  import gov.nist.secauto.swid.builder.Role;
37  import gov.nist.secauto.swid.builder.SWIDBuilder;
38  import gov.nist.secauto.swid.builder.ValidationException;
39  import gov.nist.secauto.swid.builder.VersionScheme;
40  import gov.nist.secauto.swid.builder.resource.AbstractResourceCollectionBuilder;
41  import gov.nist.secauto.swid.builder.resource.EvidenceBuilder;
42  import gov.nist.secauto.swid.builder.resource.HashAlgorithm;
43  import gov.nist.secauto.swid.builder.resource.PathRelativizer;
44  import gov.nist.secauto.swid.builder.resource.PayloadBuilder;
45  import gov.nist.secauto.swid.builder.resource.ResourceBuilder;
46  import gov.nist.secauto.swid.builder.resource.ResourceCollectionEntryGenerator;
47  import gov.nist.secauto.swid.builder.resource.file.AbstractFileSystemItemBuilder;
48  import gov.nist.secauto.swid.builder.resource.file.DirectoryBuilder;
49  import gov.nist.secauto.swid.builder.resource.file.FileBuilder;
50  import gov.nist.secauto.swid.builder.resource.firmware.FirmwareBuilder;
51  
52  import java.io.IOException;
53  import java.io.OutputStream;
54  import java.time.ZonedDateTime;
55  import java.util.List;
56  import java.util.Map;
57  
58  public abstract class AbstractJsonOutputHandler extends JsonSupport implements OutputHandler {
59    /**
60     * The tag identifier (text).
61     */
62    public static final long TAG_ID_FIELD = 0L;
63  
64    /**
65     * A name (text).
66     */
67    public static final long SWID_NAME_FIELD = 1L;
68    public static final long ENTITY_FIELD = 2L;
69    public static final long EVIDENCE_FIELD = 3L;
70    public static final long LINK_FIELD = 4L;
71    public static final long SOFTWARE_META_FIELD = 5L;
72    public static final long PAYLOAD_FIELD = 6L;
73    public static final long CORPUS_FIELD = 8L;
74    public static final long PATCH_FIELD = 9L;
75    public static final long MEDIA_FIELD = 10L;
76    public static final long SUPPLEMENTAL_FIELD = 11L;
77    public static final long TAG_VERSION_FIELD = 12L;
78    public static final long SOFTWARE_VERSION_FIELD = 13L;
79    public static final long VERSION_SCHEME_FIELD = 14L;
80    public static final long LANG_FIELD = 15L;
81    public static final long DIRECTORY_FIELD = 16L;
82    public static final long FILE_FIELD = 17L;
83    public static final long PROCESS_FIELD = 18L;
84    public static final long RESOURCE_FIELD = 19L;
85  
86    /**
87     * The size of a file (number: long).
88     */
89    public static final long SIZE_FIELD = 20L;
90    public static final long FILE_VERSION_FIELD = 21L;
91  
92    /**
93     * (bool).
94     */
95    public static final long KEY_FIELD = 22L;
96    public static final long LOCATION_FIELD = 23L;
97    public static final long FS_NAME_FIELD = 24L;
98    public static final long ROOT_FIELD = 25L;
99    public static final long PATH_ELEMENTS_FIELD = 26L;
100   public static final long PROCESS_NAME_FIELD = 27L;
101   public static final long PID_FIELD = 28L;
102   public static final long TYPE_FIELD = 29L;
103   public static final long ENTITY_NAME_FIELD = 31L;
104   public static final long REG_ID_FIELD = 32L;
105 
106   /**
107    * The roles (text: space separated).
108    */
109   public static final long ROLE_FIELD = 33L;
110   public static final long THUMBPRINT_FIELD = 34L;
111 
112   public static final long DATE_FIELD = 35L;
113   public static final long DEVICE_ID_FIELD = 36L;
114   public static final long ARTIFACT_FIELD = 37L;
115   public static final long HREF_FIELD = 38L;
116   public static final long OWNERSHIP_FIELD = 39L;
117   public static final long REL_FIELD = 40L;
118   public static final long MEDIA_TYPE_FIELD = 41L;
119   public static final long USE_FIELD = 42L;
120 
121   public static final long ACTIVATION_STATUS_FIELD = 43L;
122   public static final long CHANNEL_TYPE_FIELD = 44L;
123   // colloquial-version = (45: text)
124   public static final long COLLOQUIAL_VERSION_FIELD = 45L;
125   // description = (46: text)
126   public static final long DESCRIPTION_FIELD = 46L;
127   // edition = (47: text)
128   public static final long EDITION_FIELD = 47L;
129   // entitlement-data-required = (48: bool)
130   public static final long ENTITLEMENT_DATA_REQUIRED_FIELD = 48L;
131   // entitlement-key = (49: text)
132   public static final long ENTITLEMENT_KEY_FIELD = 49L;
133   // generator = (50: text)
134   public static final long GENERATOR_FIELD = 50L;
135   // persistent-id = (51: text)
136   public static final long PERSISTENT_ID_FIELD = 51L;
137   // product = (52: text)
138   public static final long PRODUCT_FIELD = 52L;
139   // product-family = (53: text)
140   public static final long PRODUCT_FAMILY_FIELD = 53L;
141   // revision = (54: text)
142   public static final long REVISION_FIELD = 54L;
143   // summary = (55: text)
144   public static final long SUMMARY_FIELD = 55L;
145   // unspsc-code = (56: text)
146   public static final long UNSPSC_CODE_FIELD = 56L;
147   // unspsc-version = (57: text)
148   public static final long UNSPSC_VERSION_FIELD = 57L;
149 
150   public static final long HASH_FIELD = 7L;
151 
152   /**
153    * Firmware.
154    */
155   public static final long FIRMWARE_FIELD = 59L;
156 
157   private final JsonFactory jsonFactory;
158 
159   public AbstractJsonOutputHandler(JsonFactory jsonFactory) {
160     this.jsonFactory = jsonFactory;
161   }
162 
163   protected abstract void writeRole(JsonGenerator generator, Role role) throws IOException;
164 
165   protected abstract void writeVersionScheme(JsonGenerator generator, VersionScheme versionScheme) throws IOException;
166 
167   /**
168    * @return the jsonFactory
169    */
170   public JsonFactory getJsonFactory() {
171     return jsonFactory;
172   }
173 
174   protected JsonGenerator newGenerator(OutputStream os) throws IOException {
175 
176     JsonFactory factory = getJsonFactory();
177 
178     JsonGenerator generator = factory.createGenerator(os);
179     return generator;
180   }
181 
182   @Override
183   public void write(SWIDBuilder builder, OutputStream os) throws IOException, ValidationException {
184     builder.validate();
185 
186     JsonGenerator generator = newGenerator(os);
187 
188     build(generator, builder);
189 
190     generator.close();
191   }
192 
193   protected void build(JsonGenerator generator, SWIDBuilder builder) throws IOException {
194     generator.writeStartObject();
195 
196     buildGlobalAttributes(generator, builder);
197 
198     // required attributes
199     writeTextField(generator, TAG_ID_FIELD, builder.getTagId());
200 
201     writeIntegerField(generator, TAG_VERSION_FIELD, builder.getTagVersion());
202 
203     writeTextField(generator, SWID_NAME_FIELD, builder.getName());
204     if (builder.getVersion() != null) {
205       writeTextField(generator, SOFTWARE_VERSION_FIELD, builder.getVersion());
206     }
207     VersionScheme versionScheme = builder.getVersionScheme();
208     if (versionScheme != null) {
209       writeField(generator, VERSION_SCHEME_FIELD);
210       writeVersionScheme(generator, versionScheme);
211     }
212 
213     // optional attribute
214     switch (builder.getTagType()) {
215     case PRIMARY:
216       break;
217     case CORPUS:
218       writeBooleanField(generator, CORPUS_FIELD, true);
219       break;
220     case PATCH:
221       writeBooleanField(generator, PATCH_FIELD, true);
222       break;
223     case SUPPLEMENTAL:
224       writeBooleanField(generator, SUPPLEMENTAL_FIELD, true);
225       break;
226     default:
227       throw new IllegalStateException("tagType: " + builder.getTagType().toString());
228     }
229 
230     // child elements
231     // Required
232     writeField(generator, ENTITY_FIELD);
233     List<EntityBuilder> entities = builder.getEntities();
234     if (entities.size() > 1) {
235       generator.writeStartArray();
236     }
237 
238     for (EntityBuilder entity : builder.getEntities()) {
239       build(generator, entity);
240     }
241 
242     if (entities.size() > 1) {
243       generator.writeEndArray();
244     }
245 
246     // optional
247     EvidenceBuilder evidence = builder.getEvidence();
248     if (evidence != null) {
249       writeField(generator, EVIDENCE_FIELD);
250       build(generator, evidence);
251     }
252 
253     List<LinkBuilder> links = builder.getLinks();
254     if (!links.isEmpty()) {
255       writeField(generator, LINK_FIELD);
256       if (links.size() > 1) {
257         generator.writeStartArray();
258       }
259       for (LinkBuilder link : links) {
260         build(generator, link);
261       }
262       if (links.size() > 1) {
263         generator.writeEndArray();
264       }
265     }
266 
267     List<MetaBuilder> metas = builder.getMetas();
268     if (!links.isEmpty()) {
269       writeField(generator, SOFTWARE_META_FIELD);
270       if (metas.size() > 1) {
271         generator.writeStartArray();
272       }
273       for (MetaBuilder meta : metas) {
274         buildMeta(generator, meta);
275       }
276       if (metas.size() > 1) {
277         generator.writeEndArray();
278       }
279     }
280 
281     PayloadBuilder payload = builder.getPayload();
282     if (payload != null) {
283       writeField(generator, PAYLOAD_FIELD);
284       buildPayload(generator, payload);
285     }
286 
287     // TODO: media
288     //
289     // generator.writeStringField("test", "test");
290     generator.writeEndObject();
291   }
292 
293   protected void build(JsonGenerator generator, EntityBuilder builder) throws IOException {
294 
295     // start of the entity
296     generator.writeStartObject();
297 
298     buildGlobalAttributes(generator, builder);
299 
300     writeTextField(generator, ENTITY_NAME_FIELD, builder.getName());
301     if (builder.getRegid() != null) {
302       writeTextField(generator, REG_ID_FIELD, builder.getRegid());
303     }
304 
305     List<Role> roles = builder.getRoles();
306     writeField(generator, ROLE_FIELD);
307     if (roles.size() > 1) {
308       generator.writeStartArray();
309     }
310     for (Role role : roles) {
311       writeRole(generator, role);
312     }
313     if (roles.size() > 1) {
314       generator.writeEndArray();
315     }
316 
317     if (builder.getThumbprint() != null) {
318       writeTextField(generator, THUMBPRINT_FIELD, builder.getThumbprint());
319     }
320 
321     // for empty meta
322     // writeField(generator, META_ELEMENTS_FIELD);
323     // generator.writeStartArray();
324     // generator.writeEndArray();
325 
326     // end of the entity
327     generator.writeEndObject();
328   }
329 
330   private void build(JsonGenerator generator, EvidenceBuilder builder) throws IOException {
331 
332     // start of the evidence
333     generator.writeStartObject();
334 
335     buildGlobalAttributes(generator, builder);
336 
337     buildResourceCollection(generator, builder);
338 
339     ZonedDateTime date = builder.getDate();
340     if (date != null) {
341       writeDateTimeField(generator, DATE_FIELD, date);
342     }
343 
344     if (builder.getDeviceId() != null) {
345       writeTextField(generator, DEVICE_ID_FIELD, builder.getDeviceId());
346     }
347 
348     // end of the evidence
349     generator.writeEndObject();
350   }
351 
352   private void build(JsonGenerator generator, LinkBuilder builder) throws IOException {
353 
354     // start of the link
355     generator.writeStartObject();
356 
357     buildGlobalAttributes(generator, builder);
358 
359     // required attributes
360     writeTextField(generator, HREF_FIELD, builder.getHref().toString());
361     writeTextField(generator, REL_FIELD, builder.getRel());
362 
363     // optional attributes
364     if (builder.getArtifact() != null) {
365       writeTextField(generator, ARTIFACT_FIELD, builder.getArtifact());
366     }
367     if (builder.getMedia() != null) {
368       writeTextField(generator, MEDIA_FIELD, builder.getMedia());
369     }
370     if (builder.getOwnership() != null) {
371       writeTextField(generator, OWNERSHIP_FIELD, builder.getOwnership().toString());
372     }
373     if (builder.getMediaType() != null) {
374       writeTextField(generator, MEDIA_TYPE_FIELD, builder.getMediaType());
375     }
376     if (builder.getUse() != null) {
377       writeTextField(generator, USE_FIELD, builder.getUse().toString());
378     }
379 
380     // end of the link
381     generator.writeEndObject();
382   }
383 
384   private void buildMeta(JsonGenerator generator, MetaBuilder builder) throws IOException {
385 
386     // start of the meta
387     generator.writeStartObject();
388 
389     buildGlobalAttributes(generator, builder);
390 
391     buildAttribute(generator, ACTIVATION_STATUS_FIELD, builder.getActivationStatus());
392     buildAttribute(generator, CHANNEL_TYPE_FIELD, builder.getChannelType());
393     buildAttribute(generator, COLLOQUIAL_VERSION_FIELD, builder.getColloquialVersion());
394     buildAttribute(generator, DESCRIPTION_FIELD, builder.getDescription());
395     buildAttribute(generator, EDITION_FIELD, builder.getEdition());
396     buildAttribute(generator, ENTITLEMENT_DATA_REQUIRED_FIELD, builder.getEntitlementDataRequired());
397     buildAttribute(generator, ENTITLEMENT_KEY_FIELD, builder.getEntitlementKey());
398     buildAttribute(generator, GENERATOR_FIELD, builder.getGenerator());
399     buildAttribute(generator, PERSISTENT_ID_FIELD, builder.getPersistentId());
400     buildAttribute(generator, PRODUCT_FIELD, builder.getProductBaseName());
401     buildAttribute(generator, PRODUCT_FAMILY_FIELD, builder.getProductFamily());
402     buildAttribute(generator, REVISION_FIELD, builder.getRevision());
403     buildAttribute(generator, SUMMARY_FIELD, builder.getSummary());
404     buildAttribute(generator, UNSPSC_CODE_FIELD, builder.getUnspscCode());
405     buildAttribute(generator, UNSPSC_VERSION_FIELD, builder.getUnspscVersion());
406 
407     // end of the meta
408     generator.writeEndObject();
409   }
410 
411   private void buildAttribute(JsonGenerator generator, long fieldId, String value) throws IOException {
412     if (value != null) {
413       writeTextField(generator, fieldId, value);
414     }
415   }
416 
417   private void buildPayload(JsonGenerator generator, PayloadBuilder builder) throws IOException {
418 
419     // start of the payload
420     generator.writeStartObject();
421 
422     buildGlobalAttributes(generator, builder);
423 
424     buildResourceCollection(generator, builder);
425 
426     // end of the payload
427     generator.writeEndObject();
428   }
429 
430   private <E extends AbstractResourceCollectionBuilder<E>> void buildResourceCollection(JsonGenerator generator,
431       AbstractResourceCollectionBuilder<E> builder) throws IOException {
432     buildGlobalAttributes(generator, builder);
433 
434     JsonResourceCollectionEntryGenerator creator = new JsonResourceCollectionEntryGenerator();
435     {
436       List<DirectoryBuilder> directories = builder.getResources(DirectoryBuilder.class);
437       if (!directories.isEmpty()) {
438         writeDirectories(generator, directories, creator);
439       }
440     }
441     {
442       List<FileBuilder> files = builder.getResources(FileBuilder.class);
443       if (!files.isEmpty()) {
444         writeFiles(generator, files, creator);
445       }
446     }
447     {
448       List<FirmwareBuilder> firmwares = builder.getResources(FirmwareBuilder.class);
449       if (!firmwares.isEmpty()) {
450         writeFirmware(generator, firmwares, creator);
451       }
452     }
453   }
454 
455   private void writeDirectories(JsonGenerator generator, List<DirectoryBuilder> directories,
456       JsonResourceCollectionEntryGenerator creator) throws IOException {
457     writeField(generator, DIRECTORY_FIELD);
458     writeResources(generator, directories, creator);
459   }
460 
461   private void writeFiles(JsonGenerator generator, List<FileBuilder> files,
462       JsonResourceCollectionEntryGenerator creator) throws IOException {
463     writeField(generator, FILE_FIELD);
464     writeResources(generator, files, creator);
465   }
466 
467   private void writeFirmware(JsonGenerator generator, List<FirmwareBuilder> firmwares,
468       JsonResourceCollectionEntryGenerator creator) throws IOException {
469     writeField(generator, FIRMWARE_FIELD);
470     writeResources(generator, firmwares, creator);
471   }
472 
473   private void writeResources(JsonGenerator generator, List<? extends ResourceBuilder> resources,
474       JsonResourceCollectionEntryGenerator creator) throws IOException {
475     // use an array for more than one
476     if (resources.size() > 1) {
477       generator.writeStartArray();
478     }
479     for (ResourceBuilder builder : resources) {
480       builder.accept(generator, creator);
481     }
482     if (resources.size() > 1) {
483       generator.writeEndArray();
484     }
485   }
486 
487   private void writeHash(JsonGenerator generator, HashAlgorithm algorithm, byte[] bytes) throws IOException {
488     writeField(generator, HASH_FIELD);
489     generator.writeStartArray();
490     generator.writeNumber(algorithm.getIndex());
491     generator.writeBinary(bytes);
492     generator.writeEndArray();
493   }
494 
495   private <E extends AbstractLanguageSpecificBuilder<E>> void buildGlobalAttributes(JsonGenerator generator,
496       AbstractLanguageSpecificBuilder<E> builder) throws IOException {
497     String language = builder.getLanguage();
498     if (language != null) {
499       writeTextField(generator, LANG_FIELD, language);
500     }
501   }
502 
503   private class JsonResourceCollectionEntryGenerator implements ResourceCollectionEntryGenerator<JsonGenerator> {
504 
505     public JsonResourceCollectionEntryGenerator() {
506     }
507 
508     @Override
509     public void generate(JsonGenerator parent, DirectoryBuilder builder) {
510       try {
511         parent.writeStartObject();
512 
513         buildGlobalAttributes(parent, builder);
514 
515         buildFileSystemItem(parent, builder);
516 
517         {
518           List<DirectoryBuilder> directories = builder.getResources(DirectoryBuilder.class);
519           if (!directories.isEmpty()) {
520             writeDirectories(parent, directories, this);
521           }
522         }
523         {
524           List<FileBuilder> files = builder.getResources(FileBuilder.class);
525           if (!files.isEmpty()) {
526             writeFiles(parent, files, this);
527           }
528         }
529 
530         parent.writeEndObject();
531       } catch (IOException ex) {
532         throw new RuntimeException(ex);
533       }
534     }
535 
536     @Override
537     public void generate(JsonGenerator parent, FileBuilder builder) {
538 
539       try {
540         parent.writeStartObject();
541 
542         buildGlobalAttributes(parent, builder);
543 
544         buildFileSystemItem(parent, builder);
545 
546         if (builder.getSize() != null) {
547           writeLongField(parent, SIZE_FIELD, builder.getSize());
548         }
549         if (builder.getVersion() != null) {
550           writeTextField(parent, SOFTWARE_VERSION_FIELD, builder.getVersion());
551         }
552 
553         Map<HashAlgorithm, byte[]> hashMap = builder.getHashAlgorithmToValueMap();
554         if (!hashMap.isEmpty()) {
555           if (hashMap.size() > 1) {
556             throw new UnsupportedOperationException("Only a single hash value is allowed");
557           }
558 
559           Map.Entry<HashAlgorithm, byte[]> entry = hashMap.entrySet().iterator().next();
560           writeHash(parent, entry.getKey(), entry.getValue());
561         }
562 
563         // end of the payload
564         parent.writeEndObject();
565       } catch (IOException ex) {
566         throw new RuntimeException(ex);
567       }
568     }
569 
570     @Override
571     public void generate(JsonGenerator generator, FirmwareBuilder builder) {
572       new CBORFirmwareOutputHandler().generate(generator, builder);
573     }
574 
575     private <E extends AbstractFileSystemItemBuilder<E>> void buildFileSystemItem(JsonGenerator parent,
576         AbstractFileSystemItemBuilder<E> builder) throws IOException {
577 
578       // TODO: support meta
579 
580       if (builder.getKey() != null) {
581         writeBooleanField(parent, KEY_FIELD, builder.getKey());
582       }
583 
584       if (builder.getRoot() != null) {
585         writeTextField(parent, ROOT_FIELD, builder.getRoot());
586       }
587 
588       List<String> location = builder.getLocation();
589       if (location != null && !location.isEmpty()) {
590         writeTextField(parent, LOCATION_FIELD, PathRelativizer.toURI(location).toString());
591       }
592       writeTextField(parent, FS_NAME_FIELD, builder.getName());
593     }
594   }
595 }