Skip to content

Instantly share code, notes, and snippets.

@patrickvinograd
Last active December 1, 2017 00:44
Show Gist options
  • Save patrickvinograd/28955b77a1676ef05f3b0865932eb339 to your computer and use it in GitHub Desktop.
Save patrickvinograd/28955b77a1676ef05f3b0865932eb339 to your computer and use it in GitHub Desktop.

This document describes the integration design between Vets.gov and the Veteran ID Card (VIC) service, for the purposes of allowing verified veterans to obtain a physical ID card.

Integration Overview

Vets.gov will provide an entry point for the ID card workflow; when a verified veteran starts the workflow, Vets.gov will gather the required data attributes about that veteran and send a signed request to VIC.

VIC will accept the request from Vets.gov as a trusted source for veteran status and other identifiers. VIC will then provide a web interface allowing users to upload a photo, provide some additional data, and correct some of the data provided by Vets.gov/VA that is known to be unreliable (specifically address data).

VIC will then integrate with a third-party (currently Office Depot) to manage the actual printing and delivery of the physical ID card.

API Description

Baseline Data Fields

VIC service requires the following baseline data fields:

  "edipi": "123456",
  "firstname": "John",
  "lastname": "Von Whitespace",
  "address": "5050 someplace",
  "city": "testCity",
  "state": "WV",
  "zip": "26505",
  "email": "foo@example.com",
  "phone": "",
  "title38status": "V1",
  "branchofservice": "AF",
  "dischargetype": "A"

The above parameters are the minimum required to proceed with the ID Card request. They will be provided via a POST request with a typical form-encoded payload. Additionally, in order to avoid request forgery, some additional parameters will be included in the payload (see "Signing" below).

  • Allowed values for title38status: V1, V2, V3, V4, V5, V6, V7. Values/interpretation provied by VADIR service.
  • Allowed values for branchofservice: ARMY, NAVY, AF, PHS, MC, CG, NOAA. These require translation from the values we retrieve from eMIS.
  • branchofservice may be a list, reflecting that a veteran served in multiple branches. If so it will be passed as a comma-delimited string.
  • Allowed values for dischargetype: A,B,D,E,F,H,J,K,Y,Z. These values are passed exactly as returned from eMIS.
  • dischargetype may be a list, reflecting that a veteran may have had different discharge types from multiple service episodes. If so it will be passed as a comma-delimited string.

Request Signing

Vets.gov is able to initiate the ID Card workflow because it is a trusted entity that has required a user to log in and has verified the user's veteran status. In order to avoid other entities forging a request to VIC to initiate an ID card request, Vets.gov will sign each request using the following mechanism:

Signing

  1. Vets.gov will provide a public key/certificate pubkey to VIC, either offline or at a well-defined URL.
  2. Vets.gov will maintain the corresponding private key privkey.
  3. Vets.gov will calculate a timestamp for the request, to be represented in any JSON object as a string in ISO-8601 format.
  4. For each request to VIC, Vets.gov will generate a canonical string representation of a JSON object containing the following attributes and their values in the following order: edipi, firstname, lastname, address, city, state, zip, email, phone, branchofservice, timestamp. The canonical string representation should not contain any whitespace or linebreaks between elements. Values should be in their original form, not URL-encoded. For example:
{"edipi":"123456","firstname":"John","lastname":"Von Whitespace","address":"5050 someplace","city":"testCity","state":"WV","zip":"26505","email":"foo@example.com","phone":"","title38status":"V1","branchofservice":"AF","dischagetype":"A","timestamp":"2017-08-17T16:23:40Z"}
  1. Vets.gov will calculate a signature signature for the request data by calculating a SHA256 digest of the canonical string representation, signing it with privkey, and base64-encoding the value using the URL-safe base64 encoding described in [RFC 4648] (https://tools.ietf.org/html/rfc4648#section-5).
  2. Vets.gov will include timestamp and signature in the request parameters when making a request to VIC.

Verification

  1. Upon receiving the JSON request payload, VIC should re-create the canonical string representation using the same list of attributes/values in the same order as described above (i.e. by omitting the signature attribute and ensuring the attribute order is otherwise preserved, and URL-decoding all parameter values).
  2. VIC can then verify the signature using the Vets.gov public key, the canonical string representation, and the base64-decoded signature provided in the request payload.
  3. VIC may additionally verify the timestamp of the request is within some threshold from the current time, in order to avoid accepting replayed requests.

Signing/Verification Example

The following command-line scripts demonstrate payload siging/verification in Ruby

Sign an exemplar JSON payload using a private key and output the canonical string, Base64-encoded signature, and URL-encoded query string:

require 'oj'
require 'openssl'
require 'base64'
require 'uri'

data = {
  "edipi" => "123456",
  "firstname" => "John",
  "lastname" => "Von Whitespace",
  "address" => "5050 someplace",
  "city" => "testCity",
  "state" => "WV",
  "zip" => "26505",
  "email" => "foo@example.com",
  "phone" => "",
  "title38status" => "V1",
  "branchofservice" => "AF",
  "dischargetype" => "A"
}

k = OpenSSL::PKey::RSA.new(File.read("vic_key.pem"))

data["timestamp"] = Time.now.utc.iso8601

data_str = Oj.dump(data)

digest = OpenSSL::Digest::SHA256.new
signature = k.sign(digest,data_str)
sig_str = Base64.urlsafe_encode64(signature)
data["signature"] = sig_str

puts "Canonical String:"
puts data_str

puts "Signature:"
puts sig_str

puts "Query String:"
puts URI.encode_www_form(data)

Verify a signature against an exemplar JSON payload using a public key, and output true/fase:

require 'oj'
require 'openssl'
require 'base64'
require 'uri'

data = URI.decode_www_form(ARGV[0]).to_h
signature = data.delete("signature")
signature = Base64.urlsafe_decode64(signature)

c = OpenSSL::X509::Certificate.new(File.read("vic_cert.pem"))
pubkey = c.public_key

data_str = Oj.dump(data)

digest = OpenSSL::Digest::SHA256.new
#signature = Base64.urlsafe_decode64(ARGV[0])

puts pubkey.verify(digest, signature, data_str)

Example Values

Canonical String:

{"edipi":"123456","firstname":"John","lastname":"Von Whitespace","address":"5050 someplace","city":"testCity","state":"WV","zip":"26505","email":"foo@example.com","phone":"","title38status":"V1","branchofservice":"AF","dischargetype":"A","timestamp":"2017-09-25T20:43:02Z"}

Signature (URL-safe Base64-encoded):

GRiMCS2rr_Xx_fOq-2TDof7-FW2tZC-Z3RtOPI94h4ZNaML1jiB2IyJXuEHA8Z5E7lIkXyPMC-VmqnIsxOyBz1-2tmDOLd8OkgNe1FdyKIeVoNwK2DGTKJwpc7cnPl-zHqW9z9HqnHf0vTjkQ_s4eZicAArXB6K63u3waEKomH3NTniK6MYjaFOZ99cjNPpZZ_LV2105cxvGUHdYI9ErPnAoF0ZBR7ntSObs7T7J-ZA7vg_c6wyzm3FrVdqGb8mtWSEb-RyY0waTpbayco_pBP1zGeVRpXhE_yiBYHc62ormTgwTxAgC2Spm0vcLuqs_kxI0kpYtwAwLGXveD6dpHA==

URL Query String (all values additionally URL-encoded):

edipi=123456&firstname=John&lastname=Von+Whitespace&address=5050+someplace&city=testCity&state=WV&zip=26505&email=foo%40example.com&phone=&title38status=V1&branchofservice=AF&dischargetype=A&timestamp=2017-09-25T20%3A43%3A02Z&signature=GRiMCS2rr_Xx_fOq-2TDof7-FW2tZC-Z3RtOPI94h4ZNaML1jiB2IyJXuEHA8Z5E7lIkXyPMC-VmqnIsxOyBz1-2tmDOLd8OkgNe1FdyKIeVoNwK2DGTKJwpc7cnPl-zHqW9z9HqnHf0vTjkQ_s4eZicAArXB6K63u3waEKomH3NTniK6MYjaFOZ99cjNPpZZ_LV2105cxvGUHdYI9ErPnAoF0ZBR7ntSObs7T7J-ZA7vg_c6wyzm3FrVdqGb8mtWSEb-RyY0waTpbayco_pBP1zGeVRpXhE_yiBYHc62ormTgwTxAgC2Spm0vcLuqs_kxI0kpYtwAwLGXveD6dpHA%3D%3D

Open Issues

  • Can the boolean attributes be normalized to standard JSON true/false values instead of "Y"/"N" strings? Yes.
  • Confirm that VIC implements any required business logic around tracking history of ID card requests - i.e. not letting a given veteran make an unlimited number of requests. Confirmed.

Changelog

  • 2017-08-17: retired and serviceconnected were removed from the official parameter list. Note they may still be present in some of the example code.
  • 2017-09-25: Added dischargetype to list of parameters.
  • 2017-11-30: Added title38status (has been integrated in code for a while, this document was out of date).
@leannammiller
Copy link

  • The VIC team is requesting type of discharge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment