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 gov.nist.secauto.swid.builder.AbstractLanguageSpecificBuilder;
30  import gov.nist.secauto.swid.builder.EntityBuilder;
31  import gov.nist.secauto.swid.builder.LinkBuilder;
32  import gov.nist.secauto.swid.builder.MetaBuilder;
33  import gov.nist.secauto.swid.builder.Role;
34  import gov.nist.secauto.swid.builder.SWIDBuilder;
35  import gov.nist.secauto.swid.builder.ValidationException;
36  import gov.nist.secauto.swid.builder.resource.AbstractResourceBuilder;
37  import gov.nist.secauto.swid.builder.resource.AbstractResourceCollectionBuilder;
38  import gov.nist.secauto.swid.builder.resource.EvidenceBuilder;
39  import gov.nist.secauto.swid.builder.resource.HashAlgorithm;
40  import gov.nist.secauto.swid.builder.resource.HashUtils;
41  import gov.nist.secauto.swid.builder.resource.PathRelativizer;
42  import gov.nist.secauto.swid.builder.resource.PayloadBuilder;
43  import gov.nist.secauto.swid.builder.resource.ResourceBuilder;
44  import gov.nist.secauto.swid.builder.resource.ResourceCollectionEntryGenerator;
45  import gov.nist.secauto.swid.builder.resource.file.AbstractFileSystemItemBuilder;
46  import gov.nist.secauto.swid.builder.resource.file.DirectoryBuilder;
47  import gov.nist.secauto.swid.builder.resource.file.FileBuilder;
48  import gov.nist.secauto.swid.builder.resource.firmware.FirmwareBuilder;
49  
50  import org.jdom2.Document;
51  import org.jdom2.Element;
52  import org.jdom2.Namespace;
53  import org.jdom2.output.Format;
54  import org.jdom2.output.XMLOutputter;
55  
56  import java.io.IOException;
57  import java.io.OutputStream;
58  import java.time.ZonedDateTime;
59  import java.time.format.DateTimeFormatter;
60  import java.util.List;
61  import java.util.Map;
62  
63  public class XMLOutputHandler implements OutputHandler {
64    public static final Namespace SWID_NAMESPACE
65        = Namespace.getNamespace("http://standards.iso.org/iso/19770/-2/2015/schema.xsd");
66  
67    private final Format format;
68  
69    public XMLOutputHandler() {
70      this(Format.getPrettyFormat());
71    }
72  
73    public XMLOutputHandler(Format format) {
74      this.format = format;
75    }
76  
77    @Override
78    public void write(SWIDBuilder builder, OutputStream os) throws IOException, ValidationException {
79      XMLOutputter out = new XMLOutputter(format);
80      out.output(generateXML(builder), os);
81    }
82  
83    /**
84     * Creates a JDOM2 XML Document based on the content of the builder.
85     * 
86     * @param builder
87     *          the {@link SWIDBuilder} to use the information from to build the XML model
88     * @return a JDOM2 {@link Document} based on the SWID information
89     * @throws ValidationException
90     *           if the SWID to be built is invalid
91     */
92    public Document generateXML(SWIDBuilder builder) throws ValidationException {
93      builder.validate();
94  
95      Document retval = buildDocument(builder);
96      return retval;
97    }
98  
99    protected Document buildDocument(SWIDBuilder builder) {
100     return new Document(build(builder));
101   }
102 
103   protected Element build(SWIDBuilder builder) {
104     Element element = new Element("SoftwareIdentity", SWID_NAMESPACE);
105 
106     buildAbstractLanguageSpecificBuilder(builder, element);
107 
108     // required attributes
109     element.setAttribute("name", builder.getName());
110     element.setAttribute("tagId", builder.getTagId());
111 
112     // optional attributes
113     switch (builder.getTagType()) {
114     case PRIMARY:
115       break;
116     case CORPUS:
117       element.setAttribute("corpus", Boolean.TRUE.toString());
118       break;
119     case PATCH:
120       element.setAttribute("patch", Boolean.TRUE.toString());
121       break;
122     case SUPPLEMENTAL:
123       element.setAttribute("supplemental", Boolean.TRUE.toString());
124       break;
125     default:
126       throw new IllegalStateException("tagType: " + builder.getTagType().toString());
127     }
128 
129     element.setAttribute("tagVersion", builder.getTagVersion().toString());
130 
131     buildAttribute("version", builder.getVersion(), element);
132     buildAttribute("versionScheme", builder.getVersionScheme().getName(), element);
133 
134     // child elements
135     // Required
136     for (EntityBuilder entity : builder.getEntities()) {
137       element.addContent(build(entity));
138     }
139 
140     // optional
141     EvidenceBuilder evidence = builder.getEvidence();
142     if (evidence != null) {
143       element.addContent(build(evidence));
144     }
145 
146     for (LinkBuilder link : builder.getLinks()) {
147       element.addContent(build(link));
148     }
149 
150     for (MetaBuilder meta : builder.getMetas()) {
151       element.addContent(build(meta));
152     }
153 
154     PayloadBuilder payload = builder.getPayload();
155     if (payload != null) {
156       element.addContent(build(payload));
157     }
158 
159     return element;
160   }
161 
162   protected Element build(EntityBuilder builder) {
163     Element element = new Element("Entity", SWID_NAMESPACE);
164 
165     buildAbstractLanguageSpecificBuilder(builder, element);
166 
167     // required attributes
168     element.setAttribute("name", builder.getName());
169 
170     StringBuilder sb = null;
171     for (Role role : builder.getRoles()) {
172       if (sb == null) {
173         sb = new StringBuilder();
174       } else {
175         sb.append(' ');
176       }
177       sb.append(role.getName());
178     }
179     if (sb != null) {
180       element.setAttribute("role", sb.toString());
181     }
182 
183     // optional attributes
184     buildAttribute("regid", builder.getRegid(), element);
185     buildAttribute("thumbprint", builder.getThumbprint(), element);
186 
187     return element;
188   }
189 
190   protected Element build(EvidenceBuilder builder) {
191     Element element = new Element("Evidence", SWID_NAMESPACE);
192 
193     buildAbstractResourceCollectionBuilder(builder, element);
194 
195     ZonedDateTime date = builder.getDate();
196     if (date != null) {
197       element.setAttribute("date", date.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
198     }
199     buildAttribute("deviceId", builder.getDeviceId(), element);
200 
201     return element;
202   }
203 
204   protected Element build(LinkBuilder builder) {
205     Element element = new Element("Link", SWID_NAMESPACE);
206 
207     buildAbstractLanguageSpecificBuilder(builder, element);
208 
209     // required attributes
210     element.setAttribute("href", builder.getHref().toString());
211     element.setAttribute("rel", builder.getRel());
212 
213     // optional attributes
214     buildAttribute("artifact", builder.getArtifact(), element);
215     buildAttribute("media", builder.getMedia(), element);
216     buildAttribute("ownership", builder.getOwnership(), element);
217     buildAttribute("type", builder.getMediaType(), element);
218     buildAttribute("use", builder.getUse(), element);
219 
220     return element;
221   }
222 
223   protected Element build(MetaBuilder builder) {
224     Element element = new Element("Meta", SWID_NAMESPACE);
225 
226     buildAttribute("activationStatus", builder.getActivationStatus(), element);
227     buildAttribute("channelType", builder.getChannelType(), element);
228     buildAttribute("colloquialVersion", builder.getColloquialVersion(), element);
229     buildAttribute("description", builder.getDescription(), element);
230     buildAttribute("edition", builder.getEdition(), element);
231     buildAttribute("entitlementDataRequired", builder.getEntitlementDataRequired(), element);
232     buildAttribute("entitlementKey", builder.getEntitlementKey(), element);
233     buildAttribute("generator", builder.getGenerator(), element);
234     buildAttribute("persistentId", builder.getPersistentId(), element);
235     buildAttribute("product", builder.getProductBaseName(), element);
236     buildAttribute("productFamily", builder.getProductFamily(), element);
237     buildAttribute("revision", builder.getRevision(), element);
238     buildAttribute("summary", builder.getSummary(), element);
239     buildAttribute("unspscCode", builder.getUnspscCode(), element);
240     buildAttribute("unspscVersion", builder.getUnspscVersion(), element);
241 
242     return element;
243   }
244 
245   protected Element build(PayloadBuilder builder) {
246     Element element = new Element("Payload", SWID_NAMESPACE);
247 
248     buildAbstractResourceCollectionBuilder(builder, element);
249 
250     return element;
251   }
252 
253   private static <E extends AbstractResourceCollectionBuilder<E>> void
254       buildAbstractResourceCollectionBuilder(AbstractResourceCollectionBuilder<E> builder, Element element) {
255     buildAbstractLanguageSpecificBuilder(builder, element);
256 
257     XMLResourceCollectionEntryGenerator creator = new XMLResourceCollectionEntryGenerator();
258     for (ResourceBuilder resourceBuilder : builder.getResources()) {
259       resourceBuilder.accept(element, creator);
260     }
261   }
262 
263   private static <E extends AbstractLanguageSpecificBuilder<E>> void
264       buildAbstractLanguageSpecificBuilder(AbstractLanguageSpecificBuilder<E> builder, Element element) {
265     String language = builder.getLanguage();
266     if (language != null) {
267       element.setAttribute("lang", language, Namespace.XML_NAMESPACE);
268     }
269   }
270 
271   private static void buildAttribute(String attributeName, String value, Element element) {
272     if (value != null) {
273       element.setAttribute(attributeName, value);
274     }
275   }
276 
277   private static void buildAttribute(String attributeName, Object value, Element element) {
278     if (value != null) {
279       element.setAttribute(attributeName, value.toString());
280     }
281   }
282 
283   private static class XMLResourceCollectionEntryGenerator implements ResourceCollectionEntryGenerator<Element> {
284     public XMLResourceCollectionEntryGenerator() {
285     }
286 
287     @Override
288     public void generate(Element parent, DirectoryBuilder builder) {
289       Element element = new Element("Directory", XMLOutputHandler.SWID_NAMESPACE);
290       parent.addContent(element);
291 
292       buildAbstractFileSystemItem(builder, element);
293 
294       for (ResourceBuilder child : builder.getResources()) {
295         child.accept(element, this);
296       }
297     }
298 
299     @Override
300     public void generate(Element parent, FileBuilder builder) {
301 
302       Element element = new Element("File", XMLOutputHandler.SWID_NAMESPACE);
303       parent.addContent(element);
304 
305       buildAbstractFileSystemItem(builder, element);
306 
307       XMLOutputHandler.buildAttribute("size", builder.getSize(), element);
308       XMLOutputHandler.buildAttribute("version", builder.getVersion(), element);
309 
310       Element rootElement = parent;
311       while (rootElement.getParentElement() != null) {
312         rootElement = rootElement.getParentElement();
313       }
314       for (Map.Entry<HashAlgorithm, byte[]> entry : builder.getHashAlgorithmToValueMap().entrySet()) {
315         HashAlgorithm algorithm = entry.getKey();
316         byte[] hashValue = entry.getValue();
317 
318         Namespace ns = Namespace.getNamespace(algorithm.getName(), algorithm.getNamespace());
319         Namespace nsOld = rootElement.getNamespace(ns.getPrefix());
320 
321         if (nsOld == null) {
322           rootElement.addNamespaceDeclaration(ns);
323         } else if (!nsOld.getURI().equals(ns.getURI())) {
324           element.addNamespaceDeclaration(ns);
325         }
326         element.setAttribute("hash", HashUtils.toHexString(hashValue), ns);
327       }
328     }
329 
330     @Override
331     public void generate(Element parent, FirmwareBuilder firmwareBuilder) {
332       throw new UnsupportedOperationException("firmware is not supported by the XML SWID format");
333     }
334 
335     private static <E extends AbstractFileSystemItemBuilder<E>> void
336         buildAbstractFileSystemItem(AbstractFileSystemItemBuilder<E> builder, Element element) {
337       buildAbstractResourceBuilder(builder, element);
338 
339       XMLOutputHandler.buildAttribute("root", builder.getRoot(), element);
340       List<String> location = builder.getLocation();
341       if (location != null && !location.isEmpty()) {
342         element.setAttribute("location", PathRelativizer.toURI(location).toString());
343       }
344       XMLOutputHandler.buildAttribute("name", builder.getName(), element);
345       XMLOutputHandler.buildAttribute("key", builder.getKey(), element);
346     }
347 
348     private static <E extends AbstractResourceBuilder<E>> void
349         buildAbstractResourceBuilder(AbstractResourceBuilder<E> builder, Element element) {
350       XMLOutputHandler.buildAbstractLanguageSpecificBuilder(builder, element);
351     }
352   }
353 }