Git Product home page Git Product logo

connect_vbms's Introduction

connect_vbms

Connect VBMS is a Ruby gem for communicating with the eFolder Service API provided by Veteran Benefits Management System (VBMS) at the Department of Veteran Affairs. Although the source code is open source, access to VBMS is restricted only to authorized users.

Prerequisites

The library currently uses Java for some encryption functionality. When this is replaced, the integration tests will continue to use the Java encryption/decryption utilities as a reference to check against. Python is currently used to generate documentation.

Tests

For the first run of the tests, install the Ruby dependencies and prepare the Java files & test creds:

> bundle install
> bundle exec rake tests:prepare

Every other time, just run the below from the root directory:

bundle exec rake

This will run all the tests, rubocop to identify any stylistic problems in the code and bundle-audit to find vulnerable versions of gems in Gemfile.lock. You must ensure your code passes all tests and has no Rubocop violations before submitting a pull request.

Tests normally mock all web requests so tests can be run without needing any credentials for VBMS systems. To run the integration tests against a VBMS server, you must specify all the necessary VBMS_CONNECT environment variables. You can then execute tests with CONNECT_VBMS_RUN_EXTERNAL_TESTS=1 bundle exec rake default and it will not use local webmocks.

Our development group also includes HTTPLog for viewing raw HTTP messages. To enable HTTPLog, use the CONNECT_VBMS_HTTPLOG=1 environment variable.

Docs

From the root directory, run:

rake docs

Contributing

Please read our Contributing guide to get started.

License

The project is in the public domain, and all contributions will also be released in the public domain. By submitting a pull request, you are agreeing to waive all rights to your contribution under the terms of the CC0 Public Domain Dedication.

This project constitutes an original work of the United States Government.

connect_vbms's People

Contributors

llimllib avatar amoose avatar alex avatar shanear avatar awong-dev avatar greggersh avatar leikkisa avatar pkarman avatar monfresh avatar kahlouie avatar nemodjuric avatar raymond-hughes avatar sjones352 avatar paultag avatar nanotone avatar thorntonmatthew avatar zurbergram avatar mdbenjam avatar lisac-usds avatar kencheeto avatar amprokop avatar omb-awong avatar artemvovk avatar joofsh avatar jiahuang avatar lowellrex avatar mmlumba avatar markolson avatar robertsosinski avatar slj avatar

Stargazers

 avatar  avatar  avatar Joey Moreland avatar Julia Allen avatar Aaron Houlihan avatar Premal Shah avatar  avatar Meckila avatar Michael Corrado avatar  avatar Robert Alexander avatar James Kassemi avatar  avatar Carlana Johnson avatar Joshua Cooper avatar  avatar

Watchers

Sha Hwang avatar Danny Chapman avatar Sean Bloomfield avatar Joey Moreland avatar Brian Gantick avatar Nick Heiner avatar James Cloos avatar  avatar Xena avatar  avatar Kevin avatar Teja avatar EmilyTav avatar Anne Kainic Palay avatar Abby Raskin avatar Sharon Warner avatar Andy avatar Lara Kohl avatar Stephen Bossom avatar Will Love avatar Present Occupant avatar  avatar Adam Ducker avatar Sarah Brooks avatar Shannon Sartin avatar  avatar Gina Kim avatar Karina Munoz Gonzalez avatar Joseph Stewart avatar  avatar  avatar  avatar  avatar Ian Buckholz avatar Lianna Newman avatar Mariana Almeida avatar Stephen Cooke avatar  avatar Alec Spottswood avatar Eric Moore avatar  avatar Aaron Lucas avatar Kate Brown avatar Anne Kainic avatar  avatar  avatar  avatar Oscar A. Ramirez avatar Dr. Corey Smith avatar  avatar

connect_vbms's Issues

"WIPR: Ruby encrypt"

This is the contents of @awong-dev's ruby-encrypt branch from the pre-OSS repo (rebasing this turned out to be really really hard):

From 5ee703615f401936ebe81273b3b9fa93fb91e546 Mon Sep 17 00:00:00 2001
From: "Albert J. Wong" <[email protected]>
Date: Fri, 19 Jun 2015 23:56:20 -0700
Subject: [PATCH 1/3] Implement SOAP decryption in pure Ruby.

  * Creates pkcs12 versions of the test keys.
  * Uses xmlenc gem for decryption.
  * Adds new ruby only decrypt_message_xml_ruby method.
  * Does NOT do WSSE timestamp validation.
  * Does NOT verify XML Dsig.

---
 Gemfile                                         |   2 ++
 Gemfile.lock                                    |  24 ++++++++++++++++++++++++
 spec/fixtures/test_keystore_importkey.p12       | Bin 0 -> 2594 bytes
 spec/fixtures/test_keystore_vbms_server_key.p12 | Bin 0 -> 2622 bytes
 spec/ruby_crypto_spec.rb                        |  21 +++++++++++----------
 src/vbms/common.rb                              |  16 ++++++++++++++++
 6 files changed, 53 insertions(+), 10 deletions(-)
 create mode 100644 spec/fixtures/test_keystore_importkey.p12
 create mode 100644 spec/fixtures/test_keystore_vbms_server_key.p12

diff --git a/Gemfile b/Gemfile
index 2169f6a..0650638 100644
--- a/Gemfile
+++ b/Gemfile
@@ -2,6 +2,8 @@ source 'https://rubygems.org'

 gem 'httpi'
 gem 'nokogiri'
+gem 'pg'
+gem 'xmlenc'

 # to install without postgres, "bundle install --without postgres"
 group :postgres do
diff --git a/Gemfile.lock b/Gemfile.lock
index 38aa4a5..07497c4 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,6 +1,16 @@
 GEM
   remote: https://rubygems.org/
   specs:
+    activemodel (4.2.1)
+      activesupport (= 4.2.1)
+      builder (~> 3.1)
+    activesupport (4.2.1)
+      i18n (~> 0.7)
+      json (~> 1.7, >= 1.7.7)
+      minitest (~> 5.1)
+      thread_safe (~> 0.3, >= 0.3.4)
+      tzinfo (~> 1.1)
+    builder (3.2.2)
     byebug (5.0.0)
       columnize (= 0.9.0)
     coderay (1.1.0)
@@ -10,8 +20,11 @@ GEM
       nokogiri (>= 1.4.3)
     httpi (2.3.0)
       rack
+    i18n (0.7.0)
+    json (1.8.3)
     method_source (0.8.2)
     mini_portile (0.6.2)
+    minitest (5.7.0)
     nokogiri (1.6.6.2)
       mini_portile (~> 0.6.0)
     pg (0.18.1)
@@ -36,6 +49,13 @@ GEM
       rspec-support (~> 3.2.0)
     rspec-support (3.2.2)
     slop (3.6.0)
+    thread_safe (0.3.5)
+    tzinfo (1.2.2)
+      thread_safe (~> 0.1)
+    xmlenc (0.2.0)
+      activemodel (>= 3.0.0)
+      activesupport (>= 3.0.0)
+      nokogiri (~> 1.6.0)

 PLATFORMS
   ruby
@@ -48,3 +68,7 @@ DEPENDENCIES
   pg
   pry-nav
   rspec
+  xmlenc
+
+BUNDLED WITH
+   1.10.4
diff --git a/spec/fixtures/test_keystore_importkey.p12 b/spec/fixtures/test_keystore_importkey.p12
new file mode 100644
index 0000000000000000000000000000000000000000..8596bb40c18e78f22a6f276a2766b5f5bfb3d266
GIT binary patch
literal 2594
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

literal 0
HcmV?d00001

diff --git a/spec/fixtures/test_keystore_vbms_server_key.p12 b/spec/fixtures/test_keystore_vbms_server_key.p12
new file mode 100644
index 0000000000000000000000000000000000000000..bc08028d962241c90e9d88c4549a311292127fb7
GIT binary patch
literal 2622
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
I<ZqPx7pr;A#{d8T

literal 0
HcmV?d00001

diff --git a/spec/ruby_crypto_spec.rb b/spec/ruby_crypto_spec.rb
index bcd552f..56d922f 100644
--- a/spec/ruby_crypto_spec.rb
+++ b/spec/ruby_crypto_spec.rb
@@ -5,14 +5,16 @@
   let (:plaintext_xml) { fixture_path('plaintext_basic_soap.xml') }
   let (:plaintext_unicode_xml) { fixture_path('plaintext_unicode_soap.xml') }
   let (:plaintext_request_name) { "getDocumentTypes" }
-  let (:test_keystore) { fixture_path('test_keystore.jks') }
+  let (:test_jks_keystore) { fixture_path('test_keystore.jks') }
+  let (:test_pc12_server_key) { fixture_path('test_keystore_vbms_server_key.p12') }
+  let (:test_pc12_client_key) { fixture_path('test_keystore_importkey.p12') }
   let (:test_keystore_pass) { "importkey" }

   it "encrypts in ruby, and decrypts using java" do
     # TODO(awong): Implement encrypt in ruby.
     encrypted_xml = VBMS.encrypted_soap_document(
-      plaintext_xml, test_keystore, test_keystore_pass, plaintext_request_name)
-    decrypted_xml = VBMS.decrypt_message_xml(encrypted_xml, test_keystore,
+      plaintext_xml, test_jks_keystore, test_keystore_pass, plaintext_request_name)
+    decrypted_xml = VBMS.decrypt_message_xml(encrypted_xml, test_jks_keystore,
                                              test_keystore_pass, plaintext_request_name)

     # Compare the decrypted request node with the original request node.
@@ -28,11 +30,10 @@
   end

   it "encrypts in java, and decrypts using ruby" do
-    # TODO(awong): Implement decrypt in ruby.
     encrypted_xml = VBMS.encrypted_soap_document(
-      plaintext_xml, test_keystore, test_keystore_pass, plaintext_request_name)
-    decrypted_xml = VBMS.decrypt_message_xml(encrypted_xml, test_keystore,
-                                             test_keystore_pass, plaintext_request_name)
+      plaintext_xml, test_jks_keystore, test_keystore_pass, plaintext_request_name)
+    decrypted_xml = VBMS.decrypt_message_xml_ruby(encrypted_xml, test_pc12_server_key,
+                                                  test_keystore_pass)

     # Compare the decrypted request node with the original request node.
     original_doc = Nokogiri::XML(fixture('plaintext_basic_soap.xml'))
@@ -49,8 +50,8 @@
   it "handles roundtripping utf-8 content." do
     pending("Correct Unicode Handling")
     encrypted_xml = VBMS.encrypted_soap_document(
-      plaintext_unicode_xml, test_keystore, test_keystore_pass, plaintext_request_name)
-    decrypted_xml = VBMS.decrypt_message_xml(encrypted_xml, test_keystore,
+      plaintext_unicode_xml, test_jks_keystore, test_keystore_pass, plaintext_request_name)
+    decrypted_xml = VBMS.decrypt_message_xml(encrypted_xml, test_jks_keystore,
                                              test_keystore_pass, plaintext_request_name)

     # Compare the decrypted request node with the original request node.
@@ -62,6 +63,6 @@
     decrypted_request_node = decrypted_doc.xpath(
       '/soapenv:Envelope/soapenv:Body/v4:getDocumentTypes',
       VBMS::XML_NAMESPACES)
-    expect(original_request_node).to be_equivalent_to(decrypted_request_node).respecting_element_order
+    expect(decrypted_request_node).to be_equivalent_to(original_request_node).respecting_element_order
   end
 end
diff --git a/src/vbms/common.rb b/src/vbms/common.rb
index 0fc29c1..f5838b5 100644
--- a/src/vbms/common.rb
+++ b/src/vbms/common.rb
@@ -1,4 +1,5 @@
 require 'open3'
+require 'xmlenc'

 module VBMS
   FILEDIR = File.dirname(File.absolute_path(__FILE__))
@@ -67,6 +68,21 @@ def self.decrypt_message_xml(in_xml, keyfile, keypass, logfile, ignore_timestamp
       end
     end

+    def self.decrypt_message_xml_ruby(encrypted_xml, keyfile_p12, keypass)
+      encrypted_doc = Xmlenc::EncryptedDocument.new(encrypted_xml)
+
+      # TODO(awong): Associate a keystore class with this API instead of
+      # passing path per request. The keystore client should take in a ds:KeyInfo
+      # node and know how to find the associated private key.
+      encryption_key = OpenSSL::PKCS12.new(File.read(keyfile_p12), keypass)
+      decrypted_doc = encrypted_doc.decrypt(encryption_key.key)
+
+      # TODO(awong): Signature verification.
+      # TODO(awong): Timestamp validation.
+      #
+      decrypted_doc
+    end
+
     def self.encrypted_soap_document(infile, keyfile, keypass, request_name)
       output, errors, status = Open3.capture3(DO_WSSE, '-e', '-i', infile, '-k', keyfile, '-p', keypass, '-n', request_name)
       if status != 0

From 6ab5eed82bd858b947d3a0a19eb61b7cf42f0cf0 Mon Sep 17 00:00:00 2001
From: "Albert J. Wong" <[email protected]>
Date: Mon, 22 Jun 2015 09:36:14 -0700
Subject: [PATCH 2/3] Implement SoapScum Keystore.

Allows for loading multiple pc12 certificates into a keystore
and then finding the matching cert based on a
wsse:SecurityTokenReference.

---
 .../soap-scum/client_x509_subject_keyinfo.xml      |  1 +
 .../soap-scum/server_x509_subject_keyinfo.xml      |  1 +
 spec/soap-scum_spec.rb                             | 36 +++++++++++++
 src/soap-scum.rb                                   | 59 ++++++++++++++++++++++
 4 files changed, 97 insertions(+)
 create mode 100644 spec/fixtures/soap-scum/client_x509_subject_keyinfo.xml
 create mode 100644 spec/fixtures/soap-scum/server_x509_subject_keyinfo.xml
 create mode 100644 spec/soap-scum_spec.rb
 create mode 100644 src/soap-scum.rb

diff --git a/spec/fixtures/soap-scum/client_x509_subject_keyinfo.xml b/spec/fixtures/soap-scum/client_x509_subject_keyinfo.xml
new file mode 100644
index 0000000..fb9542b
--- /dev/null
+++ b/spec/fixtures/soap-scum/client_x509_subject_keyinfo.xml
@@ -0,0 +1 @@
+<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><wsse:SecurityTokenReference><ds:X509Data><ds:X509IssuerSerial><ds:X509IssuerName>CN=Unknown,OU=Unknown,O=Unknown,L=Unknown,ST=Unknown,C=Unknown</ds:X509IssuerName><ds:X509SerialNumber>1743189802</ds:X509SerialNumber></ds:X509IssuerSerial></ds:X509Data></wsse:SecurityTokenReference></ds:KeyInfo>
diff --git a/spec/fixtures/soap-scum/server_x509_subject_keyinfo.xml b/spec/fixtures/soap-scum/server_x509_subject_keyinfo.xml
new file mode 100644
index 0000000..239deb0
--- /dev/null
+++ b/spec/fixtures/soap-scum/server_x509_subject_keyinfo.xml
@@ -0,0 +1 @@
+<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><wsse:SecurityTokenReference><ds:X509Data><ds:X509IssuerSerial><ds:X509IssuerName>CN=Unknown,OU=Unknown,O=Unknown,L=Unknown,ST=Unknown,C=Unknown</ds:X509IssuerName><ds:X509SerialNumber>1294758391</ds:X509SerialNumber></ds:X509IssuerSerial></ds:X509Data></wsse:SecurityTokenReference></ds:KeyInfo>
diff --git a/spec/soap-scum_spec.rb b/spec/soap-scum_spec.rb
new file mode 100644
index 0000000..3eaf7eb
--- /dev/null
+++ b/spec/soap-scum_spec.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+require 'soap-scum'
+
+describe :SoapScum do
+  describe "KeyStore" do
+    let (:server_x509_subject) { Nokogiri::XML(fixture('soap-scum/server_x509_subject_keyinfo.xml')) }
+    let (:client_x509_subject) { Nokogiri::XML(fixture('soap-scum/client_x509_subject_keyinfo.xml')) }
+    let (:server_pc12) { fixture_path('test_keystore_vbms_server_key.p12') }
+    let (:client_pc12) { fixture_path('test_keystore_importkey.p12') }
+    let (:keypass) { 'importkey' }
+
+    it "loads a pc12 file and cert" do
+      keystore = SoapScum::KeyStore.new
+      keystore.add_pc12(server_pc12, keypass)
+      server_pkcs12 = OpenSSL::PKCS12.new(File.read(server_pc12), keypass)
+
+      expect(keystore.get_key(server_x509_subject).to_der).to eql(server_pkcs12.key.to_der)
+      expect(keystore.get_certificate(server_x509_subject).to_der).to eql(server_pkcs12.certificate.to_der)
+    end
+
+    it "returns correct cert and key by subject" do
+      keystore = SoapScum::KeyStore.new
+      keystore.add_pc12(server_pc12, keypass)
+      keystore.add_pc12(client_pc12, keypass)
+
+      server_pkcs12 = OpenSSL::PKCS12.new(File.read(server_pc12), keypass)
+      client_pkcs12 = OpenSSL::PKCS12.new(File.read(client_pc12), keypass)
+
+      expect(keystore.get_key(server_x509_subject).to_der).to eql(server_pkcs12.key.to_der)
+      expect(keystore.get_certificate(server_x509_subject).to_der).to eql(server_pkcs12.certificate.to_der)
+
+      expect(keystore.get_key(client_x509_subject).to_der).to eql(client_pkcs12.key.to_der)
+      expect(keystore.get_certificate(client_x509_subject).to_der).to eql(client_pkcs12.certificate.to_der)
+    end
+  end
+end
diff --git a/src/soap-scum.rb b/src/soap-scum.rb
new file mode 100644
index 0000000..023003c
--- /dev/null
+++ b/src/soap-scum.rb
@@ -0,0 +1,59 @@
+require 'nokogiri'
+
+module SoapScum
+  module XMLNamespaces
+    SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/"
+    WSSE = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
+    WSU = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
+    DS = "http://www.w3.org/2000/09/xmldsig#"
+  end
+
+  class KeyStore
+    CertificateAndKey = Struct.new(:certificate, :key)
+    def initialize()
+      @by_subject = {}
+    end
+
+    def add_pc12(path, keypass = "")
+      pkcs12 = OpenSSL::PKCS12.new(File.read(path), keypass)
+      entry = CertificateAndKey.new(pkcs12.certificate, pkcs12.key)
+
+      @by_subject[x509_to_normalized_subject(pkcs12.certificate)] = entry
+    end
+
+    def get_key(keyinfo_node)
+      needle = keyinfo_to_normalized_subject(keyinfo_node)
+      @by_subject[needle].key
+    end
+
+    def get_certificate(keyinfo_node)
+      needle = keyinfo_to_normalized_subject(keyinfo_node)
+      @by_subject[needle].certificate
+    end
+
+   private
+    # Takes an x509 certificate and returns an array sorted in an order that
+    # allows for matching against other normalized subjects.
+    def x509_to_normalized_subject(certificate)
+      normalized_subject = certificate.subject.to_a.map {|name, value, _| [name, value] }.sort_by {|x| x[0] }
+      normalized_subject << ['SerialNumber', certificate.serial.to_s ]
+    end
+
+    def keyinfo_to_normalized_subject(keyinfo_node)
+      subject = keyinfo_node.at(
+        '/ds:KeyInfo/wsse:SecurityTokenReference/ds:X509Data/ds:X509IssuerSerial/ds:X509IssuerName',
+        ds: XMLNamespaces::DS,
+        wsse: XMLNamespaces::WSSE)
+      serial = keyinfo_node.at(
+        '/ds:KeyInfo/wsse:SecurityTokenReference/ds:X509Data/ds:X509IssuerSerial/ds:X509SerialNumber',
+        ds: XMLNamespaces::DS,
+        wsse: XMLNamespaces::WSSE)
+
+      normalized_subject = subject.inner_text.split(',').map {|x| x.split('=')}.sort_by{|x| x[0]}
+      normalized_subject << ['SerialNumber', serial.inner_text ]
+    end
+
+    def keyinfo_has_cert?(keyinfo_node)
+    end
+  end
+end

From d4f29bc02d59cdbe2a4f946dd44d94da0819418a Mon Sep 17 00:00:00 2001
From: "Albert J. Wong" <[email protected]>
Date: Mon, 22 Jun 2015 18:02:56 -0700
Subject: [PATCH 3/3] Implement MessageProcessor encryption.

No signature yet, but getting close.

---
 spec/soap-scum_spec.rb |  49 ++++++++++--
 src/soap-scum.rb       | 205 +++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 248 insertions(+), 6 deletions(-)

diff --git a/spec/soap-scum_spec.rb b/spec/soap-scum_spec.rb
index 3eaf7eb..19b21af 100644
--- a/spec/soap-scum_spec.rb
+++ b/spec/soap-scum_spec.rb
@@ -1,14 +1,17 @@
 require 'spec_helper'
 require 'soap-scum'
+require 'xmlenc'

 describe :SoapScum do
-  describe "KeyStore" do
-    let (:server_x509_subject) { Nokogiri::XML(fixture('soap-scum/server_x509_subject_keyinfo.xml')) }
-    let (:client_x509_subject) { Nokogiri::XML(fixture('soap-scum/client_x509_subject_keyinfo.xml')) }
-    let (:server_pc12) { fixture_path('test_keystore_vbms_server_key.p12') }
-    let (:client_pc12) { fixture_path('test_keystore_importkey.p12') }
-    let (:keypass) { 'importkey' }
+  let (:server_x509_subject) { Nokogiri::XML(fixture('soap-scum/server_x509_subject_keyinfo.xml')) }
+  let (:client_x509_subject) { Nokogiri::XML(fixture('soap-scum/client_x509_subject_keyinfo.xml')) }
+  let (:server_pc12) { fixture_path('test_keystore_vbms_server_key.p12') }
+  let (:client_pc12) { fixture_path('test_keystore_importkey.p12') }
+  let (:test_jks_keystore) { fixture_path('test_keystore.jks') }
+  let (:test_keystore_pass) { "importkey" }
+  let (:keypass) { 'importkey' }

+  describe "KeyStore" do
     it "loads a pc12 file and cert" do
       keystore = SoapScum::KeyStore.new
       keystore.add_pc12(server_pc12, keypass)
@@ -33,4 +36,38 @@
       expect(keystore.get_certificate(client_x509_subject).to_der).to eql(client_pkcs12.certificate.to_der)
     end
   end
+
+  describe "MessageProcessor" do
+    let (:keystore) {
+      keystore = SoapScum::KeyStore.new
+      keystore.add_pc12(server_pc12, keypass)
+      keystore
+    }
+    let (:message_processor) {
+      SoapScum::MessageProcessor.new(keystore)
+    }
+    let (:content_document) {
+      Nokogiri::XML('<hi-mom xmlns:example="http://example.com"><example:a-doc /></hi-mom>')
+    }
+
+    it "Creates basic soap envelope" do
+      soap_doc = message_processor.wrap_in_soap(content_document)
+      # TODO(awong): Verify structure.
+    end
+
+    it "Encrypts and signs a soap message" do
+      soap_doc = message_processor.wrap_in_soap(content_document)
+      encrypted_doc = message_processor.encrypt(soap_doc, keystore.all.first.certificate,
+                                                SoapScum::MessageProcessor::CryptoAlgorithms::RSA1_5,
+                                                SoapScum::MessageProcessor::CryptoAlgorithms::AES128,
+                                                soap_doc.at_xpath('/soapenv:Envelope/soapenv:Body',
+                                                                  soapenv: SoapScum::XMLNamespaces::SOAPENV).children)
+      encrypted_xml = encrypted_doc.serialize(encoding: 'UTF-8', save_with: Nokogiri::XML::Node::SaveOptions::AS_XML)
+      puts Xmlenc::EncryptedDocument.new(encrypted_xml).decrypt(keystore.all.first.key)
+      decrypted_xml = VBMS.decrypt_message_xml(encrypted_xml, test_jks_keystore,
+                                               test_keystore_pass, 'hi-mom')
+      puts decrypted_xml
+    end
+  end
+
 end
diff --git a/src/soap-scum.rb b/src/soap-scum.rb
index 023003c..4cdd6c2 100644
--- a/src/soap-scum.rb
+++ b/src/soap-scum.rb
@@ -1,15 +1,22 @@
+require 'base64'
 require 'nokogiri'

 module SoapScum
   module XMLNamespaces
     SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/"
     WSSE = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
+    WSSE11 = "http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd"
     WSU = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
     DS = "http://www.w3.org/2000/09/xmldsig#"
+    XENC = "http://www.w3.org/2001/04/xmlenc#"
   end

   class KeyStore
     CertificateAndKey = Struct.new(:certificate, :key)
+    def all()
+      @by_subject.map{|_, x| x}
+    end
+
     def initialize()
       @by_subject = {}
     end
@@ -56,4 +63,202 @@ def keyinfo_to_normalized_subject(keyinfo_node)
     def keyinfo_has_cert?(keyinfo_node)
     end
   end
+
+  class MessageProcessor
+    module CryptoAlgorithms
+      RSA1_5 = 'http://www.w3.org/2001/04/xmlenc#rsa-1_5'
+      RSA_OAEP = 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p'
+      # TODO(awong): Add triple-des support for xmlenc 1.0 compliance.
+      AES128 = 'http://www.w3.org/2001/04/xmlenc#aes128-cbc'
+      AES256 = 'http://www.w3.org/2001/04/xmlenc#aes256-cbc'
+    end
+
+    def initialize(keystore)
+      @keystore = keystore
+    end
+
+    # Creats a soap message wrapping the root node of the contents_doc.
+    #
+    # SOAP messages have the format
+    #
+    # <Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/">
+    #   <Body>
+    #   <!-- Content goes here -->
+    #   </Body>
+    # </Envelope>
+    #
+    # The header element is optional without mustUnderstand so this does not
+    # populate it.
+    #
+    # TODO(awong): Add mustUnderstand support.
+    def wrap_in_soap(contents_doc)
+        builder = Nokogiri::XML::Builder.new do |xml|
+          xml['soapenv'].Envelope('xmlns:soapenv' => XMLNamespaces::SOAPENV) do
+            xml['soapenv'].Body do
+              if !contents_doc.nil?
+                xml.parent << contents_doc.root.clone
+              end
+            end
+          end
+        end
+        builder.doc
+    end
+
+    def encrypt(soap_doc, certificate, keytransport_algorithm, cipher_algorithm, nodes_to_encrypt, validity: 5.minutes)
+      envelope = soap_doc.xpath('/soapenv:Envelope', soapenv: XMLNamespaces::SOAPENV)
+
+      # Ensure there is a header node.
+      header = envelope.at_xpath('/soapenv:Header', soapenv: XMLNamespaces::SOAPENV)
+      if header.nil?
+        header_builder = Nokogiri::XML::Builder.new do |xml|
+          xml["soapenv"].Header('xmlns:soapenv' => XMLNamespaces::SOAPENV)
+        end
+        envelope.children.first.add_previous_sibling(header_builder.doc.root)
+      end
+
+      # Create the wsse:Security header.
+      #   * Generate timestammp element.
+      #   * Create xmlenc template
+      #   * Create xmldsig template
+      Nokogiri::XML::Builder.with(soap_doc.at('/soap:Envelope/soap:Header', soap: XMLNamespaces::SOAPENV)) do |xml|
+        # TODO(awong): Do we need mustUnderstand?
+        xml['wsse'].Security('xmlns:wsse' => XMLNamespaces::WSSE,
+                             'xmlns:wsu' => XMLNamespaces::WSU,
+                             'soapenv:mustUnderstand' => '1') do
+          # TODO(awong): Allow configurable digest and signature methods.
+#          add_xmldsig_template(xml, certificate, "http://www.w3.org/2000/09/xmldsig#sha1", "http://www.w3.org/2000/09/xmldsig#rsa-sha1", nodes_to_sign)
+          add_xmlenc_template(xml, certificate, keytransport_algorithm, cipher_algorithm, nodes_to_encrypt)
+
+          # Add wsu:Timestamp
+          xml['wsu'].Timestamp('wsu:Id' => "TS-#{generate_id}") do
+            # Using localtime technically follows spec but seems to break
+            # various parsers.
+            now = Time.now.utc
+            xml['wsu'].Created now.xmlschema
+            xml['wsu'].Expires (now + validity).xmlschema
+          end
+        end
+      end
+
+      # Ensure IDs exist on elements like body.
+      # Perform actual signature on plaintext.
+      # Perform actual encryption
+      soap_doc
+    end
+
+   private
+    def generate_encrypted_data(node, encrypted_node_id, key_id, symmetric_key, cipher_algorithm, cipher)
+      cipher.reset
+      cipher.encrypt
+      cipher.key = symmetric_key
+      iv = cipher.random_iv
+
+      raw_xml = node.serialize(encoding: 'UTF-8', save_with: Nokogiri::XML::Node::SaveOptions::AS_XML)
+      cipher_text = iv + cipher.update(raw_xml) + cipher.final
+      builder = Nokogiri::XML::Builder.new do |xml|
+        xml['xenc'].EncryptedData('xmlns:xenc' => 'http://www.w3.org/2001/04/xmlenc#', Id: encrypted_node_id, Type: "http://www.w3.org/2001/04/xmlenc#Content") do
+          xml['xenc'].EncryptionMethod(Algorithm: cipher_algorithm)
+          xml['ds'].KeyInfo('xmlns:ds' => XMLNamespaces::DS) do
+            xml['wsse'].SecurityTokenReference('xmlns:wsse' => XMLNamespaces::WSSE,
+                                               'xmlns:wsse11' => XMLNamespaces::WSSE11,
+                                               'wsse11:TokenType' => 'http://docs.oasis-open.org/wss/oasis-wss-soap-message-security-1.1#EncryptedKey') do
+              xml['wsse'].Reference(URI: "##{key_id}")
+            end
+          end
+          xml['xenc'].CipherData do
+            xml['xenc'].CipherValue Base64.strict_encode64(cipher_text)
+          end
+        end
+      end
+      builder.doc.root
+    end
+
+    def get_block_cipher(cipher_algorithm)
+      case cipher_algorithm
+      when CryptoAlgorithms::AES128
+        return OpenSSL::Cipher::AES128.new(:CBC)
+      when CryptoAlgorithms::AES256
+        return OpenSSL::Cipher::AES256.new(:CBC)
+      else
+        raise "Unknown Cipher: #{cipher_algorithm}"
+      end
+    end
+
+    def generate_id
+      SecureRandom.hex(5)
+    end
+
+    # Takes an XMLBuilder and adds the XML Encryption template.
+    def add_xmlenc_template(xml, certificate, keytransport_algorithm, cipher_algorithm, nodes_to_encrypt)
+      # #5.4.1 Lists the valid ciphers and block sizes.
+      # Generate a secure key as per ruby docs.
+      cipher = get_block_cipher(cipher_algorithm)
+      cipher.encrypt
+      symmetric_key = cipher.random_key
+
+      key_id = "EK-#{generate_id}"
+      xml['xenc'].EncryptedKey('xmlns:xenc' => 'http://www.w3.org/2001/04/xmlenc#', Id: key_id) do
+        xml['xenc'].EncryptionMethod(Algorithm: keytransport_algorithm)
+        xml['ds'].KeyInfo('xmlns:ds' => XMLNamespaces::DS) do
+          xml['wsse'].SecurityTokenReference do
+            xml['ds'].X509Data do
+              xml['ds'].X509IssuerSerial do
+                xml['ds'].X509IssuerName certificate.subject.to_a.map { |name,value,_| "#{name}=#{value}" }.join(',')
+                xml['ds'].X509SerialNumber certificate.serial.to_s
+              end
+            end
+          end
+        end
+
+        xml['xenc'].CipherData do
+          xml['xenc'].CipherValue Base64.strict_encode64(certificate.public_key.public_encrypt(symmetric_key))
+        end
+
+        xml['xenc'].ReferenceList do
+          nodes_to_encrypt.each do |node|
+            encrypted_node_id = "ED-#{generate_id}"
+            encrypted_node = generate_encrypted_data(node, encrypted_node_id, key_id, symmetric_key, cipher_algorithm, cipher)
+            xml['xenc'].DataReference(URI: "##{encrypted_node_id}")
+            node.add_previous_sibling(encrypted_node)
+            node.remove
+          end
+        end
+      end
+    end
+
+    def add_dsig_template(xml, certificate, digest_method, signature_method, nodes_to_sign)
+      signature_id = "EK-#{generate_id}"
+      xml['ds'].Signature('xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#', Id: signature_id) do
+        xml['ds'].SignedInfo do
+          # TODO(awong): Allow modification of CanonicalizationMethod.
+          xml['ds'].CanonicalizationMethod(Algorithm: "http://www.w3.org/2001/10/xml-exc-c14n#") do
+            xml['ec'].InclusiveNamespaces('xmlns:ec' => "http://www.w3.org/2001/10/xml-exc-c14n#", PrefixList: "soapenv v4")
+          end
+          xml['ds'].SignatureMethod(Algorithm: signature_method)
+          nodes_to_sign.each do |node|
+            xml['ds'].Reference(URI: "##{node.Id}") do
+              xml['ds'].Transforms do
+                xml['ds'].Transform(Algorithm: "http://www.w3.org/2001/10/xml-exc-c14n#") do
+                  xml['ec'].InclusiveNamespaces('xmlns:ec' => "http://www.w3.org/2001/10/xml-exc-c14n#", PrefixList: "soapenv v4")
+                end
+              end
+              xml['ds'].DigestMethod(Algorithm: digest_method)
+              xml['ds'].DigestValue
+            end
+          end
+        end
+        xml['ds'].SignatureValue
+        xml['ds'].KeyInfo('xmlns:ds' => XMLNamespaces::DS, Id: "KI-#{generate_id}") do
+          xml['wsse'].SecurityTokenReference('wsu:Id' => "STR-#{generate_id}") do
+            xml['ds'].X509Data do
+              xml['ds'].X509IssuerSerial do
+                xml['ds'].X509IssuerName certificate.subject.to_a.map { |name,value,_| "#{name}=#{value}" }.join(',')
+                xml['ds'].X509SerialNumber certificate.serial.to_s
+              end
+            end
+          end
+        end
+      end
+    end
+  end
 end

Leverage approriate client to avoid unnecessary handshakes when making repeat HTTP requests

Issue:

When making multiple HTTP requests to the same host, we want to avoid new TCP connections and TLS handshakes for subsequent requests.

Is HTTPI the best HTTP lib to use if I want to make many requests to the same host?
The way it's API is structured leads me to believe that htat means a new TCP conncetion, a new TLS session, for each request.
Which would be cruddy.
Is there an HTTP API which is good?

Fix mistake in logging docs

In the request docs, it says that the :request key is an HTTPI::Request, actually it's an instance of whatever request type was passed to send_request, not the internal HTTPI one

Run tests without hitting the endpoints

Currently, tests of type: integration are skipped in CI because they attempt to touch an actual API. We need to create mocked responses for these tests in order to run our entire test suite without API credentials.


Questions:

  • Are there any security issues with mocking these API endpoints? should we use fixtures that are shared privately or are mocked endpoints ok? No
  • Are there any PII issues to consider? is there any risk of exposing PII in the test env? No

Eliminate redundant XML namespace mappings

There are three places now where we map short aliases to the same XML namespaces:

  • src/vbms/requests.rb
  • src/vbms/common.rb
  • the new SoapScum ruby crypto libraries

Such duplication happens when code is being rapidly iterated, so not noting this to shame anyone. But now we can probably collapse into a single location.

Proposed cleanups for the SoapScum module

Now that the bulk of Ruby encryption/decryption work has been done, I want to clean up a few things. As always, this isn't a judgment on any of the excellent and difficult coding required to make this work. I'm just here to do this sort of thing:

  • We probably should rename the SoapScum module to something more boring and predictable. Perhaps VBMS::SOAP
  • The SoapScum::XMLNamespaces declaration is redundant with some XML namespace declarations elsewhere in the code. We should replace with just one
  • Move the Keystore class to a separate file. Define some tests.
  • Make the SecureRandom.hex() use a slightly longer sequence to make collisions even less likely. Should we have some sort of ID registry to make them impossible (imagining how impossible it would be to debug an occasional error from the same ID being used twice in a document) No need for a registry

I will add more tweaks here and link to relevant PRs off the checkboxes.

Jruby fails on one of the ruby decryption tests

https://travis-ci.org/department-of-veterans-affairs/connect_vbms/jobs/76146034#L269

1) Ruby Encrypt/Decrypt test vs Java reference impl encrypts in java, and decrypts using ruby
   Failure/Error: decrypted_xml = VBMS.decrypt_message_xml_ruby(encrypted_xml, test_pc12_server_key,
   RuntimeError:
     org.w3c.dom.DOMException: NOT_SUPPORTED_ERR: The implementation does not support the requested type of object or operation. 
   # nokogiri/XmlNode.java:1662:in `add_child_node'
   # ./vendor/bundle/jruby/2.2.0/gems/nokogiri-1.6.6.2-java/lib/nokogiri/xml/node.rb:814:in `add_child_node_and_reparent_attrs'
   # ./vendor/bundle/jruby/2.2.0/gems/nokogiri-1.6.6.2-java/lib/nokogiri/xml/node.rb:125:in `add_child'
   # ./vendor/bundle/jruby/2.2.0/gems/nokogiri-1.6.6.2-java/lib/nokogiri/xml/node.rb:405:in `parent='
   # ./vendor/bundle/jruby/2.2.0/gems/nokogiri-1.6.6.2-java/lib/nokogiri/xml/document_fragment.rb:25:in `block in initialize'
   # ./vendor/bundle/jruby/2.2.0/gems/nokogiri-1.6.6.2-java/lib/nokogiri/xml/node_set.rb:187:in `block in each'
   # ./vendor/bundle/jruby/2.2.0/gems/nokogiri-1.6.6.2-java/lib/nokogiri/xml/node_set.rb:186:in `each'
   # ./vendor/bundle/jruby/2.2.0/gems/nokogiri-1.6.6.2-java/lib/nokogiri/xml/document_fragment.rb:25:in `initialize'
   # nokogiri/XmlDocumentFragment.java:107:in `new'
   # ./vendor/bundle/jruby/2.2.0/gems/nokogiri-1.6.6.2-java/lib/nokogiri/xml/node.rb:355:in `fragment'
   # ./vendor/bundle/jruby/2.2.0/gems/nokogiri-1.6.6.2-java/lib/nokogiri/xml/node.rb:796:in `coerce'
   # ./vendor/bundle/jruby/2.2.0/gems/nokogiri-1.6.6.2-java/lib/nokogiri/xml/node.rb:257:in `replace'
   # ./vendor/bundle/jruby/2.2.0/gems/xmlenc-0.2.0/lib/xmlenc/encrypted_data.rb:39:in `decrypt'
   # ./vendor/bundle/jruby/2.2.0/gems/xmlenc-0.2.0/lib/xmlenc/encrypted_document.rb:21:in `block in decrypt'
   # ./vendor/bundle/jruby/2.2.0/gems/xmlenc-0.2.0/lib/xmlenc/encrypted_document.rb:18:in `decrypt'
   # ./src/vbms/common.rb:78:in `decrypt_message_xml_ruby'
   # ./spec/ruby_crypto_spec.rb:35:in `block in (root)'

Minor Aesthetic Tweaks

Now that others have bravely pioneered the path and got it working, I'm just here to do some minor cleanup in their wake. Just a few other things I want to do, noting them here in one place:

  • Change constructor of client.rb to take an options hash instead (punting on this for now)
  • Add methods to client.rb to create a message type and call send (nixed by Alex)
  • # TODO: this can't really be correct in client#build_request (fixed by @llimllib)
  • Make VBMS::Document into real Ruby object with method to construct from XML tree (PR #58)
  • Rename Client#send to have a name that doesn't shadow Object#send (PR #71)

Probably a few other things to add here and below in comment, but this is a start for my notes. This is not a critique of prior code.

Make use of quotations consistent

Enable single_quotes rubocop rule. This is purely an aesthetic change for consistency.

  • Change .rubocop.yml to include the single quotes cop (currently disabled) and auto-correct the codebase for consistency with rubocop -a
Style/StringLiterals:
  Description: Checks if uses of quotes match the configured preference.
  StyleGuide: https://github.com/bbatsov/ruby-style-guide#consistent-string-literals
  Enabled: true
  EnforcedStyle: single_quotes
  SupportedStyles:
  - single_quotes
  - double_quotes

add toggle knob for client logging

this bit of logging sticks tons of data in /tmp/, which can blow up in size when running in prod.

Worth not hanging on to data we don't want (as well) -- perhaps only keep this around when we're in debug mode?

All received dates are the same

Looks like connect_vbms is returning the same received dates for every document. Looks like it's just traversing the XML and finding the first one.

For example, here's the eFolder and receipt dates:

image

Here's the relevant part of the XML response.

<listDocumentsResponse xmlns="http://vbms.vba.va.gov/external/eDocumentService/v4" xmlns:aw="http://vbms.vba.va.gov/cdm/award/v4" xmlns:claim="http://vbms.vba.va.gov/cdm/claim/v4" xmlns:com="http://vbms.vba.va.gov/cdm/common/v4" xmlns:doc="http://vbms.vba.va.gov/cdm/document/v4" xmlns:edoc="http://vbms.vba.va.gov/external/eDocumentService/v4" xmlns:part="http://vbms.vba.va.gov/cdm/participant/v4"><edoc:result actionable="false" category="19" docType="179" fileNumber="22222222" filename="form9.pdf" id="{0D290449-FC7C-4EB0-A33F-F41ECF92BEB2}" mimeType="application/pdf" newMail="false" path="22222222/form9.pdf" source="VACOLS" veteranFirstName="Joe" veteranLastName="Snuffy" veteranPersonId="0"><doc:receivedDt>2015-08-03-04:00</doc:receivedDt></edoc:result><edoc:result actionable="false" docType="625" externalId="123" fileNumber="22222222" filename="VA8-0d7e1c1f2630bf703d416f9a4943fcb9.pdf" id="{B0D6084F-0000-CAB7-B474-3333ADE5048D}" mimeType="application/pdf" newMail="true" path="22222222/VA8-0d7e1c1f2630bf703d416f9a4943fcb9.pdf" source="Form 8" subject="Form 8" veteranFirstName="John" veteranLastName="Cash" veteranMiddleName="J" veteranPersonId="0"><doc:receivedDt>2015-08-07-04:00</doc:receivedDt></edoc:result><edoc:result actionable="false" docType="625" externalId="123" fileNumber="22222222" filename="VA8-c2f9be97507641d87f08bfd0f1708d70.pdf" id="{B08F3D4F-0000-C21C-AEA7-B23733B9778A}" mimeType="application/pdf" newMail="true" path="22222222/VA8-c2f9be97507641d87f08bfd0f1708d70.pdf" source="Form 8" subject="Form 8" veteranFirstName="JOE" veteranLastName="SNUFFY" veteranMiddleName="L" veteranPersonId="0"><doc:receivedDt>2015-08-17-04:00</doc:receivedDt></edoc:result><edoc:result actionable="false" docType="625" externalId="123" fileNumber="22222222" filename="VA8-b6aa28f584c50f728d76135ba929db0c.pdf" id="{C0D1424F-0000-C31F-8072-16D1D9FF05F0}" mimeType="application/pdf" newMail="true" path="22222222/VA8-b6aa28f584c50f728d76135ba929db0c.pdf" source="VACOLS" subject="Form 8" veteranFirstName="JOE" veteranLastName="SNUFFY" veteranMiddleName="L" veteranPersonId="0"><doc:receivedDt>2015-08-18-04:00</doc:receivedDt></edoc:result><edoc:result actionable="false" docType="625" externalId="123" fileNumber="22222222" filename="VA8-d3491b7cfd37dc89148b89c599205a5e.pdf" id="{D09A4B4F-0000-C11E-8F9D-32010EA3B72D}" mimeType="application/pdf" newMail="true" path="22222222/VA8-d3491b7cfd37dc89148b89c599205a5e.pdf" source="Form 8" subject="Form 8" veteranFirstName="JOE" veteranLastName="SNUFFY" veteranMiddleName="L" veteranPersonId="0"><doc:receivedDt>2015-08-20-04:00</doc:receivedDt></edoc:result><edoc:result actionable="false" docType="625" externalId="123" fileNumber="22222222" filename="VA8-c5ee42e1a2fe08f44c4a6034f53c9458.pdf" id="{10514C4F-0000-C714-8CB7-BAC5F891DE79}" mimeType="application/pdf" newMail="true" path="22222222/VA8-c5ee42e1a2fe08f44c4a6034f53c9458.pdf" source="Form 8" subject="Form 8" veteranFirstName="JOE" veteranLastName="SNUFFY" veteranMiddleName="L" veteranPersonId="0"><doc:receivedDt>2015-08-20-04:00</doc:receivedDt></edoc:result><edoc:result actionable="false" docType="625" externalId="123" fileNumber="22222222" filename="VA8-779ef123d13f5b24c4def32af6104529.pdf" id="{50624C4F-0000-CD1A-9419-0EC4C097123C}" mimeType="application/pdf" newMail="true" path="22222222/VA8-779ef123d13f5b24c4def32af6104529.pdf" source="VACOLS" subject="Form 8" veteranFirstName="JOE" veteranLastName="SNUFFY" veteranMiddleName="L" veteranPersonId="0"><doc:receivedDt>2015-08-20-04:00</doc:receivedDt></edoc:result><edoc:result actionable="false" docType="625" externalId="123" fileNumber="22222222" filename="VA8-38144a6d5e653f4463307b7ecfb0ba58.pdf" id="{F0684C4F-0000-C515-8665-D5EACD763D07}" mimeType="application/pdf" newMail="true" path="22222222/VA8-38144a6d5e653f4463307b7ecfb0ba58.pdf" source="VACOLS" subject="Form 8" veteranFirstName="JOE" veteranLastName="SNUFFY" veteranMiddleName="L" veteranPersonId="0"><doc:receivedDt>2015-08-20-04:00</doc:receivedDt></edoc:result><edoc:result actionable="false" category="19" docType="178" externalId="123" fileNumber="22222222" filename="VA8-c7a9d88102fc4dbaeca92961300a4c2f.pdf" id="{E095514F-0000-CF13-AEFC-BE8E1B2859CC}" mimeType="application/pdf" newMail="false" path="22222222/VA8-c7a9d88102fc4dbaeca92961300a4c2f.pdf" source="VACOLS" subject="Form 8" veteranFirstName="JOE" veteranLastName="SNUFFY" veteranMiddleName="L" veteranPersonId="0"><doc:receivedDt>2011-02-04-05:00</doc:receivedDt></edoc:result><edoc:result actionable="false" category="19" docType="95" fileNumber="22222222" filename="soc.pdf" id="{4BD6C394-74EF-46AD-A3A4-1C8F15204BF3}" mimeType="application/pdf" newMail="false" path="22222222/soc.pdf" source="VACOLS" veteranFirstName="Joe" veteranLastName="Snuffy" veteranPersonId="0"><doc:receivedDt>2015-08-07-04:00</doc:receivedDt></edoc:result><edoc:result actionable="false" category="19" docType="73" fileNumber="22222222" filename="nod.pdf" id="{583504AA-42E0-4878-B60E-E3FDB05ECA68}" mimeType="application/pdf" newMail="false" path="22222222/nod.pdf" source="VACOLS" veteranFirstName="Joe" veteranLastName="Snuffy" veteranPersonId="0"><doc:receivedDt>2011-02-04-05:00</doc:receivedDt></edoc:result><edoc:result actionable="false" category="19" docType="97" fileNumber="22222222" filename="ssoc.pdf" id="{C00035B4-E552-4089-9CA7-658A9402A195}" mimeType="application/pdf" newMail="false" path="22222222/ssoc.pdf" source="VACOLS" veteranFirstName="Joe" veteranLastName="Snuffy" veteranPersonId="0"><doc:receivedDt>2015-08-08-04:00</doc:receivedDt></edoc:result><edoc:result actionable="false" category="19" docType="178" fileNumber="22222222" filename="form8.pdf" id="{46976FB5-88C8-4871-A7D5-A1654660469C}" mimeType="application/pdf" newMail="false" path="22222222/form8.pdf" source="VACOLS" veteranFirstName="Joe" veteranLastName="Snuffy" veteranPersonId="0"><doc:receivedDt>2015-08-02-04:00</doc:receivedDt></edoc:result></listDocumentsResponse>

Here's what shows up in connect_vbms:

[26] pry(main)> vbms = VBMS::Client.from_env_vars(env_name: ENV["CONNECT_VBMS_ENV"])
=> #<VBMS::Client:0x0e7e455d
 @cacert="/Users/vacomaherj/GitHub/deployment/decrypted_creds/uat_creds/caseflow_uat_ca_cert",
 @client_cert="/Users/vacomaherj/GitHub/deployment/decrypted_creds/uat_creds/caseflow_uat_client_cert",
 @endpoint_url="https://filenet.uat.vbms.aide.oit.va.gov/vbmsp2-cms/streaming/eDocumentService-v4",
 @key="/Users/vacomaherj/GitHub/deployment/decrypted_creds/uat_creds/caseflow_uat_keyfile",
 @keyfile="/Users/vacomaherj/GitHub/deployment/decrypted_creds/uat_creds/caseflow_uat_jks",
 @keypass="importkey",
 @logger=nil,
 @saml="/Users/vacomaherj/GitHub/deployment/decrypted_creds/uat_creds/caseflow_uat_saml">
[27] pry(main)> docs = vbms.send(VBMS::Requests::ListDocuments.new('22222222'))
=> [#<struct Struct::Document document_id="{0D290449-FC7C-4EB0-A33F-F41ECF92BEB2}", filename="form9.pdf", doc_type="179", source="VACOLS", received_at=Mon, 03 Aug 2015>,
 #<struct Struct::Document
  document_id="{B0D6084F-0000-CAB7-B474-3333ADE5048D}",
  filename="VA8-0d7e1c1f2630bf703d416f9a4943fcb9.pdf",
  doc_type="625",
  source="Form 8",
  received_at=Mon, 03 Aug 2015>,
 #<struct Struct::Document
  document_id="{B08F3D4F-0000-C21C-AEA7-B23733B9778A}",
  filename="VA8-c2f9be97507641d87f08bfd0f1708d70.pdf",
  doc_type="625",
  source="Form 8",
  received_at=Mon, 03 Aug 2015>,
 #<struct Struct::Document
  document_id="{C0D1424F-0000-C31F-8072-16D1D9FF05F0}",
  filename="VA8-b6aa28f584c50f728d76135ba929db0c.pdf",
  doc_type="625",
  source="VACOLS",
  received_at=Mon, 03 Aug 2015>,
 #<struct Struct::Document
  document_id="{D09A4B4F-0000-C11E-8F9D-32010EA3B72D}",
  filename="VA8-d3491b7cfd37dc89148b89c599205a5e.pdf",
  doc_type="625",
  source="Form 8",
  received_at=Mon, 03 Aug 2015>,
 #<struct Struct::Document
  document_id="{10514C4F-0000-C714-8CB7-BAC5F891DE79}",
  filename="VA8-c5ee42e1a2fe08f44c4a6034f53c9458.pdf",
  doc_type="625",
  source="Form 8",
  received_at=Mon, 03 Aug 2015>,
 #<struct Struct::Document
  document_id="{50624C4F-0000-CD1A-9419-0EC4C097123C}",
  filename="VA8-779ef123d13f5b24c4def32af6104529.pdf",
  doc_type="625",
  source="VACOLS",
  received_at=Mon, 03 Aug 2015>,
 #<struct Struct::Document
  document_id="{F0684C4F-0000-C515-8665-D5EACD763D07}",
  filename="VA8-38144a6d5e653f4463307b7ecfb0ba58.pdf",
  doc_type="625",
  source="VACOLS",
  received_at=Mon, 03 Aug 2015>,
 #<struct Struct::Document
  document_id="{E095514F-0000-CF13-AEFC-BE8E1B2859CC}",
  filename="VA8-c7a9d88102fc4dbaeca92961300a4c2f.pdf",
  doc_type="178",
  source="VACOLS",
  received_at=Mon, 03 Aug 2015>,
 #<struct Struct::Document document_id="{4BD6C394-74EF-46AD-A3A4-1C8F15204BF3}", filename="soc.pdf", doc_type="95", source="VACOLS", received_at=Mon, 03 Aug 2015>,
 #<struct Struct::Document document_id="{583504AA-42E0-4878-B60E-E3FDB05ECA68}", filename="nod.pdf", doc_type="73", source="VACOLS", received_at=Mon, 03 Aug 2015>,
 #<struct Struct::Document document_id="{C00035B4-E552-4089-9CA7-658A9402A195}", filename="ssoc.pdf", doc_type="97", source="VACOLS", received_at=Mon, 03 Aug 2015>,
 #<struct Struct::Document document_id="{46976FB5-88C8-4871-A7D5-A1654660469C}", filename="form8.pdf", doc_type="178", source="VACOLS", received_at=Mon, 03 Aug 2015>]

All the dates 8/3/2015.

Fix time issue with received_date?

In UploadDocumentWithAssociations there is this code that converts to local date. I think depending on time of year, this might have issues when EDT is only 4 hours off of GMT? Or will it be okay since it just shifts to 1am locally and not 12am?

def received_date
  @received_at.getlocal('-05:00').strftime('%Y-%m-%d-05:00')
end

Split requests.rb into multiple files

I want to split the requests.rb file into a separate file for each class because I'm sure that rubocop will make noises and I feel like it's another thing to clean up now that the dust has settled.

Stand up CI

Skipping the integration tests for now I guess.

Rename Client#send method

Rename the #send method to a more appropriate name and alias #send to include a deprecation warning that 1.0 will no longer support #send method. Update documentation as necessary. send.

Fix failing tests of the xmlenc gem

Given this is a major dependency for us, it's disconcerting how many tests for it fail. There are currently 12 failing tests for Jruby. The gem maintainer is relatively responsive, but we should help fix these tests in case we have future JRuby patches.

"Implement SoapScum Keystore"

This is the contents of @awong-dev's ruby-keystore PR from the pre-OSS repo:

From 9f72046c6bbaf195fdb8b74413af1388db4b0d61 Mon Sep 17 00:00:00 2001
From: "Albert J. Wong" <[email protected]>
Date: Mon, 22 Jun 2015 09:36:14 -0700
Subject: [PATCH] Implement SoapScum Keystore.

Allows for loading multiple pc12 certificates into a keystore
and then finding the matching cert based on a
wsse:SecurityTokenReference.

---
 .../soap-scum/client_x509_subject_keyinfo.xml      |  1 +
 .../soap-scum/server_x509_subject_keyinfo.xml      |  1 +
 spec/soap-scum_spec.rb                             | 36 +++++++++++++
 src/soap-scum.rb                                   | 59 ++++++++++++++++++++++
 4 files changed, 97 insertions(+)
 create mode 100644 spec/fixtures/soap-scum/client_x509_subject_keyinfo.xml
 create mode 100644 spec/fixtures/soap-scum/server_x509_subject_keyinfo.xml
 create mode 100644 spec/soap-scum_spec.rb
 create mode 100644 src/soap-scum.rb

diff --git a/spec/fixtures/soap-scum/client_x509_subject_keyinfo.xml b/spec/fixtures/soap-scum/client_x509_subject_keyinfo.xml
new file mode 100644
index 0000000..fb9542b
--- /dev/null
+++ b/spec/fixtures/soap-scum/client_x509_subject_keyinfo.xml
@@ -0,0 +1 @@
+<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><wsse:SecurityTokenReference><ds:X509Data><ds:X509IssuerSerial><ds:X509IssuerName>CN=Unknown,OU=Unknown,O=Unknown,L=Unknown,ST=Unknown,C=Unknown</ds:X509IssuerName><ds:X509SerialNumber>1743189802</ds:X509SerialNumber></ds:X509IssuerSerial></ds:X509Data></wsse:SecurityTokenReference></ds:KeyInfo>
diff --git a/spec/fixtures/soap-scum/server_x509_subject_keyinfo.xml b/spec/fixtures/soap-scum/server_x509_subject_keyinfo.xml
new file mode 100644
index 0000000..239deb0
--- /dev/null
+++ b/spec/fixtures/soap-scum/server_x509_subject_keyinfo.xml
@@ -0,0 +1 @@
+<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><wsse:SecurityTokenReference><ds:X509Data><ds:X509IssuerSerial><ds:X509IssuerName>CN=Unknown,OU=Unknown,O=Unknown,L=Unknown,ST=Unknown,C=Unknown</ds:X509IssuerName><ds:X509SerialNumber>1294758391</ds:X509SerialNumber></ds:X509IssuerSerial></ds:X509Data></wsse:SecurityTokenReference></ds:KeyInfo>
diff --git a/spec/soap-scum_spec.rb b/spec/soap-scum_spec.rb
new file mode 100644
index 0000000..3eaf7eb
--- /dev/null
+++ b/spec/soap-scum_spec.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+require 'soap-scum'
+
+describe :SoapScum do
+  describe "KeyStore" do
+    let (:server_x509_subject) { Nokogiri::XML(fixture('soap-scum/server_x509_subject_keyinfo.xml')) }
+    let (:client_x509_subject) { Nokogiri::XML(fixture('soap-scum/client_x509_subject_keyinfo.xml')) }
+    let (:server_pc12) { fixture_path('test_keystore_vbms_server_key.p12') }
+    let (:client_pc12) { fixture_path('test_keystore_importkey.p12') }
+    let (:keypass) { 'importkey' }
+
+    it "loads a pc12 file and cert" do
+      keystore = SoapScum::KeyStore.new
+      keystore.add_pc12(server_pc12, keypass)
+      server_pkcs12 = OpenSSL::PKCS12.new(File.read(server_pc12), keypass)
+
+      expect(keystore.get_key(server_x509_subject).to_der).to eql(server_pkcs12.key.to_der)
+      expect(keystore.get_certificate(server_x509_subject).to_der).to eql(server_pkcs12.certificate.to_der)
+    end
+
+    it "returns correct cert and key by subject" do
+      keystore = SoapScum::KeyStore.new
+      keystore.add_pc12(server_pc12, keypass)
+      keystore.add_pc12(client_pc12, keypass)
+
+      server_pkcs12 = OpenSSL::PKCS12.new(File.read(server_pc12), keypass)
+      client_pkcs12 = OpenSSL::PKCS12.new(File.read(client_pc12), keypass)
+
+      expect(keystore.get_key(server_x509_subject).to_der).to eql(server_pkcs12.key.to_der)
+      expect(keystore.get_certificate(server_x509_subject).to_der).to eql(server_pkcs12.certificate.to_der)
+
+      expect(keystore.get_key(client_x509_subject).to_der).to eql(client_pkcs12.key.to_der)
+      expect(keystore.get_certificate(client_x509_subject).to_der).to eql(client_pkcs12.certificate.to_der)
+    end
+  end
+end
diff --git a/src/soap-scum.rb b/src/soap-scum.rb
new file mode 100644
index 0000000..023003c
--- /dev/null
+++ b/src/soap-scum.rb
@@ -0,0 +1,59 @@
+require 'nokogiri'
+
+module SoapScum
+  module XMLNamespaces
+    SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/"
+    WSSE = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
+    WSU = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
+    DS = "http://www.w3.org/2000/09/xmldsig#"
+  end
+
+  class KeyStore
+    CertificateAndKey = Struct.new(:certificate, :key)
+    def initialize()
+      @by_subject = {}
+    end
+
+    def add_pc12(path, keypass = "")
+      pkcs12 = OpenSSL::PKCS12.new(File.read(path), keypass)
+      entry = CertificateAndKey.new(pkcs12.certificate, pkcs12.key)
+
+      @by_subject[x509_to_normalized_subject(pkcs12.certificate)] = entry
+    end
+
+    def get_key(keyinfo_node)
+      needle = keyinfo_to_normalized_subject(keyinfo_node)
+      @by_subject[needle].key
+    end
+
+    def get_certificate(keyinfo_node)
+      needle = keyinfo_to_normalized_subject(keyinfo_node)
+      @by_subject[needle].certificate
+    end
+
+   private
+    # Takes an x509 certificate and returns an array sorted in an order that
+    # allows for matching against other normalized subjects.
+    def x509_to_normalized_subject(certificate)
+      normalized_subject = certificate.subject.to_a.map {|name, value, _| [name, value] }.sort_by {|x| x[0] }
+      normalized_subject << ['SerialNumber', certificate.serial.to_s ]
+    end
+
+    def keyinfo_to_normalized_subject(keyinfo_node)
+      subject = keyinfo_node.at(
+        '/ds:KeyInfo/wsse:SecurityTokenReference/ds:X509Data/ds:X509IssuerSerial/ds:X509IssuerName',
+        ds: XMLNamespaces::DS,
+        wsse: XMLNamespaces::WSSE)
+      serial = keyinfo_node.at(
+        '/ds:KeyInfo/wsse:SecurityTokenReference/ds:X509Data/ds:X509IssuerSerial/ds:X509SerialNumber',
+        ds: XMLNamespaces::DS,
+        wsse: XMLNamespaces::WSSE)
+
+      normalized_subject = subject.inner_text.split(',').map {|x| x.split('=')}.sort_by{|x| x[0]}
+      normalized_subject << ['SerialNumber', serial.inner_text ]
+    end
+
+    def keyinfo_has_cert?(keyinfo_node)
+    end
+  end
+end

Scan CUI and caseflow for connect VBMS methods

The goals:

  • Ensure we are not removing (or planning to) remove any used methods in the API.
  • Ensure pessimistic versioning (twiddle-wakka ~>) or another mechanism is in place to keep breaking changes from entering these repos.
    • caseflow currently has the gem vendored but, let's also add proper versioning.
    • CUI aka DrTurboTax
    • efolder-express has no versioning whatsoever

Use pure Ruby for encryption and remove Java

We should be able to do all the crypto stuff with just ruby.


Use ruby for encryption and decryption.

  • Verify structure in SOAP envelope
    • add specs to validate SOAP structure
  • Sign appropriate nodes (envelope and document) depending on type and timestamp
  • Permit encryption algorithm to be dynamic (SHA1? pshaww!)

Formalize a Git branching methodology

As we move toward a more stable version of this gem, we need to formalize a process for contributing to the gem's code without breaking applications that are using it. I would like to propose that we officially adopt git flow and merge in pull requests to a separate develop branch instead of master. Then we can use master for merging from develop and updating the version of the gem when we are ready for new releases.

Add to Gemnasium

Gemnasium is a service for alerting developers when their projects are using old gems and if any gems have active security vulnerabilities. It is free for public and open source projects. We should enable it for connect_vbms.

Remove wiki

Since it's not being used, let's remove it from the repo settings. Also, point users to the /docs folder via the README.

Only an owner may perform this action.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.