Java TAXII Server Implementation


TAXII is a protocol standard for sharing security threat information between systems.  TAXII operates over HTTP/HTTPS and uses a specific XML schema for the message payloads.  Typically STIX is used as the main content of the messages.  TAXII is slowly being adopted by various organizations, particularly the banking industry.  The folks backing TAXII have provided YETI, a basic Python/Django TAXII server implementation and a Python client as well.  They also have a Java client which provides all the JAXB XML mappings for the specification.  In most enterprise environments, Python will not be ideal.  I decided to take the TAXII Java Client code and produce a simple Java Jersey (JAX-RS) TAXII server implementation of the DISCOVERY and POLL endpoints only. There are lots of println’s to show what is going on.  I built the java-taxii  and stix-binding code into a jars and loaded them into a local Maven repo to have available.

Dependencies: com.sun.jersey:jersey-bundle:1.17, javax.servlet:servlet-api:2.4, org.mitre.stix:stix-bindings:1.0 and org.mitre.taxii:java-taxii-all:1.0

Avalanche/SoltraEdge is a nice implementation of TAXII with a great UI, but licensing of the community edition is very limited in terms of usage and customization/modification.  It is developed in Python/Django as well and is most likely a spin-off from YETI.  Their website is incomplete, so it is hard to know what their commercial offerings are.  If you already have your security threat data stored somewhere, you’ll want to be able to customize your own TAXII interface in front of this data store.  Even YETI assumes you want to store incoming data in its own SQLITE database and serve it up from there.

The protocol is well documented, so it is easy to understand what each field is for so you can abide by the specifications.  The INBOX and COLLECTION_MANAGEMENT endpoints are more complex than the POLL and DISCOVERY endpoints, but can be done in a similar fashion.

Follow-up post with a sample/starter collection management endpoint: Java Taxii Collection Management Endpoint

import java.io.*;
import java.math.BigInteger;
import java.net.URI;
import java.util.*;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import javax.xml.bind.*;
import javax.xml.datatype.*;
import org.mitre.taxii.*;
import org.mitre.taxii.client.HttpClient;
import org.mitre.taxii.messages.TaxiiXml;
import org.mitre.taxii.messages.xml11.*;

@Path("/")
public class TestEndpoints {

    /* Example request headers
            User-Agent: java-taxii.httpclient
            content-type: application/xml
            accept: application/xml
            x-taxii-accept: urn:taxii.mitre.org:message:xml:1.1
            x-taxii-content-type: urn:taxii.mitre.org:message:xml:1.1
            x-taxii-services: urn:taxii.mitre.org:services:1.1
            x-taxii-protocol: urn:taxii.mitre.org:protocol:http:1.0
    */
    
    // Are these thread-safe???
    private ObjectFactory factory = new ObjectFactory();
    private TaxiiXmlFactory txf = new TaxiiXmlFactory();
    private TaxiiXml taxiiXml = txf.createTaxiiXml();
    
    @POST
    @Path("discovery")
    @Produces (MediaType.APPLICATION_XML)
    @Consumes (MediaType.APPLICATION_XML)
    public Response discovery(@Context HttpServletRequest request, String x) {
        try {
            printHeaders(request);

            System.out.println("---------- Request:");
            DiscoveryRequest discoveryRequest = (DiscoveryRequest) getRequestObject(x);
            System.out.println(toXml(discoveryRequest));

            System.out.println("---------- Response:");
            List services = new ArrayList<>();
            services.add(factory.createServiceInstanceType()
                    .withServiceType(ServiceTypeEnum.POLL)
                    .withAddress("/poll")
                    .withAvailable(true)
                    .withProtocolBinding(Versions.VID_TAXII_HTTP_10)
                    .withServiceVersion(Versions.VID_TAXII_SERVICES_11)
                    .withMessageBindings(Versions.VID_TAXII_XML_11)
                    .withMessage("Super awesome data comes from this service")
                    .withContentBindings(factory.createContentBindingIDType().withBindingId(ContentBindings.CB_STIX_XML_111))
            );
            
            DiscoveryResponse discoveryResponse = factory.createDiscoveryResponse()
                    .withInResponseTo(discoveryRequest.getMessageId())
                    .withMessageId(MessageHelper.generateMessageId())
                    .withServiceInstances(services);
            
            String responseString = toXml(discoveryResponse);
            System.out.println(taxiiXml.marshalToString(discoveryResponse, true));
            
            return generateResponse(responseString, request);
        } catch(Exception ex) {
            ex.printStackTrace();
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
        }
    }
    
    @POST
    @Path("poll")
    @Produces (MediaType.APPLICATION_XML)
    @Consumes (MediaType.APPLICATION_XML)
    public Response poll(@Context HttpServletRequest request, String x) {
        try {
            printHeaders(request);

            System.out.println("---------- Request:");
            PollRequest pollRequest = (PollRequest) getRequestObject(x);
            System.out.println(toXml(pollRequest));
            String type = pollRequest.getPollParameters().getResponseType().equals(ResponseTypeEnum.FULL) ? "FULL" : "COUNT ONLY";
            System.out.println("Response Type: " + type);
            System.out.println("Collection: " + pollRequest.getCollectionName());
            System.out.println("Start Time: " + (pollRequest.getExclusiveBeginTimestamp() != null ? pollRequest.getExclusiveBeginTimestamp().toXMLFormat() : "(none)"));
            System.out.println("End Time: " + (pollRequest.getInclusiveEndTimestamp() != null ? pollRequest.getInclusiveEndTimestamp().toXMLFormat() : "(none)"));

            System.out.println("---------- Response:");
            PollResponse pollResponse = factory.createPollResponse()
                    .withInResponseTo(pollRequest.getMessageId())
                    .withMessageId(MessageHelper.generateMessageId())
                    .withCollectionName(pollRequest.getCollectionName())
                    .withRecordCount(factory.createRecordCountType().withValue(BigInteger.valueOf(9999)).withPartialCount(false)) 
                    .withExclusiveBeginTimestamp(pollRequest.getExclusiveBeginTimestamp())
                    .withInclusiveEndTimestamp(pollRequest.getInclusiveEndTimestamp())
                    .withContentBlocks(
                            factory.createContentBlock()
                                    .withContentBinding(factory.createContentInstanceType().withBindingId(ContentBindings.CB_STIX_XML_111))
                                    .withContent(factory.createAnyMixedContentType().withContent("Content Block Stuff Goes Here, STIX for example"))
                                    .withTimestampLabel(getTimestamp(null))
                                    .withMessage("Here's your data")
                    );
            
            String responseString = toXml(pollResponse);
            System.out.println(taxiiXml.marshalToString(pollResponse, true));
            
            return generateResponse(responseString, request);
        } catch(Exception ex) {
            ex.printStackTrace();
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
        }
    }
    
    private Response generateResponse(String responseString, HttpServletRequest request) throws Exception {
        return Response.ok(responseString)
                    .header(HttpClient.HEADER_X_TAXII_PROTOCOL, getProtocol(request))
                    .header(HttpClient.HEADER_X_TAXII_CONTENT_TYPE, Versions.VID_TAXII_XML_11)
                    .header(HttpClient.HEADER_X_TAXII_SERVICES, Versions.VID_TAXII_SERVICES_11)
                    .build();
    }
    
    private String getProtocol(HttpServletRequest request) throws Exception {
        String scheme = new URI(request.getRequestURL().toString()).getScheme();
        if(scheme != null && scheme.equalsIgnoreCase("https")) {
            return Versions.VID_TAXII_HTTPS_10;
        } else {
            return Versions.VID_TAXII_HTTP_10;
        }
    }
    
    private void printHeaders(HttpServletRequest request) {
        System.out.println("--------------------------------------------");
        List headerNames = Collections.list(request.getHeaderNames());         
        for(String name : headerNames) {
            System.out.println(name + ": " + request.getHeader(name));
        }
    }

    private String toXml(Object discoveryResponse) throws Exception {
        final Marshaller m = taxiiXml.createMarshaller(false); 
        m.setProperty(Marshaller.JAXB_FRAGMENT, true); // Don't generate xml declaration.
        final StringWriter sw = new StringWriter();
        m.marshal(discoveryResponse, sw);
        return sw.toString();
    }

    private Object getRequestObject(String x) throws Exception {
        Unmarshaller um = taxiiXml.getJaxbContext().createUnmarshaller();
        return um.unmarshal(new StringReader(x));
    }

    private XMLGregorianCalendar getTimestamp(Date dte) throws Exception {
        if(dte == null) {
            dte = new Date();
        }
        GregorianCalendar gc = new GregorianCalendar();
        gc.setTime(dte);
        return DatatypeFactory.newInstance().newXMLGregorianCalendar(gc);
    }
Advertisements

2 thoughts on “Java TAXII Server Implementation

  1. First of all, thank you for this post. I was able to get the service up and running fairly quickly.

    I had a couple of initial issues getting the service to respond. I used MITRE’s YETI implementation to test a simple client request (utilized java-taxii’s simple discovery test, and then a discovery call with response) to ensure the client was performing correctly. I then turned it to this service and got a couple of errors.

    I had to add “Saxon-HE” and “jaxb2-basics-runtime” artifacts to my pom. (I was getting ClassNotFoundExceptions otherwise). Once running I used the method below to test an injtial connection. A more involved discovery class had a more robust response object, but this can confirm whether the service is running and responding.

    Here’s SimpleDiscovery:

    public void simpleDiscovery() throws UnsupportedEncodingException, JAXBException, IOException, URISyntaxException {
    System.out.println();
    System.out.println(“simpleDiscovery”);
    System.out.println();
    HttpClient taxiiClient = new HttpClient();

    final String serverUrl = SERVER_URL + “TAXIIService/taxii-hailer/discovery”;

    // Prepare the message to send.
    DiscoveryRequest dr = of.createDiscoveryRequest()
    .withMessageId(MessageHelper.generateMessageId());

    // Call the services
    Object responseObj = taxiiClient.callTaxiiService(new URI(serverUrl), dr);

    System.out.println(taxiiXml.marshalToString(dr, true));
    System.out.println(taxiiXml.marshalToString(responseObj, true));

    }

    • I just tried your code and it worked against my server. Here are my Maven dependencies for the server:

      com.sun.jersey
      jersey-bundle
      1.17

      asm
      asm
      3.2

      javax.servlet
      servlet-api
      2.4
      provided

      org.mitre.taxii
      java-taxii-all
      1.0

      org.mitre.stix
      stix-bindings
      1.0

      org.mitre.cybox
      cybox-bindings
      1.0

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s